Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
RailsのAPIを普通に動かしたい話 2021 / 05 / 28 ~ 事業成長を加速させたエンジニアリングのウラ側 ~ @terry_i_ MedPeer, inc. - Engineer
Slide 2
Slide 2 text
Agenda 自己紹介 導入 背景 起きたこと 原因と解決 まとめ
Slide 3
Slide 3 text
誰やお前
Slide 4
Slide 4 text
福本 晃之 Teruhisa Fukumoto MedPeer Web Developer (2019/10~) f-teruhisa @terry_i_
Slide 5
Slide 5 text
Twitter #medpeer
Slide 6
Slide 6 text
導入
Slide 7
Slide 7 text
No content
Slide 8
Slide 8 text
特定保健指導 プラットフォーム開発
Slide 9
Slide 9 text
関連技術 / アーキテクチャ 『特定保健指導"フィッツプラス"事業を支えるモノリシック Rails + VIPER Swift アーキテクチャ』: https://tech.medpeer.co.jp/entry/2020/06/22/121153
Slide 10
Slide 10 text
フィッツプラスのRails API ● モノリシックRailsを中心とした構成 ● 同じRails上に異なるスキーマのAPIが複数存在する ● 各APIスキーマのコンテキストが微妙に異なる ○ スマホ/Webアプリ, 社内/外向け, toB/toC etc...
Slide 11
Slide 11 text
そこでタイトルの話題ですよ
Slide 12
Slide 12 text
RailsのAPIを普通に動かしたい話
Slide 13
Slide 13 text
“普通” is 何?? ● スキーマ定義通りにレスポンスを返すこと ● エラー検知できること、通常動作は検知しないこと ● テストが可能なこと、テストしやすいこと
Slide 14
Slide 14 text
普通が意外と難しい ※APIを開発している現場のイメージです
Slide 15
Slide 15 text
● API開発でまんまとハマった罠 ● 最低限気をつけといたほうがよさそうな話 ● やった工夫とか小さいTipsとか 今日する話「普通を目指すために」 ≠API開発のベストプラクティスの話
Slide 16
Slide 16 text
起きたこと
Slide 17
Slide 17 text
今回取り上げるサービスのAPI スマホアプリ ⇔ サーバー ⇔ 自社のRails(★)
Slide 18
Slide 18 text
1日の食事写真をスマホからアップロード スマホアプリ ⇔ サーバー ⇔ 自社のRails(★)
Slide 19
Slide 19 text
リリース前 テストもバッチリ書いてるし staging環境での動作検証も問題ない。 通知されたエラーも全部潰したし、 勝ちゲーや!!俺はエライ!!
Slide 20
Slide 20 text
リリース後に問題が...
Slide 21
Slide 21 text
同じエラーの通知が100回鳴る
Slide 22
Slide 22 text
ユニークなはずのレコードが2つ作られる
Slide 23
Slide 23 text
No content
Slide 24
Slide 24 text
なんでや
Slide 25
Slide 25 text
サンプルコード ※プロダクトコードから改変しています
Slide 26
Slide 26 text
No content
Slide 27
Slide 27 text
No content
Slide 28
Slide 28 text
● トランザクション ● 並列度の高いAPIリクエスト ● ActiveJobによる非同期処理 この辺の複合的な要因
Slide 29
Slide 29 text
ちょっとずつ見ていきます
Slide 30
Slide 30 text
原因と解決
Slide 31
Slide 31 text
1. トランザクション 2. 並列度の高いAPIリクエスト 3. ActiveJobによる非同期処理 原因
Slide 32
Slide 32 text
1. トランザクション 2. 並列度の高いAPIリクエスト 3. ActiveJobによる非同期処理 原因
Slide 33
Slide 33 text
No content
Slide 34
Slide 34 text
あっ(凡ミス)
Slide 35
Slide 35 text
find_or_create_by 自体がトランザクションを張る トランザクション内でカジュアルに使う https://railsguides.jp/active_record_querying.html#find-or-create-by
Slide 36
Slide 36 text
解決するなら...?? ● find_or_create_byしない(簡単) ● トランザクションをネスト(require: true)させる ○ トランザクションの状態を読み解きづらくあんまやりたくない ● エラー時にトランザクションを抜けてリトライさせる ● (create_withとかでINSERT時にlock外したりしてもOKぽいけど、コード上で ロック状態を読み解かせるのはツライ気がしている) 参考になるやつ『Rails APIドキュメント: Active Recordのトランザクション(翻訳)』 : https://techracho.bpsinc.jp/hachi8833/2020_11_30/101160
Slide 37
Slide 37 text
1. トランザクション 2. 並列度の高いAPIリクエスト 3. ActiveJobによる非同期処理 原因
Slide 38
Slide 38 text
No content
Slide 39
Slide 39 text
トランザクションのミスだけなら エラーになって処理されないだけでは...??
Slide 40
Slide 40 text
ログを漁ってみた結果... ※リクエスト元の管理画面のAPI連携ログの画面
Slide 41
Slide 41 text
ログを漁ってみた結果 ※リクエスト元の管理画面のAPI連携ログの画面 同じエンドポイントのAPIに対して 中身の違う複数のリクエストが同時に来てた ※同じ日の食事が複数投稿/更新されたら、溜めてJobで一気に送ってたみたい
Slide 42
Slide 42 text
APIリクエストの”並列度”を考えてなかった ● そもそも同時にAPIを呼ばれる考えがなかった ● どのようにAPIが叩かれるかを把握してなかった ● 並列度が高い状態でのトランザクションの扱い...?? ○ 何がいつロックされるのかよくわからん
Slide 43
Slide 43 text
● 検索(find)と更新(create / update)の分ける ○ 検索はトランザクション外で実行 ● APIの処理全体を明示的にリトライさせる ○ リトライ時に他のトランザクションのCOMMITを拾い、 DBから検索できるように 解決した際の方針
Slide 44
Slide 44 text
原因 1. トランザクション 2. 並列度の高いAPIリクエスト 3. ActiveJobによる非同期処理
Slide 45
Slide 45 text
No content
Slide 46
Slide 46 text
S3への画像アップロードを Jobに渡している
Slide 47
Slide 47 text
Jobはこんな感じ
Slide 48
Slide 48 text
No content
Slide 49
Slide 49 text
詳細(今回は割愛) https://zenn.dev/t_fukumoto/articles/fe3598ea0a128d93c8cd
Slide 50
Slide 50 text
起きたこと(再掲) ActiveJob::DeserializationErrorが起きまくった
Slide 51
Slide 51 text
● Jobの引数に配列を渡していた ○ タイミングによってGlobalIDのシリアライズに失敗するっぽい ● (エラーで)死んだJobが残る ○ 失敗してもJob自体が破棄されない ○ Sidekiqが律儀にリトライしようとする 原因 参考になるやつ『Rails: Active Jobスタイルガイド(翻訳)』 : https://techracho.bpsinc.jp/hachi8833/2020_09_30/96694
Slide 52
Slide 52 text
はい リトライで盛られていたので、 実際には100回起きたわけじゃなさそう
Slide 53
Slide 53 text
Jobに渡すときのポイント ● 引数に単一のインスタンスを渡す ● JobではSidekiqのリトライ機能を使う(sidekiq6系~) ● discard_onでエラーを指定し明示的にJobを破棄 ● トランザクション内でJobは呼ばない ○ 今回はやらなかったけど、やりがちなので気をつける
Slide 54
Slide 54 text
1. トランザクション 2. 並列度の高いAPIリクエスト 3. ActiveJobによる非同期処理 原因
Slide 55
Slide 55 text
🎉めでたし🎉 ※その後Jobで再度リクエストし直してもらい事なきを得る (重複データはRakeで滅尽🔪)
Slide 56
Slide 56 text
おまけ(FIXME)
Slide 57
Slide 57 text
並列度の高いRspecのテスト....?? 引用『RSpec での悲観ロックのテスト』 : https://tech.actindi.net/2015/11/09/3656032930
Slide 58
Slide 58 text
まとめ
Slide 59
Slide 59 text
● API開発は奥行きを意識する(トランザクション, 並列度) ● APIを叩く側のコンテキストにも関心を持つ ● 検索と更新 / 作成を分け、うまくリトライさせる ● Jobは実行タイミングなど通常の処理との違いを理解する まとめ
Slide 60
Slide 60 text
Thank you!! @terry_i_ MedPeer, inc. - Engineer