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

RailsのAPIを普通に動かしたい話/How to run API with Rails normally

RailsのAPIを普通に動かしたい話/How to run API with Rails normally

2021年5月28日(金)に行われたイベント『事業成長を加速させたエンジニアリングのウラ側』の登壇資料です。

API、普通に動かしたいじゃないですか。動かしたいですよね?でも普通って簡単じゃないよね...という話をします。 フィッツプラスでは異なるスキーマのAPI開発を同じRailsアプリケーション上で行っています。 その際に気づいたAPI開発のお作法や罠についてお話します。

- URL: https://medpeer.connpass.com/event/211745/

Bfc42a2fb137897db919bac632c83085?s=128

Teruhisa Fukumoto

May 28, 2021
Tweet

Transcript

  1. RailsのAPIを普通に動かしたい話 2021 / 05 / 28 ~ 事業成長を加速させたエンジニアリングのウラ側 ~ @terry_i_

    MedPeer, inc. - Engineer
  2. Agenda 自己紹介 導入 背景 起きたこと 原因と解決 まとめ

  3. 誰やお前

  4. 福本 晃之 Teruhisa Fukumoto MedPeer Web Developer (2019/10~) f-teruhisa @terry_i_

  5. Twitter #medpeer

  6. 導入

  7. None
  8. 特定保健指導 プラットフォーム開発

  9. 関連技術 / アーキテクチャ 『特定保健指導"フィッツプラス"事業を支えるモノリシック Rails + VIPER Swift アーキテクチャ』: https://tech.medpeer.co.jp/entry/2020/06/22/121153

  10. フィッツプラスのRails API • モノリシックRailsを中心とした構成 • 同じRails上に異なるスキーマのAPIが複数存在する • 各APIスキーマのコンテキストが微妙に異なる ◦ スマホ/Webアプリ,

    社内/外向け, toB/toC etc...
  11. そこでタイトルの話題ですよ

  12. RailsのAPIを普通に動かしたい話

  13. “普通” is 何?? • スキーマ定義通りにレスポンスを返すこと • エラー検知できること、通常動作は検知しないこと • テストが可能なこと、テストしやすいこと

  14. 普通が意外と難しい ※APIを開発している現場のイメージです

  15. • API開発でまんまとハマった罠 • 最低限気をつけといたほうがよさそうな話 • やった工夫とか小さいTipsとか 今日する話「普通を目指すために」 ≠API開発のベストプラクティスの話

  16. 起きたこと

  17. 今回取り上げるサービスのAPI スマホアプリ ⇔ サーバー ⇔ 自社のRails(★)

  18. 1日の食事写真をスマホからアップロード スマホアプリ ⇔ サーバー ⇔ 自社のRails(★)

  19. リリース前 テストもバッチリ書いてるし staging環境での動作検証も問題ない。 通知されたエラーも全部潰したし、 勝ちゲーや!!俺はエライ!!

  20. リリース後に問題が...

  21. 同じエラーの通知が100回鳴る

  22. ユニークなはずのレコードが2つ作られる

  23. None
  24. なんでや

  25. サンプルコード ※プロダクトコードから改変しています

  26. None
  27. None
  28. • トランザクション • 並列度の高いAPIリクエスト • ActiveJobによる非同期処理 この辺の複合的な要因

  29. ちょっとずつ見ていきます

  30. 原因と解決

  31. 1. トランザクション 2. 並列度の高いAPIリクエスト 3. ActiveJobによる非同期処理 原因

  32. 1. トランザクション 2. 並列度の高いAPIリクエスト 3. ActiveJobによる非同期処理 原因

  33. None
  34. あっ(凡ミス)

  35. find_or_create_by 自体がトランザクションを張る トランザクション内でカジュアルに使う󰢃 https://railsguides.jp/active_record_querying.html#find-or-create-by

  36. 解決するなら...?? • find_or_create_byしない(簡単) • トランザクションをネスト(require: true)させる ◦ トランザクションの状態を読み解きづらくあんまやりたくない • エラー時にトランザクションを抜けてリトライさせる

    • (create_withとかでINSERT時にlock外したりしてもOKぽいけど、コード上で ロック状態を読み解かせるのはツライ気がしている) 参考になるやつ『Rails APIドキュメント: Active Recordのトランザクション(翻訳)』 : https://techracho.bpsinc.jp/hachi8833/2020_11_30/101160
  37. 1. トランザクション 2. 並列度の高いAPIリクエスト 3. ActiveJobによる非同期処理 原因

  38. None
  39. トランザクションのミスだけなら エラーになって処理されないだけでは...??

  40. ログを漁ってみた結果... ※リクエスト元の管理画面のAPI連携ログの画面

  41. ログを漁ってみた結果 ※リクエスト元の管理画面のAPI連携ログの画面 同じエンドポイントのAPIに対して 中身の違う複数のリクエストが同時に来てた ※同じ日の食事が複数投稿/更新されたら、溜めてJobで一気に送ってたみたい

  42. APIリクエストの”並列度”を考えてなかった • そもそも同時にAPIを呼ばれる考えがなかった • どのようにAPIが叩かれるかを把握してなかった • 並列度が高い状態でのトランザクションの扱い...?? ◦ 何がいつロックされるのかよくわからん

  43. • 検索(find)と更新(create / update)の分ける ◦ 検索はトランザクション外で実行 • APIの処理全体を明示的にリトライさせる ◦ リトライ時に他のトランザクションのCOMMITを拾い、

    DBから検索できるように 解決した際の方針
  44. 原因 1. トランザクション 2. 並列度の高いAPIリクエスト 3. ActiveJobによる非同期処理

  45. None
  46. S3への画像アップロードを Jobに渡している

  47. Jobはこんな感じ

  48. None
  49. 詳細(今回は割愛) https://zenn.dev/t_fukumoto/articles/fe3598ea0a128d93c8cd

  50. 起きたこと(再掲) ActiveJob::DeserializationErrorが起きまくった

  51. • Jobの引数に配列を渡していた ◦ タイミングによってGlobalIDのシリアライズに失敗するっぽい • (エラーで)死んだJobが残る ◦ 失敗してもJob自体が破棄されない ◦ Sidekiqが律儀にリトライしようとする

    原因 参考になるやつ『Rails: Active Jobスタイルガイド(翻訳)』 : https://techracho.bpsinc.jp/hachi8833/2020_09_30/96694
  52. はい リトライで盛られていたので、 実際には100回起きたわけじゃなさそう

  53. Jobに渡すときのポイント • 引数に単一のインスタンスを渡す • JobではSidekiqのリトライ機能を使う(sidekiq6系~) • discard_onでエラーを指定し明示的にJobを破棄 • トランザクション内でJobは呼ばない ◦

    今回はやらなかったけど、やりがちなので気をつける
  54. 1. トランザクション 2. 並列度の高いAPIリクエスト 3. ActiveJobによる非同期処理 原因

  55. 🎉めでたし🎉 ※その後Jobで再度リクエストし直してもらい事なきを得る (重複データはRakeで滅尽🔪)

  56. おまけ(FIXME)

  57. 並列度の高いRspecのテスト....?? 引用『RSpec での悲観ロックのテスト』 : https://tech.actindi.net/2015/11/09/3656032930

  58. まとめ

  59. • API開発は奥行きを意識する(トランザクション, 並列度) • APIを叩く側のコンテキストにも関心を持つ • 検索と更新 / 作成を分け、うまくリトライさせる •

    Jobは実行タイミングなど通常の処理との違いを理解する まとめ
  60. Thank you!! @terry_i_ MedPeer, inc. - Engineer