Slide 1

Slide 1 text

デプロイを任されたので、 教わった通りにデプロイしたら障害になった件 〜俺のやらかしを越えてゆけ〜 izumitomo 2024/10/25

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

1⽇⽬も終盤! ⻑丁場ですので アイスブレイクから

Slide 4

Slide 4 text

©Techouse All Rights Reserved P4 有明といえば ビッグサイトですよね!

Slide 5

Slide 5 text

©Techouse All Rights Reserved P5 ビッグサイトの思い出 ● 営業イベントにSalesとして参加 ○ 出展者がブースでサービスを宣伝し、 来場者は興味のあるブースで話を聞く ● 弊社サービス『クラウドハウス』 も出展 ○ ⽣⾝の相⼿に対し、⾃分の⾔葉で ⾃社プロダクトを売ることに挑戦 来場者でごった返す会場

Slide 6

Slide 6 text

弊社ブースの様⼦ ブースの前を通りかかった⼈を 何とかブースに連れ込もうとしている⾃分 🤨 🤨 🙂 🙂 🙂

Slide 7

Slide 7 text

©Techouse All Rights Reserved P7 ビッグサイトの思い出 ● あの会社はああやってサービスを訴求しているのか… ● 企業の担当者はそういう課題感を持ってるのか… ● 俺が作ったあの超⼤作機能、相⼿に全然刺さらねえ…

Slide 8

Slide 8 text

©Techouse All Rights Reserved P8 ビッグサイトの思い出 ● あの会社はああやってサービスを訴求しているのか… ● 企業の担当者はそういう課題感を持ってるのか… ● 俺が作ったあの超⼤作機能、相⼿に全然刺さらねえ… ⇒これがプロダクト作りに⽋かせないinsightってやつか…!

Slide 9

Slide 9 text

©Techouse All Rights Reserved P9 ビッグサイトの思い出 〜郷に⼊っては郷に従え〜 ● エンジニアも顧客の前に⽴ってみてはいかが? ○ ただし、⾝なりには気をつけよう! ○ 我々エンジニアは時にあり得ない判断を平然と やってのける 実際にあった怖い話

Slide 10

Slide 10 text

©Techouse All Rights Reserved P10 ビッグサイトの思い出 〜郷に⼊っては郷に従え〜
 実際にあった怖い話 ● エンジニアも顧客の前に⽴ってみてはいかが? ○ ただし、⾝なりには気をつけよう! ○ 我々エンジニアは時にあり得ない判断を平然と やってのける

Slide 11

Slide 11 text

©Techouse All Rights Reserved P11 上野 友輔@izumitomo ● 株式会社Techouse ○ 去年ブースでガラポンやってた会社 ● Rails歴 2年 ○ 普段はRails x GraphQL で開発 ● Kaigi on Rails初登壇🙌 ⾃⼰紹介

Slide 12

Slide 12 text

デプロイしてますか?✋

Slide 13

Slide 13 text

そのデプロイの仕組み、 説明できますか?✋

Slide 14

Slide 14 text

©Techouse All Rights Reserved P14 デプロイとは ● デプロイとは開発したアプリケーションを本番環境に移⾏し、 実際にユーザが利⽤できる状態にする⼀連のプロセス ○ CI/CDプロセスが整備されると⾃然と⽬が向かなくなる ○ 特に初学者はブラックボックスになりがち ○ そしてなにかとやらかす 󰷻 😑 🤯

Slide 15

Slide 15 text

©Techouse All Rights Reserved P15 今⽇話すこと‧持ち帰ってもらえるもの 今⽇話すこと ● デプロイで実際にやらかした失敗とその対応 持ち帰ってもらえるもの ● Railsのダウンタイムのないデプロイにおける注意点 ● 初学者がデプロイに⽬を向けることのメリット

Slide 16

Slide 16 text

ある⽇

Slide 17

Slide 17 text

©Techouse All Rights Reserved P17 上野くん、デプロイ任せていい? 退職間近の先輩社員

Slide 18

Slide 18 text

©Techouse All Rights Reserved P18 任せてください!

Slide 19

Slide 19 text

©Techouse All Rights Reserved P19 あの緑のボタン押すだけですよね!

Slide 20

Slide 20 text

©Techouse All Rights Reserved P20 (新卒1年⽬)

Slide 21

Slide 21 text

©Techouse All Rights Reserved P21 ……

Slide 22

Slide 22 text

©Techouse All Rights Reserved P22 ……

Slide 23

Slide 23 text

©Techouse All Rights Reserved P23 そうだよ

Slide 24

Slide 24 text

©Techouse All Rights Reserved P24 ● あっさりデプロイを託されるも、流⽯に不安だったので どのようにデプロイされているかを軽く整理 ○ RailsアプリケーションをAmazon ECS on Fargateで管理 ○ GitHub Actionでスクリプトを実⾏してデプロイ ○ ゼロダウンタイムデプロイでサービスは閉塞しない

Slide 25

Slide 25 text

©Techouse All Rights Reserved P25 デプロイの流れ Docker Build ECR push migration実⾏ コンテナの更新 mainにMerge

Slide 26

Slide 26 text

©Techouse All Rights Reserved P26 デプロイの流れ migrationが実⾏されてからECSコンテナの更新が完了するまでの間は、 DBの変更だけ反映され、アプリケーションコードは古いままとなる ⇒テーブルやカラムのDELETEやRENAMEなどは要注意 Docker Build ECR push migration実⾏ コンテナの更新 mainにMerge DBの変更のみ反映

Slide 27

Slide 27 text

©Techouse All Rights Reserved P27 DBとアプリケーション間の不整合に 注意すればいいってことか🤔

Slide 28

Slide 28 text

©Techouse All Rights Reserved P28 ● さっそくデプロイの機会が訪れる ○ ある機能開発でDBの変更のみを先に本番に反映しておくことに ■ テーブルにカラムを追加するだけの変更で追加したカラムに 依存するアプリケーションコードは存在しない

Slide 29

Slide 29 text

©Techouse All Rights Reserved P29 
 
 👆

Slide 30

Slide 30 text

©Techouse All Rights Reserved P30 
 
 👆 無事にデプロイ完了🙌

Slide 31

Slide 31 text

©Techouse All Rights Reserved P31 
 
 👆 無事にデプロイ完了 🙌


Slide 32

Slide 32 text

©Techouse All Rights Reserved P32 デプロイ直後、サービスの例外通知⽤のslackチャンネル に1件の例外が通知された

Slide 33

Slide 33 text

©Techouse All Rights Reserved P33 デプロイ直後、サービスの例外通知⽤のslackチャンネル に1件の例外が通知された なにこれ?🤔

Slide 34

Slide 34 text

©Techouse All Rights Reserved P34 ん?🤔

Slide 35

Slide 35 text

©Techouse All Rights Reserved P35 ん?🤔


Slide 36

Slide 36 text

©Techouse All Rights Reserved P36 ん?🤔
 死ぬほど焦る 😱😱😱

Slide 37

Slide 37 text

©Techouse All Rights Reserved P37 退職間近の先輩社員 焦るな!! まずは落ち着いて状況整理から!

Slide 38

Slide 38 text

©Techouse All Rights Reserved P38 原因調査をしたくなるのもわかるが まずは関係部署に連絡と顧客影響を調査!

Slide 39

Slide 39 text

©Techouse All Rights Reserved P39 了解です…申し訳ないです…

Slide 40

Slide 40 text

©Techouse All Rights Reserved P40 影響範囲の調査 ● 例外は全てSidekiqの⾮同期処理の中で発⽣していた ○ ジョブのリトライが実⾏され、リトライ処理で成功していた ○ 結果として幸いにもユーザ影響はなかった

Slide 41

Slide 41 text

©Techouse All Rights Reserved P41 誰にでも失敗はあるから気にするな! いい経験だから後で原因調査するといいよ! ためになるアドバイスを残し、先輩は退職

Slide 42

Slide 42 text

©Techouse All Rights Reserved P42 頼りになる先輩 
 誰にでも失敗はあるから気にするな! 
 いい経験だから後で原因調査するといいよ! 
 ためになるアドバイスを残し、先輩は退職 数⽇後

Slide 43

Slide 43 text

©Techouse All Rights Reserved P43 原因解明へ この前デプロイで起こったあの問題 ちゃんと調べてみるか🤔

Slide 44

Slide 44 text

©Techouse All Rights Reserved P44 発⽣した例外クラス:ActiveRecord::PreparedStatementCacheExpired メッセージ:ERROR: cached plan must not change result type …PreparedStatementって何?

Slide 45

Slide 45 text

©Techouse All Rights Reserved P45 PreparedStatementを理解するにあたって以下の⽤語を 軽くおさらい ● プレースホルダ ● バインド

Slide 46

Slide 46 text

©Techouse All Rights Reserved P46 周辺知識の整理 Railsコンソールで User.find(1)を実⾏すると以下のログが出る $1,$2という特殊な記号を⽤いて動的に値を割り当てているが、 この記号をプレースホルダと呼び、値の割り当てをバインドと呼ぶ

Slide 47

Slide 47 text

©Techouse All Rights Reserved P47 周辺知識の整理 ● バインドのタイミングに関して2つの⼿法がある ○ 静的プレースホルダ ■ データベース側でバインドする ○ 動的プレースホルダ ■ アプリケーション側でバインドする

Slide 48

Slide 48 text

©Techouse All Rights Reserved P48 周辺知識の整理 ● バインドのタイミングに関して2つの⼿法がある ○ 静的プレースホルダ ■ データベース側でバインドする ■ PreparedStatementに関係があるのはこっち ○ 動的プレースホルダ ■ アプリケーション側でバインドする

Slide 49

Slide 49 text

©Techouse All Rights Reserved P49 静的プレースホルダのバインドの流れ 1. アプリケーション側は以下の2つを送る a. プレースホルダが⼊ったSQL⽂ b. 実際のパラメータの値 2. データベース側はバインドを⾏い、SQL⽂を実⾏する SELECT * from users where id = $1 AND LIMIT $2 
 $1 = 1, $2 = 1 SELECT * from users where id = 1 AND LIMIT $2 1
 1


Slide 50

Slide 50 text

©Techouse All Rights Reserved P50 1. アプリケーション側は以下の2つを送る a. プレースホルダが⼊ったSQL⽂ SQL⽂の構造が確定しているのでSQL実⾏前に構⽂解析ができる ⇒構⽂解析済みのSQL⽂をPreparedStatementとして、コネクションが  切れるまで保持する b. 実際のパラメータの値 2. データベース側はバインドを⾏い、SQL⽂を実⾏する SELECT * from users where id = $1 AND LIMIT $2 
 静的プレースホルダのバインドの流れ

Slide 51

Slide 51 text

©Techouse All Rights Reserved P51 PreparedStatementについて ● PREPARE⽂で作成し、EXECUTE⽂で実⾏できる EXECUTE⽂の実⾏結果
 (SELECT name FROM users where id = 1 LIMIT 1 が実⾏される)
 PREPARE⽂の実⾏結果 (返り値はPREPARE)

Slide 52

Slide 52 text

©Techouse All Rights Reserved P52 
 
 検証環境でPreparedStatementの 中⾝を確認してみるか

Slide 53

Slide 53 text

©Techouse All Rights Reserved P53 ● 『クラウドハウス』シリーズはDBにPostgreSQLを採⽤ ○ PostgreSQLにはPrepatedStatementを確認できる pg_prepared_statementsというシステムビューがある ● 検証環境のコンテナの中に⼊ってRailsコンソールから確認 してみる

Slide 54

Slide 54 text

©Techouse All Rights Reserved P54 Railsコンソールからpg_prepared_statementsを覗いてみた結果

Slide 55

Slide 55 text

©Techouse All Rights Reserved P55 ⾒覚えのある単語 発⽣した例外クラス:ActiveRecord::PreparedStatementCacheExpired メッセージ:ERROR: cached plan must not change result type

Slide 56

Slide 56 text

©Techouse All Rights Reserved P56 result_typeはstatementをEXECUTEした時の結果の型が⼊る websitesテーブルのスキーマ(自動生成)

Slide 57

Slide 57 text

©Techouse All Rights Reserved P57 
 
 なるほど…そういうことか😮

Slide 58

Slide 58 text

©Techouse All Rights Reserved P58 1. アプリケーション側は以下の2つを送る a. プレースホルダが⼊ったSQL⽂ ⇒構⽂解析済みのSQL⽂をPreparedStatementとして、コネクションが  切れるまで保持する b. 実際のパラメータの値 2. データベース側はバインドを⾏い、SQL⽂を実⾏する SELECT * from users where id = $1 AND LIMIT $2 
 再掲:静的プレースホルダのバインドの流れ

Slide 59

Slide 59 text

©Techouse All Rights Reserved P59 再掲:整理したデプロイの流れ migrationの実⾏からコンテナの更新で古いコンテナが停⽌するまでの間、 不整合が⽣まれる Docker Build ECR push migration実⾏ コンテナの更新 mainにMerge DBの変更のみ反映

Slide 60

Slide 60 text

©Techouse All Rights Reserved P60 再掲:整理したデプロイの流れ migrationの実⾏からコンテナの更新で古いコンテナが停⽌するまでの間、 不整合が⽣まれる ⇒古いコンテナのDBコネクションには、migration実⾏前に作られた  古いPreparedStatementが残っている Docker Build ECR push migration実⾏ コンテナの更新 mainにMerge DBの変更のみ反映

Slide 61

Slide 61 text

©Techouse All Rights Reserved P61 古いPreparedStatement テーブルにカラムが追加され、PreparedStatementが古くなると ● 構⽂解析時に確定したresult_types ● 実際にstatementを実⾏して返ってきた結果の型 が異なる状態に陥ってしまう

Slide 62

Slide 62 text

©Techouse All Rights Reserved P62 古いPreparedStatement テーブルにカラムが追加され、PreparedStatementが古くなると ● 構⽂解析時に確定したresult_types ● 実際にstatementを実⾏して返ってきた結果の型 が異なる状態に陥ってしまう 発⽣した例外クラス:ActiveRecord::PreparedStatementCacheExpired メッセージ:ERROR: cached plan must not change result type

Slide 63

Slide 63 text

©Techouse All Rights Reserved P63 
 
 ざっくり何が起こったか検討が付いたし ソースコードを読みに⾏くか🧐

Slide 64

Slide 64 text

©Techouse All Rights Reserved P64 Railsの中を読む ● 発⽣した例外を元に調べるとすぐに該当のコードが⾒つかった ● トランザクションの中のみraiseするようになっている ○ トランザクション外だとstatementを削除してリトライする ○ トランザクション内ではPostgresql仕様上、リトライで解消できない activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb(7.2-stable)


Slide 65

Slide 65 text

©Techouse All Rights Reserved P65 ローカルで再現 ● ソースコードをもとに、ローカルで簡単に例外を再現できた ○ 2つのターミナルを準備してそれぞれ別々に動かす ■ RailsコンソールからSQLを実⾏するターミナル ■ カラム追加のmigrationを実⾏するターミナル

Slide 66

Slide 66 text

©Techouse All Rights Reserved P66 対応策 Rails7以降、この状況を回避するために config.active_record.enumerate_columns_in_select_statements というオプションが追加されている

Slide 67

Slide 67 text

©Techouse All Rights Reserved P67 これをTRUEにすると、ワイルドカードクエリを回避する config.active_record.enumerate_columns_in_select_statements = FALSE config.active_record.enumerate_columns_in_select_statements = TRUE カラム追加を⾏ってもresult_typesの不⼀致が発⽣しない 対応策

Slide 68

Slide 68 text

©Techouse All Rights Reserved P68 今回の事象について理解できたが、 スキーマを変更するデプロイは今までも⾏われていたはず ⇒このやらかしは初めて起こったのか?

Slide 69

Slide 69 text

©Techouse All Rights Reserved P69 今回の事象について理解できたが、 
 スキーマを変更するデプロイは今までも行われていたはず 
 
 ⇒このやらかしはこれが本当に初めてだったのか? 
 
 調べた結果

Slide 70

Slide 70 text

©Techouse All Rights Reserved P70 過去に起きてたけど 無視されてた…

Slide 71

Slide 71 text

©Techouse All Rights Reserved P71

Slide 72

Slide 72 text

©Techouse All Rights Reserved P72 先輩の優しさに気付く ためになるタスクを残してくれた 先輩の優しさに感謝🥰

Slide 73

Slide 73 text

©Techouse All Rights Reserved P73 余談:MySQLについて ● RailsのPreparedStatementのデフォルトの利⽤設定について、 MySQLは利⽤しない設定になっている ● MySQLでもRails7.2以降からデフォルトで利⽤する動きがあった ○ しかしバグ報告により現在もデフォルトは利⽤しない設定となっている

Slide 74

Slide 74 text

©Techouse All Rights Reserved P74 そして気付く 
 以前から起こってたのでは ……?
 時は流れて

Slide 75

Slide 75 text

©Techouse All Rights Reserved P75 
 
 デプロイ慣れた😘

Slide 76

Slide 76 text

©Techouse All Rights Reserved P76 若かりし頃 
 ⼀つ⼀つ丁寧にデプロイしていたあの頃も今は昔 ● DBとアプリケーション間の不整合は起きるか? ● 仮に起きる場合、どのような問題が発⽣する? ● その場合の対処は何が考えられる? ● …

Slide 77

Slide 77 text

©Techouse All Rights Reserved P77 流れるようにMergeボタンを押していく⽇々 現在 👆 🫰 🫰 🤟 🤟 👆

Slide 78

Slide 78 text

©Techouse All Rights Reserved P78 現在
 流れるように Mergeボタンを押していく日々 
 
 
 ある⽇

Slide 79

Slide 79 text

©Techouse All Rights Reserved P79 「エクスポートが実⾏中のまま終わらない」 と先ほど顧客から連絡が来ました…

Slide 80

Slide 80 text

©Techouse All Rights Reserved P80 再度エクスポートを⾏うと 今度は成功したとのことです

Slide 81

Slide 81 text

©Techouse All Rights Reserved P81 原因わかりますか?

Slide 82

Slide 82 text

©Techouse All Rights Reserved P82 
 
 うーん…ちょっと調べてみますね

Slide 83

Slide 83 text

©Techouse All Rights Reserved P83 
 
 (エクスポート機能は初期に突貫で作られた らしいので、多分バグがありそう…🤔)

Slide 84

Slide 84 text

©Techouse All Rights Reserved P84 
 
 (やれやれ…先⼈のバグを直してやるか😘)

Slide 85

Slide 85 text

©Techouse All Rights Reserved P85 現状把握 ● エクスポートは⾮同期処理で⾏われる ○ ⾮同期処理はSidekiqで⾏っている ○ エクスポートのジョブの状態や開始‧完了時刻はDBに保存

Slide 86

Slide 86 text

©Techouse All Rights Reserved P86 現状把握 ● 調べてみると確かに顧客のエクスポートのジョブのステータス が「実⾏中」のまま数時間が経過していた ○ 本来であればエクスポートは⻑くても数分で終わる ○ 既にそのジョブはSidekiqのWorker内に存在しない ■ ジョブの実⾏状況を監視するSidekiq Web UIというツールがある

Slide 87

Slide 87 text

©Techouse All Rights Reserved P87 現状把握
 ● ログをもとにエクスポート処理のコードを読みながら 原因を探っていく ○ しかし原因がわからない… ■ ローカルで⾊々動かしてみても特に何も得られず ■ 異常終了しているのに例外を検知できていないのが謎

Slide 88

Slide 88 text

©Techouse All Rights Reserved P88 ふと、異常終了したジョブを眺めていると ある事実に気づいた

Slide 89

Slide 89 text

©Techouse All Rights Reserved P89 
 
 そういや、このジョブの開始直後に 俺デプロイしたな…😶

Slide 90

Slide 90 text

©Techouse All Rights Reserved P90 
 
 もしかしてこれ… デプロイした俺のせい?🫣

Slide 91

Slide 91 text

©Techouse All Rights Reserved P91 
 
 検証環境で試してみるか

Slide 92

Slide 92 text

©Techouse All Rights Reserved P92 検証環境でエクスポートの⾮同期処理の実⾏中にデプロイを ⾛らせてみる…

Slide 93

Slide 93 text

©Techouse All Rights Reserved P93 検証環境でエクスポートの⾮同期処理の実⾏中にデプロイを ⾛らせてみる… ⇒「実⾏中」のまま⽌まった 状況の再現に成功(嬉しいけど嬉しくはない)

Slide 94

Slide 94 text

©Techouse All Rights Reserved P94 検証環境でエクスポートの⾮同期処理の実⾏中にデプロイを ⾛らせてみる… ⇒「実⾏中」のまま⽌まった 状況の再現に成功(嬉しいけど嬉しくはない) この瞬間、エンジニアとしての直感が働いた

Slide 95

Slide 95 text

©Techouse All Rights Reserved P95 
 
 ⼀応…他の⾮同期処理も⾒とくか🤔

Slide 96

Slide 96 text

©Techouse All Rights Reserved P96 インポートの処理中にデプロイしてみる

Slide 97

Slide 97 text

©Techouse All Rights Reserved P97 インポートの処理中にデプロイしてみる ⇒「実⾏中」のまま⽌まる

Slide 98

Slide 98 text

©Techouse All Rights Reserved P98 インポートの処理中にデプロイしてみる ⇒「実⾏中」のまま⽌まる メール送信の処理中にデプロイしてみる

Slide 99

Slide 99 text

©Techouse All Rights Reserved P99 インポートの処理中にデプロイしてみる ⇒「実⾏中」のまま⽌まる メール送信の処理中にデプロイしてみる ⇒「実⾏中」のまま⽌まる

Slide 100

Slide 100 text

©Techouse All Rights Reserved P100 インポートの処理中にデプロイしてみる ⇒「実⾏中」のまま⽌まる メール送信の処理中にデプロイしてみる ⇒「実⾏中」のまま⽌まる SMS送信の処理中にデプロイしてみる

Slide 101

Slide 101 text

©Techouse All Rights Reserved P101 インポートの処理中にデプロイしてみる ⇒「実⾏中」のまま⽌まる メール送信の処理中にデプロイしてみる ⇒「実⾏中」のまま⽌まる SMS送信の処理中にデプロイしてみる ⇒「実⾏中」のまま⽌まる

Slide 102

Slide 102 text

©Techouse All Rights Reserved P102 気付いた 
 
 デプロイのたびに ジョブ消し⾶んでる🚀🚀

Slide 103

Slide 103 text

©Techouse All Rights Reserved P103 気付いた
 
 
 デプロイのたびに 
 ジョブ消し飛んでる 🚀
 😱⼤障害😱

Slide 104

Slide 104 text

©Techouse All Rights Reserved P104 調査開始 ● Sidekiqのドキュメントとソースコードを読み、実⾏中のジョ ブがあるときにどのようにSidekiqが停⽌するかを理解 ○ SIGTERMシグナルが重要な役割を果たしている

Slide 105

Slide 105 text

©Techouse All Rights Reserved P105 ここで⼀旦、シグナルのおさらい

Slide 106

Slide 106 text

©Techouse All Rights Reserved P106 おさらい:シグナル ● シグナルとはソフトウェア割り込みの⼀種 ○ 特定のイベント発⽣時にプロセスに通知するための仕組み ○ Ctrl-CやCtrl-Zで常⽇頃お世話になっているアレ ● SIGTERMシグナルはプロセスの安全な終了のために使われる ○ SIGTERM受信時の動作はプログラムに委ねられている ■ 例えばプロセスの強制終了を伝えるSIGKILLを受信した場合、 制御はOSに委ねられるためプログラムは関与できない

Slide 107

Slide 107 text

©Techouse All Rights Reserved P107 おさらい:シグナル sleep中のプロセスにSIGTERMを送ってみる

Slide 108

Slide 108 text

©Techouse All Rights Reserved P108 おさらい:シグナル sleepしているプロセスのID(PID)をpstreeで特定してみる PID25988のプロセスがsleepを実⾏しているので、このプロセス に対してSIGTERMを送ればよい

Slide 109

Slide 109 text

©Techouse All Rights Reserved P109 おさらい:シグナル 別のターミナルから kill -SIGTERM 25988  を実⾏してSIGTERMを送ると、sleepは以下のように終了する

Slide 110

Slide 110 text

©Techouse All Rights Reserved P110 ● SidekiqはSIGTERMを受け取ると、現在実⾏中のジョブの 終了を待つquietという状態に移⾏する ○ quietの時、新しいジョブの実⾏は受け付けない ○ 実⾏中のジョブの終了を待つ猶予時間は調整可能 Sidekiqの仕様

Slide 111

Slide 111 text

©Techouse All Rights Reserved P111 quiet ジョブ実⾏中 キュー SIGTERM キュー もうすぐ⾃分停⽌するんで 新規ジョブは受け付けません

Slide 112

Slide 112 text

©Techouse All Rights Reserved P112 Sidekiqの仕様 ● 猶予時間が経っても実⾏中のジョブが存在する場合、その ジョブをキューに押し戻す ○ 新たにSidekiqコンテナが⽴ち上がると、キューに戻されたそ のジョブを再び実⾏する

Slide 113

Slide 113 text

©Techouse All Rights Reserved P113 少し待ってみても ジョブが終わらん… キュー キューに戻して 未来の⾃分に処理を任せよう quiet キュー プロセス終了

Slide 114

Slide 114 text

©Techouse All Rights Reserved P114 
 
 現状、明らかに仕様と 異なる挙動をしている…

Slide 115

Slide 115 text

©Techouse All Rights Reserved P115 
 
 まずはローカル環境で ジョブの消失を観察するか🧐

Slide 116

Slide 116 text

©Techouse All Rights Reserved P116 Docker環境でSidekiqコンテナにSIGTERMを送ってみる…

Slide 117

Slide 117 text

©Techouse All Rights Reserved P117 Docker環境でSidekiqコンテナにSIGTERMを送ってみる… ⇒ジョブがキューに押し戻された 状況の再現に失敗(嬉しくないけど嬉しい)

Slide 118

Slide 118 text

©Techouse All Rights Reserved P118 Docker環境でSidekiqコンテナにSIGTERMを送ってみる… ⇒ジョブがキューに押し戻された 状況の再現に失敗(嬉しくないけど嬉しい) ローカルで発⽣しないということは……?

Slide 119

Slide 119 text

©Techouse All Rights Reserved P119 
 
 ECS周りの設定が怪しい…!🤩

Slide 120

Slide 120 text

©Techouse All Rights Reserved P120 
 
 (ECS全然わからん😆)

Slide 121

Slide 121 text

©Techouse All Rights Reserved P121 ● ECSのドキュメントを読んでコンテナライフサイクルを把握 ○ 停⽌時にコンテナのエントリプロセスはSIGTERMを受け取る ○ SIGTERM受信から⼀定の猶予時間の後にSIGKILLが⾶んでくる ■ この猶予時間は調整可能 ○

Slide 122

Slide 122 text

©Techouse All Rights Reserved P122 ● つまりアプリケーションはSIGTERMを受け取ってからSIGKILL が来るまでに処理を正常に終了させるように必要がある ○ SidekiqではSIGTERMを受け取るとquietに移⾏して所定の時間 だけ処理が完了するのを待つ

Slide 123

Slide 123 text

©Techouse All Rights Reserved P123 ● つまりアプリケーションはSIGTERMを受け取ってからSIGKILL が来るまでに処理を正常に終了させるように必要がある ○ SidekiqではSIGTERMを受け取るとquietに移⾏して所定の時間 だけ処理が完了するのを待つ ○ この待っている間にSIGKILLが⾶んできたらジョブが消える! ■ SidekiqとECSの設定を⾒直す

Slide 124

Slide 124 text

©Techouse All Rights Reserved P124 ● しかし設定は正しかった ○ Sidekiqは25秒待つ(デフォルト) ○ ECSの⽅は120秒待つ

Slide 125

Slide 125 text

©Techouse All Rights Reserved P125 あてが外れたので、次はローカルと検証環境のSidekiq Web UIをじっくり⾒⽐べていくことに

Slide 126

Slide 126 text

©Techouse All Rights Reserved P126 Sidekiq Web UIの画⾯

Slide 127

Slide 127 text

©Techouse All Rights Reserved P127 ローカル 以下の稼働中のSidekiqコンテナに対し、SIGTERMを送ってみる 稼働中のSidekiqプロセス

Slide 128

Slide 128 text

©Techouse All Rights Reserved P128 ローカル SIGTERMを送るとquietに移⾏した 初期状態 SIGTERMを送った後

Slide 129

Slide 129 text

©Techouse All Rights Reserved P129 ローカル そして猶予時間が経過すると停⽌した 初期状態 SIGTERMを送った後 時間経過後

Slide 130

Slide 130 text

©Techouse All Rights Reserved P130 検証環境 検証環境で同様にコンテナを停⽌させてみる 初期状態

Slide 131

Slide 131 text

©Techouse All Rights Reserved P131 検証環境 SidekiqコンテナがSIGTERMを受け取るはずの状態になっても なぜかquietにならない… 初期状態 SIGTERMを受け取るはずの状態

Slide 132

Slide 132 text

©Techouse All Rights Reserved P132 検証環境 そしてそのまま消えた 初期状態 SIGTERMを受け取るはずの状態 ECSタスクが停⽌

Slide 133

Slide 133 text

©Techouse All Rights Reserved P133 
 
 SidekiqにSIGTERM到達してない🫣

Slide 134

Slide 134 text

©Techouse All Rights Reserved P134 ● SIGTERMを受け取るエントリプロセスがどこに なっているのかを調べる ○ ECSのタスク定義に記載されているcommandフィールド がエントリプロセスだと判明 ■ DockerfileとECSタスク定義の書き⽅次第で変わる

Slide 135

Slide 135 text

©Techouse All Rights Reserved P135 
 commandフィールド startup_sidekiq.shの中⾝

Slide 136

Slide 136 text

©Techouse All Rights Reserved P136 
 commandフィールド startup_sidekiq.shの中⾝ ぱっと⾒、正しそうだが…🤔

Slide 137

Slide 137 text

©Techouse All Rights Reserved P137 ● プロセスツリーを書き出してみる(PIDは適当)

Slide 138

Slide 138 text

©Techouse All Rights Reserved P138 ● プロセスツリーを書き出してみる(PIDは適当) ● PID14がエントリプロセスとなる

Slide 139

Slide 139 text

©Techouse All Rights Reserved P139 ● プロセスツリーを書き出してみる(PIDは適当) bashにSIGTERM送ってるな🫣

Slide 140

Slide 140 text

©Techouse All Rights Reserved P140 対応 ● Sidekiqをエントリプロセスとして扱えばよいのでexecを使う

Slide 141

Slide 141 text

©Techouse All Rights Reserved P141 ● execは今のシェルプロセスを指定したプログラムに置き換える ○ 詳細は割愛するが、以下のように置き換わる

Slide 142

Slide 142 text

©Techouse All Rights Reserved P142 execを付けた状態で⾮同期処理の実⾏中にデプロイしてみる…

Slide 143

Slide 143 text

©Techouse All Rights Reserved P143 execを付けた状態で⾮同期処理の実⾏中にデプロイしてみる… ⇒ジョブがキューに押し戻された!

Slide 144

Slide 144 text

©Techouse All Rights Reserved P144 execを付けた状態で⾮同期処理の実⾏中にデプロイしてみる… ⇒ジョブがキューに押し戻された! 無事解決できたが、再びエンジニアの直感が働く

Slide 145

Slide 145 text

©Techouse All Rights Reserved P145 
 
 インフラ周りの設定って 使い回されがちだよな…🤔

Slide 146

Slide 146 text

©Techouse All Rights Reserved P146 
 
 他のECSタスクも⼀応 確認しておくか

Slide 147

Slide 147 text

©Techouse All Rights Reserved P147 
 
 他のECSタスクも一応 
 確認しておくか 
 その結果

Slide 148

Slide 148 text

©Techouse All Rights Reserved P148 ● 全部同じミスしてた… ○ SidekiqだけでなくPumaやgRPCなど、プロダクト内の全 ECSコンテナが同じやらかしをしていた ● というわけであわせて⼀気に修正

Slide 149

Slide 149 text

©Techouse All Rights Reserved P149 ● 今度こそ⼀件落着だが、三度エンジニアの直感が働く ○ というか途中で流⽯に気づいたけど、⾒ないふりしてた

Slide 150

Slide 150 text

©Techouse All Rights Reserved P150 
 
 インフラ周りの設定って 使い回されがちだよな(2回⽬)

Slide 151

Slide 151 text

©Techouse All Rights Reserved P151 
 
 他の事業部…⼤丈夫だよな?

Slide 152

Slide 152 text

©Techouse All Rights Reserved P152 
 
 他の事業部 …大丈夫だよな? 
 その結果

Slide 153

Slide 153 text

©Techouse All Rights Reserved P153 驚愕の事実 全社で同じミスしてると気付く (bashにシグナル送ってた)

Slide 154

Slide 154 text

©Techouse All Rights Reserved P154 驚愕の事実 全社で同じミスしてると気付く (bashにシグナル送ってた) その結果

Slide 155

Slide 155 text

©Techouse All Rights Reserved P155 「もしかして…俺が浅いだけか?」 と急に不安になる新卒1年⽬

Slide 156

Slide 156 text

©Techouse All Rights Reserved P156 「もしかして…俺が浅いだけか?」 と急に不安になる新卒1年⽬ その結果

Slide 157

Slide 157 text

©Techouse All Rights Reserved P157 bashにシグナルを送る 深遠な意味を考え始める

Slide 158

Slide 158 text

©Techouse All Rights Reserved P158 他の事業部のDevに聞いてみることに

Slide 159

Slide 159 text

©Techouse All Rights Reserved P159 
 
 あの…デプロイ時にSidekiqの実⾏中の ジョブ、消えてたりしませんか…? 他事業部のDev

Slide 160

Slide 160 text

©Techouse All Rights Reserved P160 
 
 いや、特に問題ないよ? 他事業部のDev

Slide 161

Slide 161 text

©Techouse All Rights Reserved P161 
 
 あれっ…そうですか (やっぱ俺がなんか⾒落としてるのか…)

Slide 162

Slide 162 text

©Techouse All Rights Reserved P162 
 
 だって実⾏中のジョブがないか、 毎回チェックしてデプロイしてるからね

Slide 163

Slide 163 text

©Techouse All Rights Reserved P163 
 
 🤯🤯🤯 


Slide 164

Slide 164 text

©Techouse All Rights Reserved P164 ● 運⽤でたまたま回避できていた ○ Sidekiq Web UIを使えばプロセスをquietにできるので 可能ではある ● 全事業部で修正を進めていくことになった

Slide 165

Slide 165 text

©Techouse All Rights Reserved P165 余談:技術責任者に報告 ● ECSのタスク定義というインフラ周りの設定に⼿を⼊れたこと もあり、今回の件を技術責任者のYさんに伝えておくことに ○ Yさんは社内の全プロダクトをインフラ⾯から⼿厚くサポート ○ 過去にYさんがECSタスクのオートスケールイン‧スケールアウト でGraceful Shutdownを導⼊しようとしていた ■ もしかしたら今回の件が役に⽴つかもしれない

Slide 166

Slide 166 text

©Techouse All Rights Reserved P166 
 
 以前ECSでGraceful Shutdownを導⼊ しようとしてたと思うんですけど… Yさん

Slide 167

Slide 167 text

©Techouse All Rights Reserved P167 
 
 Yさん そうなんだけどね、 上⼿く設定がハマってないんだよ…!

Slide 168

Slide 168 text

©Techouse All Rights Reserved P168 
 
 あの…それもしかしたら

Slide 169

Slide 169 text

©Techouse All Rights Reserved P169 
 
 現状、原因としてはいくつか考えられて、 まずECSのstopTimeoutが短すぎるという可能性、 そしてそもそものシグナルハンドラにバグがある厄介な パターン、別の可能性として間に挟まっているCDNのタ イムアウトが考慮できてないのもありえて… ……だから………かもしれないし……

Slide 170

Slide 170 text

©Techouse All Rights Reserved P170 
 
 現状、原因としてはいくつか考えられて、 まずECSのstopTimeoutが短すぎるという可能性、 そしてそもそものシグナルハンドラにバグがある厄介な パターン、別の可能性として間に挟まっているCDNのタ イムアウトが考慮できてないのもありえて… ……だから………かもしれないし…… そもそもSIGTERM届いてないかもです…

Slide 171

Slide 171 text

©Techouse All Rights Reserved P171 
 
 ……

Slide 172

Slide 172 text

©Techouse All Rights Reserved P172 
 
 ……

Slide 173

Slide 173 text

©Techouse All Rights Reserved P173 
 
 …まじで?

Slide 174

Slide 174 text

©Techouse All Rights Reserved P174 
 
 …まじで? 誰にでも⾒落としはあるので、臆せず意⾒することは⼤事

Slide 175

Slide 175 text

©Techouse All Rights Reserved P175 まとめ(初学者向け) ● デプロイ業務とやらかしを通じて多くの学びを得た ○ 普段の開発業務では関⼼すら持てていなかった技術領域に デプロイという⾃然な切り⼝から⾶び込むことができた ○ 当時まだ駆け出しだった⾃分にとって⼤きな財産になった ● 今⼀度デプロイに⽬を向けてみてはいかが?

Slide 176

Slide 176 text

©Techouse All Rights Reserved P176 まとめ(初学者をフォローするミドル層向け) ● 若⼿の成⻑を促すために、時には⾝の丈以上の責任ある仕事を 託す勇気と、⼼置きなくチャレンジできる環境づくりが⼤切 ○ 全⼒のチャレンジにはやらかしがつきもの ■ でもそのやらかしは必ず彼らの糧になる ○ 転ばぬ先の杖というより、転んだ先の杖を⽤意しておく ■ 当時の⾃分にデプロイを託すというチームの決断の意味と、 その環境を整えてくれていたことを後になって気付いた

Slide 177

Slide 177 text

©Techouse All Rights Reserved P177 謝辞 ● デプロイを託してくれた先輩含む開発チーム ● 何度も技術的アドバイスをくれたYさん ● 全社のやらかしを晒す暴挙を許してくれた会社 皆様に感謝🥰

Slide 178

Slide 178 text

©Techouse All Rights Reserved P178 ご清聴ありがとうございました デプロイを任されたので、 教わった通りにデプロイしたら障害になった件 〜俺 俺たちのやらかしを越えてゆけ〜 Fin. =