mtx2s’s blog

エンジニアリングをエンジニアリングする。

バッチサイズ削減はソフトウェアデリバリに何をもたらすか

「処理能力向上」と「バッチサイズ削減」。ソフトウェアデリバリのリードタイムを短縮したい組織の多くは前者、すなわちチームの処理能力向上に注力する。それが、ソフトウェアエンジニアリングに携わる我々が長年常識としてきたやり方だ。バッチサイズ削減について考えることなどなかった。むしろ、バッチサイズを大きくした方が効率的とさえ考えられてきた。

チームの処理能力向上はもちろん重大な関心事であるが、その実現難易度は高い。時間やコストを要する。それに比べ、バッチサイズ削減は、関係者の合意さえ得られればすぐにでも実現できる。そうであるにも関わらず、バッチサイズ削減という選択肢が軽視されるのは、バッチサイズが及ぼす影響の可視性が著しく低いためではないか。

ドナルド・ライナーセン(Donald G. Reinertsen)は、バッチサイズ削減が、リードタイム短縮やフローにおける変動低減といった効果をもたらすことを挙げている

プロセス / フロー / フローユニット

バッチサイズへの理解を深めていく前に、「プロセス」とは何であるかを定義しなければならない。

ソフトウェアデリバリのプロセスは凡そ、実装/コードレビュー/ビルド/テスト/デプロイといった、順序付けられたいくつかのステージが連なるパイプラインとして構成されている。プロダクトバックログから取り出されたアイテムは、それを構成するいくつかのアイテムに細分化された上で、それぞれがこのパイプラインのステージを順に進んでいく。このような、アイテム移動の様を「フロー(flow)」と呼び、移動していく個々のアイテムを「フローユニット(flow unit)」と呼ぶ。

f:id:mtx2s:20220307064842p:plain

「プロセス」の概念で重要なのは、どこからどこまでをプロセスとするか、観察者がその範囲(システム境界)を任意に定義できる点にある。デリバリパイプライン全体をひとつのプロセスとすることはもちろん、ステージひとつをプロセスとして扱うこともできるし、連続する複数のステージをひと括りでプロセスとして観察することもできる。

プロセスの範囲によっては、フローユニットの粒度にも選択肢がある。デリバリパイプライン全体をプロセスとして観察するなら、トピックブランチに切り出して実装するレベルのアイテムをフローユニットとしても良いし、プロダクトバックログアイテム(ストーリーなど)をフローユニットとしても良い。

WIP数 ≠ バッチサイズ

プロセスにはイベントがある。プロセスへのフローユニットの「到着」と、プロセス内で処理を終えたフローユニットの「出発」だ。到着から出発までの期間にあるフローユニットや、そのフローユニットに対する作業がいわゆる「WIP(Work In Process, Work In Progress, 仕掛り作業)」と呼ばれるものだ。「DIP, Design In Process」と呼ばれることもある。

f:id:mtx2s:20220308091313p:plain

コードレビュープロセスであれば、プルリクとして投げられたトピックブランチ上の変更(changes)が、フローユニットでありWIPということになる(WIPは仕掛りの「作業(work)」なのだから、正しくは、変更に対する「コードレビュー」作業をWIPと言った方が良いのかもしれない)。

誤解しそうになるが、WIPの数はバッチサイズじゃない。WIP数は単に、ある時点におけるプロセス内のフローユニットの数を示しているに過ぎない。「バッチ」とは、フローユニットのコレクションのことではあるが、むしろ、プロセスに対してひとまとまりで到着したり、出発する単位だと捉えた方が理解しやすい。そのコレクションに含まれるフローユニットの数こそが、本稿の主役である「バッチサイズ」だ。

下図は、バッチによるプロセスからの出発の様子をCFD(Cumulative Flow Diagram, 累積フローダイアグラム)で描いたものだ。

f:id:mtx2s:20220309222556p:plain

CFDは、時系列でのフローユニットの増加を累積で描いた面グラフで、イシュー管理ツールに付属するレポート機能などで馴染みがあるのではないだろうか。ここでは到着したフローユニットの数をグリーン、出発したフローユニットの数をブルーで表現している。

グリーンとブルーに挟まれた垂直方向の長さは、その時点でのWIP数を示している。それが時間の経過とともに徐々に大きくなっていき、バッチが出発したタイミングで一気に小さくなる。そしてまた徐々に大きくなっていく様子が読み取れる。

WIP数の増加によるフロー効率とリードタイムの悪化

プロセス内のフローの効率性を考える上で、プロセスリードタイムは重要な指標だ(以降は単に「リードタイム」と呼ぶ)。リードタイムとは、ひとつあたりのフローユニットがプロセスに到着してから次へ出発するまでの時間で、WIP時間とも言える。

f:id:mtx2s:20220308091340p:plain

ここに、処理能力(processing rate, service rate)が1時間あたり平均1/3個のプロセスがある。プロセスが空の状態であれば、フローユニットが1個だけ到着すると、出発するまでのリードタイムが3時間であるこということだ。この期間、WIP数が1個であることに注意したい。

f:id:mtx2s:20220312080407p:plain

同じプロセスにおいて平均3個のWIPがあるなら、平均リードタイムはどうなるか。

f:id:mtx2s:20220312080432p:plain

9時間となり、3倍にのびる。リトルの法則からも明らなように、平均リードタイムは平均WIP数に比例するからだ。

平均リードタイム = 平均WIP数 / 平均処理能力

f:id:mtx2s:20220308082745p:plain

WIP数が3個の時のリードタイムである9時間のうち、付加価値時間(value-added time, service time)はその約33%の3時間だけだ。残りの約67%にあたる6時間は、単なる待ち時間(wait time, queue time)にあてられている。つまり、リードタイムに対する付加価値時間の割合が高いほど、フローの効率性が高いと言うことだ。このような、リードタイムに占める付加価値時間の割合を「フロー効率(flow efficiency)」と言う。

フロー効率(%) = 付加価値時間 / リードタイム

リードタイムとフロー効率に影響を与えるWIP数の変化は、いったい何によって生じているのだろうか。その代表的な要因が、バッチサイズなのだ。

余談ではあるが、プロセスリードタイムは「サイクルタイム(cycle time)」と呼ばれることが多いようだ。しかし、文献によっては、「スループットタイム(throughput time)」と呼ばれ、サイクルタイムが別の意味で使われることもある。このため、本稿では「プロセスリードタイム」あるいは単に「リードタイム」と呼ぶことにした。

バッチ出発によるWIP数の増加

プロセスを出発するフローユニットのバッチ化がリードタイムに及ぼす影響をみるために、バッチサイズの異なる2つのケースを考えてみる。それをCFDとして下図に描いた。書籍『The Principles of Product Development Flow: Second Generation Lean Product Development』の例を参考にしている。

f:id:mtx2s:20220310000919p:plain

先ほども述べた通り、グリーンとブルーに挟まれた垂直方向の長さは、時系列でのWIP数の変動を表しているのであるから、その合計であるグリーン領域の面積が大きいほど、平均WIP数が大きい。その面積はバッチサイズに比例するので、平均WIP数は、左のCFDの方が大きい。

リトルの法則にある通り、平均リードタイムは平均WIP数に比例する。これらのことから、バッチサイズが大きいほどリードタイムが大きくなってしまうことがわかる。

バッチ到着による高稼働率の常態化とWIP数の増加

大きなバッチとしてフローユニットのかたまりがプロセスに押し寄せれば、WIP数が一気に跳ね上がる。リードタイムが悪化するのは想像に容易いが、プロセスへの影響はそれだけではすまない。

到着のバッチサイズが大きくなる理由はいくつも考えられる。その主要なもののひとつに、稼働率(utilization)を上げようとする意図はないだろうか。それが、高い稼働率を常態化させる。しかし、高稼働率の常態化は、リードタイムの指数関数的な悪化を招いてしまう。

まず、「稼働率」とは、時間あたりに到着するフローユニットの数である「到着率」を、時間あたりに処理できるフローユニットの数である「処理能力」で割った値を言う。

稼働率(%) = 到着率 / 処理能力

例えば、フローユニットがプロセスに到着する間隔が5時間に1個であれば、到着率は1時間あたり0.20個となる。処理能力は、フロー効率が100%である場合のリードタイムが4時間であるとすると、1時間あたり0.25個となる。この時の稼働率は、80%ということになる。

稼働率が100%未満である時の、稼働率とWIP数との関係は、次のグラフのようになる。稼働率が100%に近づくほど、WIP数が大きく跳ね上がる様子がわかる。

f:id:mtx2s:20220310223759p:plain

リードタイムはWIP数に比例するため、高稼働率にあるプロセスはリードタイムが長い。それは、フロー効率が低い状態でもある。このことから、稼働率とフロー効率の両方を同時に上げることが困難であることが理解できる。このような、フロー効率と稼働率(リソース効率)の関係性を、二クラス・モーディグ(Niklas Modig)とパール・オールストローム(Par Ahlstrom)は、「効率性のパラドックス(efficiency paradox)」と呼んだ。

モーディグらも言うように、効率性のパラドックスは、もう一つの要素である変動性によってより深みにはまる。

変動性と稼働率がリードタイムに与える影響

稼働率とリードタイムの関係は、プロセスが抱える「変動性(variability)」に影響を受ける。「ばらつき」と言った方がイメージしやすいだろうか。その要因は、「リソース」「フローユニット」「外部要因」の3つに分けることができる。

リソースであれば、プロセスを稼働させているチームメンバーの体調やモチベーションによる影響もあり得るし、ビルド環境が壊れることもあるかもしれない。フローユニットの変動として真っ先に思い浮かぶのは、個々のストーリーや機能の開発規模のばらつきだろう。外部要因であれば、割り込みも含め、フローユニットの到着のばらつきが考えられる。

このような変動がまったく無いプロセスはあり得ないが、プロセスによって高変動か低変動かの差はある。次の図は、高変動にあるプロセスと、低変動にあるプロセスに関する稼働率とリードタイムの関係を描いたグラフだ。

f:id:mtx2s:20220310223832p:plain

高変動にあるプロセスの方が、低変動にあるプロセスより、稼働率がリードタイムに与える影響が大きくなることがわかる。

バッチ処理によるマルチタスクとオーバーヘッド

フローユニット到着のバッチ化は、プロセス内での処理をマルチタスキングに導く。今や誰もが知るように、タスク切り替えによるスイッチングコストは、リードタイムに影響する。

ジェラルド・ワインバーグ(Gerald M. Weinberg)によると、タスクの並列数が2になると、スイッチングコストによって稼働時間の20%をロスし、並列数が3だと40%をロスするという。これも変動と言えるだろう。

f:id:mtx2s:20220311064831p:plain

処理能力向上とバッチサイズ削減

以上のように、リードタイムは、付加価値時間だけで占められた時間ではない。待ち時間やマルチタスクでのスイッチングコストといった、"非"付加価値時間が多分に含まれている。バッチサイズ削減を通したWIP数のコントロールは、非付加価値時間を削減し、フロー効率を高めようとするアプローチなのだ。

では処理能力向上はと言うと、価値をより短時間で付加する能力を得ようとすることであり、付加価値時間の圧縮を目指すアプローチだと言える。本稿冒頭に書いた「処理能力向上だけでなく、バッチサイズ削減にも目を向けなくて良いのか」という問いは、言い換えれば、「付加価値時間圧縮だけでなく、非付加価値時間削減に目を向けなくて良いのか」という問いでもある。

デヴィッド・アンダーソン(David J. Anderson)によれば、ソフトウェアデリバリのパイプライン全体のフロー効率は概ね1%から25%の範囲に入るようだ。これはつまり、付加価値時間の圧縮効果はリードタイム全体の1%から25%程度であり、非付加価値時間の削減効果は75%から99%ものポテンシャルを秘めているということでもある。バッチサイズ削減に取り組まない手はないだろう。

バッチサイズ削減を突き詰めようとすると、トピックブランチを統合ブランチにマージして以降のフローが自動化され、かつ疎結合であることが鍵となる。ここのコストが高いと、バッチサイズを大きくしようとする力が働く。目指す究極は、継続的デプロイ(continuous deployment)による一個流しだ。そのためにはニコール・フォースグレン(Nicole Forsgren)らが言うように、「テスト、デプロイの自動化」とあわせて「デプロイとテストの容易性」に注力すべきであることは言うまでもない。