19.3K Views
April 22, 22
スライド概要
2022/01/27 kaonavi Tech Talk #1で登壇したときの資料です。
https://kaonavi.connpass.com/event/235914/
SIer2社を経て株式会社サイバーエージェントに入社。ゲームやコミュニティサービスの開発に携わる。その後、株式会社トランスリミットにて海外向けのアプリゲーム開発にCTOとして従事。2020年2月にカオナビに入社し、様々なプロジェクトの開発を支援。同年9月にCTOに就任。
kaonavi Tech Talk #1 問題解決に必要な能力 〜CodeBuildとOPCacheに振り回された話〜 2022/01/27 松下雅和 / @matsukaz
松下 雅和 漫画, ゲーム, カメラ, 自転車 @matsukaz 株式会社カオナビ CTO AWS, Node.js, TypeScript, Ruby, Python, PHP, Go, Scrum Webエンジニア 株式会社 サイバーエージェント SE アーキテクト 都築電気 株式会社 株式会社 オープンストリーム 2001.04 2005.08 2011.01 CTO CTO 株式会社 トランスリミット 株式会社 カオナビ 2014.10 2020.02
アジェンダ ● 前提知識 ○ OPCache ○ Apache MPM Prefork ○ ビルドパイプラインのBefore/After ● とある事象発生 ● 調査&解決までの流れ ● 問題解決に必要な能力
前提知識
OPCache ● コンパイル済みバイトコード(OPCode)を共有メモリに保存し、実行のた びにスクリプトの読み込み・パースの手間を省く仕組み OPCode OPCode Execute (Zend VM) Shared Memory OPCode Save OPCode Optimizer OPCode Compiler Check OPCode Cache Parser キャッシュなし Lexer PHP Script
OPCache ● コンパイル済みバイトコード(OPCode)を共有メモリに保存し、実行のた びにスクリプトの読み込み・パースの手間を省く仕組み PHP Script Read OPCode キャッシュあり Shared Memory OPCode OPCode Execute (Zend VM) Check OPCode Cache
OPCache ● コンパイル済みバイトコード(OPCode)を共有メモリに保存し、実行のた びにスクリプトの読み込み・パースの手間を省く仕組み OPCode Execute (Zend VM) OPCode OPCode Read OPCode Shared Memory Save OPCode Optimizer キャッシュあり OPCode Compiler Check OPCode Cache Parser キャッシュなし Lexer PHP Script
Apache MPM Prefork ● Apache起動時に予め(pre)子プロセスをコピー(fork)し、アクセスに備 える方式 ● リクエストを1つの子プロセスが処理する ○ 他のプロセスの影響を受けないが、多くのメモリやCPUが必要 http リクエスト http リクエスト SAPI 子プロセス SAPI 子プロセス 子プロセスを管理 コントローラー プロセス # ps auxf | grep httpd USER PID ... COMMAND root 11 ... httpd -DFOREGROUND apache 12 ... \_ httpd -DFOREGROUND apache 13 ... \_ httpd -DFOREGROUND
Apache MPM Prefork + OPCache ● OPCodeを共有メモリで管理(ファイル共有も可能) ○ プロセスをまたがっても同じOPCodeを共有 ○ OPCodeの保存や読み込みはセマフォを利用して排他制御 http リクエスト http リクエスト http リクエスト SAPI 子プロセス コントローラー プロセス SAPI 子プロセス SAPI 子プロセス OPCode Shared Memory
ビルドパイプライン (Before) ● 同一サーバに新しいバージョンのコードを配置 ● symlinkでコードの参照を切り替え ● httpd reloadコマンドを実行 ○ 設定ファイルの再読み込み ○ OPCacheのクリア # tree /var/www/html -L 2 /var/www/html ├── current │ ├── app │ ├── … │ └── releases ├── 20211115100000 └── 20211116100000
ビルドパイプライン (After) ● CodeBuild + CodeDeployを利用したBlue-Green Deployment ○ リリースのたびに新しいサーバに切り替わる仕組み ALB
ビルドパイプライン (After) ● CodeBuild + CodeDeployを利用したBlue-Green Deployment ○ リリースのたびに新しいサーバに切り替わる仕組み ALB ALB デプロイ ビルド CodeBuild CodeDeploy
ビルドパイプライン (After) ● CodeBuild + CodeDeployを利用したBlue-Green Deployment ○ リリースのたびに新しいサーバに切り替わる仕組み ALB ALB ALB 切り替え デプロイ ビルド CodeBuild CodeDeploy
とある事象発生
ある日のリリースにて ● ロードアベレージが高くなる現象が発生
現象確認と初動対応 ● 現象発生直前のリリースが怪しいと判断し、リバートを決断 ● ただ、リバート後も判断しきれない状態が続く (サーバを増設したのに負荷が下がりきらない) ○ 月末によるカオナビの機能(人事評価)の駆け込み需要? ● その後も、リリースのたびに負荷が高かったり低かったり…
インフラGで本格的な調査開始 ● OPCacheが怪しい… キャッシュヒット率
インフラGで本格的な調査開始 ● OPCacheが怪しい… キャッシュミス回数
根本原因がわからず ● ステージング環境でもOPCacheのヒット率低下が発生 ● 決め手がないまま、サーバの増設・縮退で高負荷を回避 ● 「カオナビの動作が最近遅い」との問い合わせも…(関連は不明) ● 開発側のメンバーも参戦! ここからは松下目線の話
調査&解決までの流れ
まずはOPCacheの仕組みを理解 ● OPCacheの状態を手軽に確認したい ○ https://gist.github.com/ck-on/4959032 ● OPCacheのウォームアップを一気に行いたい ○ https://gist.github.com/klimslim/ce8f727b3b419badce243bf5a74389b6 ○ require_once()とopcache_compile_file()による違いはない ● ローカルの検証環境は大体できた
以前との違いを再確認 ● ビルドパイプラインの変更とOPCache関連がやはり怪しい デプロイフローの確認 以前の方式の挙動確認
当たりをつける ● キャッシュミスが発生し続ける = opcache_compile_file()でキャッシュ されないファイルがあるはず ● realpathまわりが怪しい? ■余談 php-fpmを利用していた場合は、 OPCacheと realpathとの関連はものすごく重要! 詳しくはこちらの資料を参照 → https://www.slideshare.net/hnw/realpath-opcache
挙動を再現!
つまり? ● OPCacheの設定 ○ max_accelerated_files = 10000(デフォルト) ○ OPCacheは、内部でsymlinkとrealpathの両方のパスを 保持する(?)ため、2倍のキーを消費 ○ 16229 / 2 = 8115で頭打ちに
裏取り ● どんな仕組みでそうなっているのかちゃんと把握 http://blog.jpauli.tech/2015-03-05-opcache-html/
だがしかし…
oh...
再び盛り上がる人々
本番に近い環境で再検証 ● 誰も利用していない時間のステージング環境で再現確認
本番に近い環境で再検証 ● 突然再現しなくなる(キャッシュミスしなくなる)
!?
より詳細に調査 ● 現象発生中にキャッシュされていたファイル一覧を出力
より詳細に調査 ● OPCache側ではなくてファイルかディレクトリの問題? ● 適当にファイル作ってみる → キャッシュされる New
より詳細に調査 ● OPCache側ではなくてファイルかディレクトリの問題? ● 適当にファイル作ってみる → キャッシュされる ● 同じ内容のファイルを作ってみる → キャッシュされる New Not Cached New
より詳細に調査 ● OPCache側ではなくてファイルかディレクトリの問題? ● 適当にファイル作ってみる → キャッシュされる ● 同じ内容のファイルを作ってみる → キャッシュされる ● コピーしてみる → キャッシュされ…ない?! New Not Cached New Not Cached Copied
より詳細に調査 ● statで該当ファイルを見てみる… なぜか未来日付!!
大勝利!!
解説
そして解決へ…
そもそもなぜ未来日付になる? ● いまだ不明… ● 必ずなるわけでもない、ますます謎 ● AWSのサポートに問い合わせてみたものの分からず ○ CodeBuildでビルドした時点でなってる模様
問題解決に必要な能力
対象に関連する前提知識 ● インフラ ○ ファイルシステム、symlink ○ AWS関連(Codepipeline、CodeDeploy) ● ミドルウェア ○ Apache MPM Prefork ● PHPの動作の仕組み ○ OPCache
マインド・考え方 ● 論理的思考 ○ 広く・深く考える ○ 分割して考える ○ 筋道を考える ● ラバーダッキング
問題解決までの行動 現状認識 原因特定 解決策の 立案 ● 発生している事象を正確に洗い出す ● 期待する状態との差異を把握 解決策の 実施
問題解決までの行動 現状認識 原因特定 解決策の 立案 解決策の 実施 ● 原因となりうる要素の洗い出し ● 再現環境の構築・再現方法の確立 ● 原因の絞り込み ○ 当たりをつける(思い込みには注意) ○ どこまでOKでどこからNGか、境界を見極める
問題解決までの行動 現状認識 原因特定 ● 取りうる選択肢の洗い出し ● 判断軸をもとに解決策を決定 ○ 暫定対応・恒久対応 ○ 緊急性 ○ 対応にかかるコスト ■ スピード・リソース ○ 影響範囲・副作用の有無 解決策の 立案 解決策の 実施
問題解決までの行動 現状認識 原因特定 解決策の 立案 ● 解決策による効果を測定 ● 想定外の副作用に注意 ● いつでも実施前の状態に戻せるようにする 解決策の 実施
エンジニアとして大事なこと ● 実装技術の向上やフルスタックエンジニアを目指す以外にも、それぞれの 領域を深く掘り下げる意識も必要 ○ フロントエンジニア ■ 通信やブラウザの仕組み、端末の挙動 ○ バックエンドエンジニア ■ 実行環境の設定・ミドルウェア・OSの仕組み ○ インフラエンジニア ■ 動作させるミドルウェアやアプリケーションの特性や挙動の特徴
組織として大事なこと ● インシデントは初動が大事 ○ いろんなレイヤーで起こるため、原因の切り分けが難しい ○ 組織全体で協力が必要な場面もある ○ 「自分は関係ない」ではなく、 「知ってる知識が役に立つかも?」というスタンスで ● スピード感を持って動ける体制を作る
まとめ ● 問題解決のときにこそ、その人の地力が現れる ○ 組織力としても同様 ● エンジニアリングの世界は奥が深い! ● 完全に理解した、から一歩その先へ
おわり