サイト別のアクセス情報をGitから取得する処理で、同じリポジトリへの取得が同時に走り、shallow.lock の衝突が起きました。サイトごとに処理を分けていても、取得先のリポジトリが共通なら、並列実行がそのまま安全とは限りません。今回は、同じリポジトリへの取得を直列にする修正を加えました。
サイト別の処理でも、取得先は共有していた
アクセス情報はサイトごとに扱います。そのため、サイト単位で取得処理を分けることには意味があります。一方で、取得元のGitリポジトリが同じなら、それぞれの処理は完全に独立しているわけではありません。
今回の衝突は、同一リポジトリへの取得が並行して行われたことで発生しました。Gitは処理中にロック用のファイルを使うことがあり、同じリポジトリに対する操作が重なると、ロックの取得でぶつかる場合があります。今回確認されたのは、shallow.lock の衝突です。
ここで注意したいのは、処理の対象をサイトごとに分けたことと、処理が触れる資源まで別々であることは同じではない、という点です。入力や目的が異なっても、共通のリポジトリを使っているなら、並列実行の影響を受ける可能性があります。
並列にする単位と、順番を守る単位を分ける
並列処理は、互いに独立した作業を同時に進められるため、処理時間を短くできる可能性があります。ただし、複数の処理が同じ資源を同時に操作する場合は、結果の競合やロックの衝突につながることがあります。
今回の修正では、サイト別の処理そのものを一律に止めるのではなく、同じリポジトリへの取得を直列にしました。つまり、サイト単位で扱うことと、リポジトリへのアクセス順を制御することを分けて考えています。共有する対象が同じ処理だけを順番に進めるのが、今回の変更の要点です。
この考え方は、並列化を避けるということではありません。並列にしてよい範囲と、共有資源のために順番を守る範囲を区別します。今回の材料から確認できる対策は、同一リポジトリへの取得を直列化したことまでです。具体的な実装方法や、処理時間への影響については確認できていません。
ロックの衝突を、処理単位の問題として見る
ロックファイルが見つかったとき、ロックだけを取り除けばよいとは限りません。ロックは、別の操作が同じ対象を扱っていることを示している可能性があるためです。原因が並行アクセスなら、ロックを消すだけでは、次の実行でも同じ競合が起きるおそれがあります。
そのため、まず衝突がどの資源で起きたかを見て、複数の処理が同じリポジトリにアクセスしている点を問題として扱いました。そのうえで、同一リポジトリへの取得を直列にする方針に変更しています。今回の対応は、ロックファイルへの個別の対処ではなく、競合を起こす処理の重なり方を変えるものです。
自動コミットにも安全条件を置く
この修正は、実行環境が安全条件を満たしたことを確認したうえで、自動コミットされました。変更を自動で記録する場合も、条件を確認してから通す設計にしています。
ただし、安全条件を満たしてコミットされたことと、変更後の取得処理が実環境で問題なく動いたことは別の確認事項です。今回の材料から確認できるのは、修正内容と、自動コミット前に安全条件を満たしたことまでです。衝突が再発しないことや、運用上の効果が検証済みだとは扱いません。
今回の学びは、並列化の可否をサイトなどの論理的な単位だけで決めないことです。同じリポジトリのように共有する資源があるなら、そこへの操作は順番を制御する必要があります。並列にする範囲と直列にする範囲を、資源の共有関係から決めることが、競合を防ぐ手がかりになります。