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

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

DeNA_Tech
PRO
September 21, 2022

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

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

DeNA_Tech
PRO

September 21, 2022
Tweet

More Decks by DeNA_Tech

Other Decks in Technology

Transcript

  1. 大きなGitリポジトリをJenkinsで扱うコツ 〜Copy on Writeを使ったディスク節約術〜 前田 薫 品質本部品質管理部SWET第二グループ 株式会社ディー・エヌ・エー © DeNA

    Co.,Ltd.
  2. 2 自己紹介 前田 薫 日本にLightning Talksを持ち込み、最初にしゃべった、 というのを自慢にしています。 30年以上T-Codeという漢字直接入力を使っています。 DeNA SWETグループ

    @mad-p @mad_p
  3. 3 目次 はじめに リポジトリ肥大化に対する一般的な対応方法 複数のクローンを持つ場合に有効な対応方法 事例紹介: データの性質を分析して対応方法を考える 〜macOSのcopy-on-write活用〜 〜macOSのcopy-on-write活用〜 1

    2 3 4 まとめ 5
  4. 4 1. はじめに

  5. 5 Gitリポジトリは大きくなる コミットを重ねることで • 歴史が長くなる • ファイル数が増加 1 = コミット

    = ファイル(ブロブ)
  6. 6 リポジトリが大きくなると問題が多い クローンすると • 過去のコミット履歴を手元にダウンロード • .git/ 以下に保存 • チェックアウトでワーキングツリーを作成

    リポジトリが大きくなると • ダウンロード時間が長くなる • .gitに必要なディスク容量が大きくなる 2
  7. 7 2. リポジトリ肥大化に対する 一般的な対応方法

  8. 8 リポジトリ肥大化に対する一般的な対応方法 DeNA Testing Blogの記事で解説した内容をかんたんにおさらい 大きなGitリポジトリをクローンするときの工夫を図解します https://swet.dena.com/entry/2021/07/12/120000 1

  9. 9 リポジトリ肥大化に対する一般的な対応方法 大きく2つの対応方法 • リポジトリが大きくならないように設計する ◦ LFSを活用 • クローン時・チェックアウト時に必要なデータだけを取得する ◦

    パーシャルクローン ◦ シャロークローン ◦ スパースチェックアウト 2
  10. 10 リポジトリが大きくならないように設計する LFSを活用する • 大きなファイルはGit LFSに保存 • バイナリファイルがおすすめ • リポジトリ作成時に設定

    するのがよい ◦ 歴史ができてからの移行は 不可能ではないが大変 • 設定方法 ◦ git lfs track "*.zip" ◦ .gitattributesをコミット 3
  11. 11 クローン時に必要なデータだけを取得する パーシャルクローン(ブロブレスクローン) • 作業に必要なブロブだけをダウンロード • 指定方法 ◦ git clone

    --filter=blob:none \ git@github.com:org/repo 4 = 必要になったら取得するぞという情報
  12. 12 クローン時に必要なデータだけを取得する シャロークローン • 最新の歴史だけ取得 • 指定方法 ◦ git clone

    --depth=1 \ git@github.com:org/repo.git 5 = 取得されない部分
  13. 13 チェックアウト時に必要なデータだけを取得する スパースチェックアウト • 一部のフォルダやファイルだけを チェックアウト • 設定方法 6 git

    clone --sparse --no-checkout \ git@github.com:org/repo cd repo git sparse-checkout add \ want_folder1 want_folder2 git checkout
  14. パーシャルクローンと スパースチェックアウトを両方使う • 歴史の長さ、ファイル数、両軸で 節約できるのでおすすめ パーシャルクローン、シャロークローン、 スパースチェックアウトは、 クローン後のフォルダに設定が保存される • ローカルにのみ適用

    14 パーシャルクローン + スパースチェックアウト 7
  15. 15 3. 複数のクローンを持つ場合に 有効な対応方法

  16. 16 複数クローンを持つ場合とは • 同一のリポジトリをひとつのマシン上の複数の フォルダにクローンする場合 • Jenkins agentの場合によく発生 ◦ ジョブ別にワークスペースを作成

    ◦ 次回のジョブ実行に備えて 残しておく ◦ 同じリポジトリを使う 複数のジョブが存在 1
  17. 17 複数のクローンを持つ場合に有効な対応方法 ローカルにダウンロードしたデータを複数のクローンで共有する • クローン済み.gitをリファレンスとして参照するクローン • LFSローカルストレージの共有 2

  18. 18 クローン済み.gitをリファレンスとして参照するクローン クローン済みの.gitを使ってクローン • 追加で必要なもののみ新規にダウンロード • Bのgit操作ではAとBの両方のデータを使う • git clone

    \ --reference-if-able=/path/to/A \ git@github.com:org/repo ./B • 後からリファレンスを加える ことも可能(付録参照) 3
  19. 19 組織的なリファレンスクローンの活用事例 よく使うリポジトリをミラーする • 定期的にgit fetch ジョブ内でクローンする際 リファレンスとしてミラーを指定 • サブモジュールでも利用

    • ジョブによって使う リポジトリは異なる 運用が難しく、おすすめはしません 4
  20. 20 LFSローカルストレージの共有 LFSからダウンロードしたオブジェクトは ローカルストレージに保存される • ローカルストレージを共有すると ひとつにまとめることが可能 • 設定方法 •

    git config --local \ lfs.storage \ /path/to/shared/storage 5
  21. 21 4. 事例紹介: データの性質を分析して対応方法を考える 〜macOSのcopy-on-write活用〜

  22. 22 事例: あるモバイルゲームタイトルのJenkins agent あるモバイルゲームタイトル • リリースから数年 • 毎月のイベントごとにアプリ更新とキャラクターの追加 ◦

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

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

    特にキャラクターのモデル/モーションデータが大きい ◦ スパースチェックアウトで減らせない部分が多い • サウンドデータのリポジトリも大きいが、ジョブは少ない • マージ時のコンフリクト検出のためにチェックアウトしているものもあり ◦ read-onlyの使い方とは限らない → モデル/モーションデータを対象として節約方法を検討 3
  25. 同じ内容のファイルツリーが 多数保持されている • 1組が4万個25GBくらい • これが20組くらい • macだけでいいから なんとかならないか 25

    容量を使っているのはどんなデータか 4
  26. 26 同一内容のファイルを共有して節約できないか 同一内容をまとめてひとつにしたい • macOSのAPFSには 同一内容のファイルのディスク 領域だけを共有する手段がある • copy-on-write動作をするので read-onlyと限らない場合でも有効

    • この図のようになれば理想 5
  27. 27 データの性質: ファイル追加はあるがファイル更新は少ない モデル/モーションデータは • 開発が進むにつれて新規キャラ/モーションが追加される • 既存モデル/モーションの改修は少ない(更新頻度が少ない) • 月次リリースに対応してブランチ管理されている

    • ブランチを切りかえても内容が変わらないファイルが多い ジョブでチェックアウトしているデータは • ブランチをパラメータとするジョブが多く、ブランチ切りかえで上書きされる • どのファイルが上書きされるかを予見することは難しい 6
  28. 28 UNIXに伝統的にあるファイルのリンク 同一内容のファイルをまとめてひとつにするための、古典的なテクニック • ハードリンク • シンボリックリンク いずれも、read-onlyのデータで有効に使える手法 利用にあたっては、以下の点に注意する必要あり •

    書きかえたときに、共有されている他のファイルに変更が反映されるか • Gitにコミットするとどうなるか 7
  29. 29 UNIXのファイルとディスクブロックの対応 ファイルシステム内では、inode(アイノード)という構造で管理 8

  30. 30 ハードリンク ひとつのinodeに複数のファイル名をつける • ln fileA fileB • 書きかえは両方の名前に 反映される

    • Gitには別のファイルとして コミットされる (チェックアウトすると別の実体に) 9
  31. 31 シンボリックリンク ファイル名をポインタとして使って、別のファイルの中身を参照できる • ln -s fileA fileB • 書きかえは両方の名前に

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

    fileB • 書きかえは片方にのみ 反映される • Gitには別ファイルとして コミットされる ◦ チェックアウトすると 別の実体に 11
  33. 33 macOS APFSのcopy-on-write 部分的に書きかえると、 書き込みが発生した 部分のみコピーして 書きかえる (copy-on-write動作) 今回の用途に適している 12

  34. 34 copy-on-writeを利用してファイルをまとめる copy-on-writeでディスクの節約ができそうか? • 同一内容のファイルはgit checkoutで上書きされるか? ◦ → 一度まとめれば、ブランチを切りかえてもその状態が維持できるのか? copy-on-writeを使いやすい対象とは?

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

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

    同一かつ未共有なら、cp -cでディスク領域共有したファイルと置きかえる • 統計情報を出力。dry-runモードも用意 2つのファイル間で、すでにディスク領域共有済かどうかを調べるツール • https://github.com/dyorgio/apfs-clone-checker (MITライセンス) 15
  37. 37 試験運用結果 1か月程度運用してみた結果 • 40%程度のディスク領域を共有できた ◦ 96万ファイル中39万ファイル ◦ 486GB中201GB ◦

    (dry-runモードの出力) • 見積りの半分程度しか効果が出ていないのはなぜだろう? 16
  38. 38 トラブルシューティング (1/3) 効果が出ない理由として考えられるもの(予想) • ジョブスクリプトで、予備的に全クリアしている? • 別ブランチのチェックアウトで大量に書きかえられている? 1分に1回、ls -lUTrを使って、どのファイルが書きかえられたか見張る

    • 書きかえた瞬間をつかまえ、ディレクトリ名を特定 • Jenkinsログのタイムスタンプを手がかりに、 その瞬間に実行されていた処理を特定 17
  39. 39 トラブルシューティング (2/3) わかったことと対策 • 「予備的な全クリア」はなかった • 「ブランチを削除するツール」が悪さをしていた ◦ 削除したいブランチをチェックアウトしているとエラーになる。

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

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

    • あきらめたジョブも あるので分母も減少 20 導入前 試験運用 改善後 macノード4台の空き容量の変化 空き容量 200GB程度 500GB程度
  42. 42 これが 21

  43. 43 こうなった! 22

  44. 44 事例紹介まとめ 容量の大きいデータをmacOSのcopy-on-write機能でディスク領域共有 • 容量を使っているデータを特定 • データ性質を調査: 更新が少ない、ブランチ切りかえで部分的に入れかえ • copy-on-write利用可能性の調査、効果見積り

    • 実装 • 試験運用 • 効果が出ていない原因をトラブルシューティング、対策を実施 • 本運用 • 効果を確認 23
  45. 45 5. まとめ

  46. 46 まとめ • Gitリポジトリが大きくなるとどんな問題があるか解説 • 大きなリポジトリを扱う場合の工夫を紹介 ◦ リポジトリ作成時、クローン時、チェックアウト時 ◦ 同一リポジトリを複数クローンしている場合

    • macOSのcopy-on-write機能を利用してディスク容量を節約した事例 調査・予備実験、実装、トラブルシューティング、結果を紹介 Gitリポジトリとデータの特性を理解して、大きくなりがちなリポジトリと うまくつき合っていきましょう
  47. © DeNA Co.,Ltd. 47

  48. 48 付録: 想定質問と答え、補足など

  49. 49 copy-on-writeにデメリット・リスクはある? duなどのファイル容量調査ツールでは、別のinodeは別のディスク領域を使っていると計算される • 「duで測定したディレクトリごとのディスク容量の合計」と 「dfで見たディスク容量」が一致しなくなる • copy-on-write活用によってどれくらいの節約になったか、効果測定が難しい ◦ 2つのファイルが共有しているかは、p.36で紹介した

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

    inode番号などを記録 • git checkoutでは、ワーキングツリー内のファイルとキャッシュが違っている 場合、sha1を計算し直してくれるようだ ◦ 一致すれば上書きしない(望ましい動作) • git reset --hardではinode番号が違うとsha1の再計算をせずに上書きする ◦ cp -cで作成したものは内容は同一でもinode番号は変わっている ◦ このため、有意をいわさず上書きされてしまうようだ(望まれない動作) 2
  51. 51 パーシャルクローンを活用しないのはなぜ? 2つの理由があります • Gitにパーシャルクローンが実装される以前に、リファレンス活用の仕組みを 整えてしまった ◦ リファレンスを使っていれば、パーシャルクローンは必要ない • パーシャルクローンを活用するには、Jenkinsの各ジョブに設定を追加する

    必要があり、作成済ジョブを見直して設定追加していくのは手間がかかる 3
  52. 52 スパースチェックアウトは活用しないのか? 作成済のジョブに対してスパースチェックアウトの設定を順次行っています • ジョブで必要なファイル集合がすぐわかるジョブから順にとりかかっている • 分析が大変そうなものはどうしても後回しになってしまう 4

  53. 53 クローンしたディレクトリに後からリファレンスをつける方法 通常どおりにクローンしたフォルダに対して、後からリファレンスを加えることも できます • .git/objects/info/alternates というファイルを作成し、リファレンスの .git/objects のフルパスを書き込む ◦

    サブモジュールの場合は .git/modules/<submodule_path>/objects/info/alternates • alternates 作成後、以下のコマンドを実行すると、 リファレンスに存在するオブジェクト(重複分)を削除できる ◦ git repack -a -d -l 5
  54. 54 リファレンスクローンのミラー運用が難しいとは? フォルダBで必要なオブジェクトが A側から消えるとB側でエラーになる • A側でgcなどで消えないよう gc.pruneExpire=neverを設定 • たまにAがこわれる場合もある ◦

    Aを作り直し、Aを見ている B(複数)を作り直し 6 fatal: bad object HEAD fatal: bad object <commit_hash> fatal: unable to read <blob_hash>
  55. 55 git lfs dedup copy-on-writeをサポートしている環境では、git lfs dedupが使えます • ローカルストレージと ワーキングツリー内の

    ファイルが同一の場合、 cp -cしたもので置きかえてくれる ◦ ★印の2つでディスク領域を共有 • 各ワーキングツリー内で git lfs dedup を実行 • 手元のマシンでは20GB程度の節約 7
  56. 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
  57. 57 macOS以外では使えないのか? Linuxのbtrfsには同様の機能があるようです。 https://btrfs.wiki.kernel.org/index.php/Deduplication ZFSにもあるそうですが、ライセンスの問題があってLinuxで使うのは難しそうで す。 WindowsのNTFSはボリュームの単位でしか「以前のバージョン」を保存できないた め、個別のファイル単位でのディスクブロック共有はできなさそうです。 9

  58. © DeNA Co.,Ltd. 58