Upgrade to Pro — share decks privately, control downloads, hide ads and more …

大きなGitリポジトリをJenkinsで扱うコツ 〜Copy on Writeを使ったディスク節約術〜

DeNA_Tech
September 21, 2022

大きなGitリポジトリをJenkinsで扱うコツ 〜Copy on Writeを使ったディスク節約術〜

CEDEC2022で 品質本部品質管理部SWET第二グループ 前田 薫 が発表した資料です。
大きなGitリポジトリをクローンするときの通信量・ディスク容量を節約するための、リポジトリの設定やgitコマンドのオプションの工夫を紹介します。また、macOSにあるcopy-on-writeの機能を利用して、同一内容のコピーをまとめることでディスクを400GB以上節約できた事例も紹介します。考えかたや注意点などもあわせて解説します。

DeNA_Tech

September 21, 2022
Tweet

More Decks by DeNA_Tech

Other Decks in Technology

Transcript

  1. 10 リポジトリが大きくならないように設計する LFSを活用する • 大きなファイルはGit LFSに保存 • バイナリファイルがおすすめ • リポジトリ作成時に設定

    するのがよい ◦ 歴史ができてからの移行は 不可能ではないが大変 • 設定方法 ◦ git lfs track "*.zip" ◦ .gitattributesをコミット 3
  2. 22 事例: あるモバイルゲームタイトルのJenkins agent あるモバイルゲームタイトル • リリースから数年 • 毎月のイベントごとにアプリ更新とキャラクターの追加 ◦

    月次リリースに対応してブランチ管理 • 巨大リポジトリトップ4(.gitサイズ) ◦ アセット(22G)、アセットバンドル(LFSあり16G)、 アセットソース(15G)、アプリ(3.4G) 運用しているJenkins agentマシン群(15台規模) • mac, win, linux混成agentクラスタ • macノードの慢性的なディスク容量不足 1
  3. 23 ここまでで実現できた節約 ここまでの方法で節約できたこと • リファレンスを活用したクローン ◦ .gitの容量/ダウンロード時間の節約 ◦ 容量×回数で計820GB →

    60GB ◦ 元がシャローもあるので節約は実質半分くらい • LFSローカルストレージの共有化 ◦ .git/lfsの容量/ダウンロード時間の節約 ◦ 90GB → 20GB 2
  4. 24 次の問題: チェックアウトしたファイルの容量が大きい ジョブ内で参照するファイル • Jenkinsでは次回実行に備えディレクトリを残しておく どんなデータが容量を使っているか調査してわかったこと • アセットリポジトリを扱うジョブが20個くらいある ◦

    特にキャラクターのモデル/モーションデータが大きい ◦ スパースチェックアウトで減らせない部分が多い • サウンドデータのリポジトリも大きいが、ジョブは少ない • マージ時のコンフリクト検出のためにチェックアウトしているものもあり ◦ read-onlyの使い方とは限らない → モデル/モーションデータを対象として節約方法を検討 3
  5. 27 データの性質: ファイル追加はあるがファイル更新は少ない モデル/モーションデータは • 開発が進むにつれて新規キャラ/モーションが追加される • 既存モデル/モーションの改修は少ない(更新頻度が少ない) • 月次リリースに対応してブランチ管理されている

    • ブランチを切りかえても内容が変わらないファイルが多い ジョブでチェックアウトしているデータは • ブランチをパラメータとするジョブが多く、ブランチ切りかえで上書きされる • どのファイルが上書きされるかを予見することは難しい 6
  6. 30 ハードリンク ひとつのinodeに複数のファイル名をつける • ln fileA fileB • 書きかえは両方の名前に 反映される

    • Gitには別のファイルとして コミットされる (チェックアウトすると別の実体に) 9
  7. 32 macOS APFSのcopy-on-write ファイル名、inodeは別実体、inodeから指すディスク領域のみを共有 • cpコマンドに-cオプションをつけるとこの方法でコピーされる ◦ cp -c fileA

    fileB • 書きかえは片方にのみ 反映される • Gitには別ファイルとして コミットされる ◦ チェックアウトすると 別の実体に 11
  8. 34 copy-on-writeを利用してファイルをまとめる copy-on-writeでディスクの節約ができそうか? • 同一内容のファイルはgit checkoutで上書きされるか? ◦ → 一度まとめれば、ブランチを切りかえてもその状態が維持できるのか? copy-on-writeを使いやすい対象とは?

    • 同一ファイルかどうか判定しやすい ◦ ディレクトリツリーを対応させて、同じファイル名のものを候補 ◦ リファレンス用にミラーしているディレクトリと比較できる 13
  9. 35 予備実験、効果見積り 予備実験 • ブランチ間でチェックアウトしてみて、更新のないファイルが上書きされるか調査 ◦ ls -liでinode番号とタイムスタンプを表示して調べる ◦ 上書きされないことがわかった

    効果見積り • 同一内容のファイルがどの程度あるか調査 • ツリーをつきあわせて、cmpコマンドでファイル内容を比較 • 4万ファイル×25組=100万ファイルの97%程度、523GB中516GBを節約できそう • この調査に10時間かかった。一度に全部やると24時間以上必要になり難しそう copy-on-writeのリスクを検討 (付録参照) 14
  10. 36 試験運用 日次ジョブを作成してディスク領域を共有 • 20個のツリーのうち、1日に2つずつ置きかえる ◦ 1日に2時間程度の処理時間がかかる • ミラーのツリーを「お手本」として、ファイル名が同じものの内容を比較 •

    同一かつ未共有なら、cp -cでディスク領域共有したファイルと置きかえる • 統計情報を出力。dry-runモードも用意 2つのファイル間で、すでにディスク領域共有済かどうかを調べるツール • https://github.com/dyorgio/apfs-clone-checker (MITライセンス) 15
  11. 39 トラブルシューティング (2/3) わかったことと対策 • 「予備的な全クリア」はなかった • 「ブランチを削除するツール」が悪さをしていた ◦ 削除したいブランチをチェックアウトしているとエラーになる。

    回避策として「とりあえずmasterをチェックアウト」していた。 ▪ masterが古く、大量のファイルを更新することになっていた ◦ git checkout --detach HEADを使うことで解決 • アプリビルド用ジョブで、同梱アセット以外を消していた ◦ これらのジョブを処理対象から外すことで解決 18
  12. 40 トラブルシューティング (3/3) わかったことと対策 • cp -cでファイルを置きかえた直後にget reset --hardすると、 ファイル内容が同一でも上書きされてしまうことがわかった

    ◦ 一度git statusしておけば、git reset --hardでも上書きされない ◦ ファイル置きかえが終わった後、git statusするように変更 その他の工夫 • ツリーが処理済みであるかを素早く予想する ◦ 初期から存在するキャラのファイルだけ共有化済みか調べてみる ◦ 未処理のツリーを優先して処理できる 19
  13. 41 最終結果 • 88%のディスク領域を 共有できた ◦ 86万ファイル中 77万ファイル ◦ 464GB中412GB

    • あきらめたジョブも あるので分母も減少 20 導入前 試験運用 改善後 macノード4台の空き容量の変化 空き容量 200GB程度 500GB程度
  14. 46 まとめ • Gitリポジトリが大きくなるとどんな問題があるか解説 • 大きなリポジトリを扱う場合の工夫を紹介 ◦ リポジトリ作成時、クローン時、チェックアウト時 ◦ 同一リポジトリを複数クローンしている場合

    • macOSのcopy-on-write機能を利用してディスク容量を節約した事例 調査・予備実験、実装、トラブルシューティング、結果を紹介 Gitリポジトリとデータの特性を理解して、大きくなりがちなリポジトリと うまくつき合っていきましょう
  15. 49 copy-on-writeにデメリット・リスクはある? duなどのファイル容量調査ツールでは、別のinodeは別のディスク領域を使っていると計算される • 「duで測定したディレクトリごとのディスク容量の合計」と 「dfで見たディスク容量」が一致しなくなる • copy-on-write活用によってどれくらいの節約になったか、効果測定が難しい ◦ 2つのファイルが共有しているかは、p.36で紹介した

    apfs-clone-checker で調べられる 領域共有が解除されるような操作によって急にディスク領域が必要になる • ディスクの残り容量が十分にあると思っていたら、急にディスクフルになってしまう 可能性がなくはない • バックアップは正常に取れるが、リストアしようとしたらディスクに入り切らないという 可能性もある 1
  16. 50 git reset --hard で上書きされてしまう理由 • git statusは内部的にキャッシュを作成 • ワーキングツリー内のトラックしている各ファイルについて、sha1、

    inode番号などを記録 • git checkoutでは、ワーキングツリー内のファイルとキャッシュが違っている 場合、sha1を計算し直してくれるようだ ◦ 一致すれば上書きしない(望ましい動作) • git reset --hardではinode番号が違うとsha1の再計算をせずに上書きする ◦ cp -cで作成したものは内容は同一でもinode番号は変わっている ◦ このため、有意をいわさず上書きされてしまうようだ(望まれない動作) 2
  17. 53 クローンしたディレクトリに後からリファレンスをつける方法 通常どおりにクローンしたフォルダに対して、後からリファレンスを加えることも できます • .git/objects/info/alternates というファイルを作成し、リファレンスの .git/objects のフルパスを書き込む ◦

    サブモジュールの場合は .git/modules/<submodule_path>/objects/info/alternates • alternates 作成後、以下のコマンドを実行すると、 リファレンスに存在するオブジェクト(重複分)を削除できる ◦ git repack -a -d -l 5
  18. 55 git lfs dedup copy-on-writeをサポートしている環境では、git lfs dedupが使えます • ローカルストレージと ワーキングツリー内の

    ファイルが同一の場合、 cp -cしたもので置きかえてくれる ◦ ★印の2つでディスク領域を共有 • 各ワーキングツリー内で git lfs dedup を実行 • 手元のマシンでは20GB程度の節約 7
  19. 56 クローン済みディレクトリ(ミラー)をcp -caしたらよいのでは? 特にローカルマシン上ではよい方法です! Jenkinsの場合は以下のような点に注意すれば運用できると思います • すでにクローン済みかどうか判定して、初回だけ処理を変える ◦ git cloneする代わりにcp

    -caでミラーから複製する(ディスク共有済みになる) ◦ その後にgit fetchして最新化するとよい • cp -caした後のgit fetchで追加ダウンロードしたオブジェクトは共有されない ◦ cf. リファレンスの場合はミラー側でfetchした分も共有される • JenkinsのSCMを利用する場合にはcp -caを利用できないので注意 ◦ Jenkinsfile取得のためのクローン、Jenkinsfile内に記述したcheckoutステップなど ◦ cf. リファレンスの設定は可能 8