shardを増やしたのに、CIの待ち時間が変わらない
CIのPlaywrightが遅いという相談を受けたとき、大体最初に出てくる対策が「workersを増やす」か「shardを増やす」です。実際に私も昔はそうしていました。マシンを4台に増やして--shard=1/4から--shard=4/4まで並べたのに、CI全体の待ち時間がほとんど変わらない。1年目の頃の私が、まさにこれで首をひねっていました。
原因を調べてみると、大抵4つのshardのうち1つだけが突出して遅く、残り3つはとっくに終わって待っているだけ、という状態でした。マシンを増やしたのに速くなった実感がないのは当然です。増やした分がただ待機時間になっていたわけです。
workers・fullyParallel・shardは別レイヤーの話
ここでまず整理しておきたいのが、Playwrightの「並列」に関わる設定が実は3つあり、それぞれ効く場所が違うという点です。1年目の頃の私は、この3つを「並列を強くする似たオプション」くらいに雑にまとめていました。
- workers: 1台のマシンの中で、いくつのワーカープロセスを同時に立ち上げるか。
--workers=4やconfigのworkers: 4で指定します。 - fullyParallel: 同じファイルの中にある複数のtestも並列に流すかどうか。デフォルトはファイル単位の並列で、1ファイル内のtestは順番に実行されます。
- –shard=X/Y: テストスイート全体をY個に分割し、複数マシンに分散して実行する仕組みです。
公式ドキュメントにも、workersは「the maximum number of parallel worker processes」を制御するもの、fullyParallelは有効にすると「runs all tests in all files of a specific project in parallel」になるものとはっきり書かれています。workersとfullyParallelは同じマシンの中の並列度の話で、shardは複数マシンへの分散の話です。ここが噛み合っていないと、shardを増やしても意味がある改善にならないんですね。
本命の罠:shardは「均等」を時間で見ていない
ここからが本番です。3つのレイヤーを理解しても、まだ罠が残っています。--shardがテストを分割する基準です。公式のsharding解説ページを見ると、デフォルトの挙動は次のように説明されています。
entire test files are assigned to shards
ファイル単位でshardに割り当てる、ということです。つまりファイルAに100個のtestがあってもファイルBに1個しかなくても、それぞれ「1ファイル」として数えられます。ここに実行時間は一切登場しません。
この分割ロジックの詳しい中身(どんな順序で並べているか)までは、公式ドキュメントには明記がありませんでした。ここは正直に書いておきます。ある事例(kickflow社のブログ)では、自社のshard 4に集中していたテストの内訳を実際に調べたところ、アルファベット順で隣接するディレクトリ(organization/とroute/)が同じshardに固まっていたことから、「ファイル名のアルファベット順にソートしてから均等に分割している」と推測したと書かれています。ただしこれは観測からの逆算で、Playwright側のソースコードや公式ドキュメントで直接裏を取った話ではないとも明記されています。私も同じ検証はできていないので、「そう見える」という事例がある、くらいの理解にとどめておくのが正確だと思います(本当にアルファベット順そのままなのか、もう少し複雑な規則が入っているのか、そこまでは私も断言できません)。
いずれにしても確実に言えるのは、shardは「テストファイルの数」や「testの数」を揃えることはしても、「実行にかかる時間」は見ていないという点です。1つのファイルに重いE2Eシナリオが3本集まっていれば、そのファイルが割り当たったshardだけが極端に遅くなります。
「全体の待ち時間=一番遅いshardの時間」という見方
ここで意識を変える必要があります。CI全体の所要時間は、shardの平均時間では決まりません。一番遅いshardの時間で決まります。9個のshardが5分で終わっても、残り1個が40分かかれば、CI全体は40分待つことになります。workersやマシン数をいくら増やしても、この「一番遅いやつ」を短くしない限り、体感は変わりません。
ある事例(同じくkickflow社のブログ)では、テスト数がほぼ均等(74〜75件)に割り振られたshardの間で、実行時間に最大8倍の差が出ていたと報告されています。具体的には「shard 4が80分かかるのに対し、shard 6は10分で終わる」という状態だったそうです。テスト数は揃っているのに、実行時間だけ8倍違う。これがまさに「均等分割はテストの個数の話であって、時間の話ではない」ことの実例です。
この事例では、実行時間ベースでテストを再配分する仕組み(ビンパッキング的な考え方で、直近の実行時間の実績を使ってshardの重みを調整する)を自作し、段階的にバランスを整えたところ、最も遅いshardが80分から35分まで縮まった、と書かれています。全shardの実行時間の幅も、当初の10分〜80分から28分〜35分まで収まったとのことでした。あくまで一つの会社の自社CIでの実測値なので、自分たちの環境でそのまま同じ倍率になるとは限りません。ですが、「ファイル単位の均等分割は時間を見ていない」という構造上の問題自体は、Playwrightの仕組みそのものに起因するので、テストの構成次第でどのプロジェクトにも起こり得ます。
公式が用意している緩和策:fullyParallel
この偏りに対して、Playwright公式ドキュメントもファイルサイズの不均衡がshard間の負荷不均衡につながる可能性を指摘していて、対策としてfullyParallel: trueの利用を勧めています。fullyParallelを有効にすると、shardの分割単位がファイルではなく個々のtestになり、「ensuring each shard receives an even distribution of tests」、つまりtest単位で均等に配られるようになります。
// playwright.config.ts
export default defineConfig({
fullyParallel: true,
});
ここ、注意してほしいのですが、fullyParallelが均等にしてくれるのはtestの数です。重いtestと軽いtestが混在していれば、test単位で分けてもやっぱり時間の偏りは残ります。ファイル単位よりは粒度が細かくなる分マシにはなりますが、「時間で均す」機能ではない、という点は変わりません。過信しないほうがいいところです。
遅いshardの中身は–reporterとtraceで見る
じゃあ実際どうやって「どのshardが遅いのか、その中の何が重いのか」を見つけるか。まずはCIのログでshardごとの所要時間を並べて、突出しているものを特定しましょう。そのうえで、該当shardだけを手元やCI上で単独実行して、詳しい結果を見ます。
npx playwright test --shard=4/12 --reporter=list
--reporter=listにすると、テストごとの実行時間が1行ずつ流れるので、どのtestが重いのか目視で当たりを付けられます。さらに細かく見たいときは、traceを有効にして重いtestだけ開き直します。
npx playwright test --shard=4/12 --grep "重いと当たりを付けたtest名" --trace=on
traceを開くと、ナビゲーション待ちが長いのか、アサーションの自動待機で詰まっているのか、それとも単に操作が多いだけの正直な重さなのかが見えてきます。traceの読み方やタイムラインの区間の見方は、以前Speedboardとタイムラインで遅いテストの原因を探る記事にまとめているので、そちらも合わせて読んでみてください。
原因が「そのtestが本質的に重い」のであれば、shardの割り振りを工夫するよりも先に、testそのものを軽くする方が効きます。無駄な待機やページ遷移が減らせないか、先にそちらを疑うのが私のやり方です。
それでも重いテストが残るなら、CI設定自体を見直す
testを軽くしてもなお重いshardが残る場合、CIのワークフロー側でできることもあります。ワークフローYAMLの書き方やブラウザキャッシュの設定は、以前GitHub ActionsでのCI入門の記事で書いた内容と地続きです。shardの数を増やす前に、そもそものジョブの立ち上がりが遅くないかも確認しておくと無駄がありません。
--grepや--projectなど、workers以外の絞り込みオプションについてはCLIオプションの記事で整理しています。「重いtestだけを狙って実行する」場面でよく使うので、こちらも参考にしてみてください。
まとめ:増やす前に、まず偏りを疑う
workersは1マシン内の並列数、fullyParallelはファイル内testの並列可否、shardは複数マシンへの分散。この3つは別レイヤーで、どれを増やしても解決する問題が違います。そのうえで、shardのデフォルトはファイル単位の分割で、実行時間ではなくテストの数(fullyParallel有効ならtestの数)を揃えているだけです。CI全体の待ち時間は平均ではなく一番遅いshardで決まるので、まずはshardごとの実行時間を並べて偏りがないか見るところから始めてみてください。マシンを増やす前にやることは、案外ここにあります。

コメント