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/

Teruhisa Fukumoto

May 28, 2021
Tweet

More Decks by Teruhisa Fukumoto

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  3. 誰やお前

    View Slide

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

    View Slide

  5. Twitter
    #medpeer

    View Slide

  6. 導入

    View Slide

  7. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. そこでタイトルの話題ですよ

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  16. 起きたこと

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. View Slide

  24. なんでや

    View Slide

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

    View Slide

  26. View Slide

  27. View Slide

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

    View Slide

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

    View Slide

  30. 原因と解決

    View Slide

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

    View Slide

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

    View Slide

  33. View Slide

  34. あっ(凡ミス)

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  38. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  43. ● 検索(find)と更新(create / update)の分ける
    ○ 検索はトランザクション外で実行
    ● APIの処理全体を明示的にリトライさせる
    ○ リトライ時に他のトランザクションのCOMMITを拾い、
    DBから検索できるように
    解決した際の方針

    View Slide

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

    View Slide

  45. View Slide

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

    View Slide

  47. Jobはこんな感じ

    View Slide

  48. View Slide

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

    View Slide

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

    View Slide

  51. ● Jobの引数に配列を渡していた
    ○ タイミングによってGlobalIDのシリアライズに失敗するっぽい
    ● (エラーで)死んだJobが残る
    ○ 失敗してもJob自体が破棄されない
    ○ Sidekiqが律儀にリトライしようとする
    原因
    参考になるやつ『Rails: Active Jobスタイルガイド(翻訳)』 : https://techracho.bpsinc.jp/hachi8833/2020_09_30/96694

    View Slide

  52. はい
    リトライで盛られていたので、
    実際には100回起きたわけじゃなさそう

    View Slide

  53. Jobに渡すときのポイント
    ● 引数に単一のインスタンスを渡す
    ● JobではSidekiqのリトライ機能を使う(sidekiq6系~)
    ● discard_onでエラーを指定し明示的にJobを破棄
    ● トランザクション内でJobは呼ばない
    ○ 今回はやらなかったけど、やりがちなので気をつける

    View Slide

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

    View Slide

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

    View Slide

  56. おまけ(FIXME)

    View Slide

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

    View Slide

  58. まとめ

    View Slide

  59. ● API開発は奥行きを意識する(トランザクション, 並列度)
    ● APIを叩く側のコンテキストにも関心を持つ
    ● 検索と更新 / 作成を分け、うまくリトライさせる
    ● Jobは実行タイミングなど通常の処理との違いを理解する
    まとめ

    View Slide

  60. Thank you!!
    @terry_i_
    MedPeer, inc. - Engineer

    View Slide