こんにちは!ソウゾウのSoftware engineerの@ogidowです
「メルカリShops [フライング] アドベントカレンダー2022」の15日目を担当します。
はじめに
メルカリShopsでは様々なキャッシュバック系キャンペーンを行っています。
一例として、販売手数料キャッシュバックキャンペーンなどがあります。
これらのキャンペーンではソウゾウの担当者が顧客管理システム上で、1件ずつショップに対してキャッシュバック(販売利益の付与)を行う必要がありました。
顧客管理システムから1件ずつオペレーションする方法は、サービスの成長に伴い対象のショップが増加するとオペレーションコストが大幅に増えてしまいキャンペーンの実施が困難になることが予想されます。
そこで、対象ユーザが増加した場合も問題なくキャンペーンを実施できるよう、Google Sheets(以下、スプレッドシート)を利用した販売利益一括付与システムを開発しました。
販売利益一括付与システムの概要
今回紹介するシステムはメルカリShops上のショップに対して、販売利益の付与を一括で行うシステムです。
以下の流れでスプレッドシートを入稿し販売利益の付与を行います。
- スプレッドシートにショップのIDと付与する金額等を記入
- Google Apps Scriptを利用し入力値にバリデーションをかける
- スプレッドシートをGoogle Drive上のフォルダに移動する
- 定期的にスプレッドシートを読み取り、対象Shopに販売利益の付与を行う。
- 読み取りが終わったスプレッドシートは、重複して読み取られないように別のフォルダに移動する。
- 販売利益の付与が全て完了するとSlackに通知を送る
- 1件でも付与に失敗した場合は、Slackへの通知と共にエラーの内容をスプレッドシートに書き込む
細かい仕様や工夫した点について以下で説明していきます。
権限管理
販売利益一括付与システムはその利用権限を絞らないと誰もが自由に販売利益を付与できてしまい、お金を扱うシステムとしてはセキュリティ的に問題があることが容易に想像できます。
そこで、特定の人のみが販売利益を付与できるように制限する必要があります。
今回はシステムが参照するGoogle Drive上のフォルダのwrite権限を特定の人だけに与えることで、業務で必要な人のみが利用できるようにしました。
運用フローとしては以下のようになっています。
- 業務の担当者が入稿用のスプレッドシートを作成しスプレッドシート上で、入力内容のバリデーションを行う。
- バリデーションに問題がなければ権限を持った人にスプレッドシートをチェックしてもらう。
- 問題がなければ、権限を持った人が入稿用のフォルダにスプレッドシートを移動する
Google Apps Scriptを利用したバリデーション
入力用のスプレッドシートはフォーマットが決まっています。
フォーマット通りに記入しなければ、システムがエラーを起こしてしまいます。
もちろん、システム側でシートのバリデーションは行っていますが、Google Apps Scriptを利用してシート側でもバリデーションを実行できるようにしました。
このようにすることで、より早い段階でシステム利用者にフィードバックをすることができます。
スプレッドシートの読み取り
スプレッドシートの読み取りはSalesadjustment と呼ばれる販売利益を扱うサービス上に実装されたAPIが行なっています。
入稿されたスプレッドシートは、GCPのCloud Schedulerによって定期的に起動されるAPIにより読み取られます。
シートの処理に時間がかかり、処理が終了する前に次の定期実行に入ってしまうと同一のシートが重複して処理されてしまいます。
重複を防ぐために処理の初めにまずロック処理を行っています。
ロックはDBのレコードを作成することで表現します。
ロック用のテーブルはtypeというカラムを持っており、typeは販売利益の付与処理で固定されています。
typeカラムはユニーク制約を持っているため、既に同一typeのレコードが存在するとレコードの作成に失敗します。
そのため、レコードの作成に失敗した場合は処理中のプロセスが存在すると判定してそのまま処理を終了します。
スプレッドシートを読み取ったあとはその内容を検証し、問題がなければDBに保存します。
DBへの保存後、以下で説明するCloud Tasksへのenqueue処理を実行します。
Cloud Tasks へのenqueue処理が終了後、次回の実行で同じスプレッドシートが処理されないように処理したスプレッドシートを入稿用とは別のフォルダに移動し、ロック用のレコードを削除します。
販売利益の付与
販売利益の付与では、付与用のDBレコードの保存のほか、実際のお金の移動はメルペイを経由して行うなど関連サービスへのリクエストを行う必要があり、比較的重い処理となります。
処理件数が数件程度なら同期的に実行しても問題ありませんが、数百件、数千件とデータが増えた場合に実行時間が許容できなくなる可能性があります。
また、処理の途中で、他サービスへのリクエストが失敗したりするとリトライの際にスプレッドシートの先頭から処理しなければなりません。
そこで、スプレッドシートを扱う処理では、スプレッドシートの読み取りとバリデーション、スプレッドシートの内容の保存だけを行い、付与用のレコードの作成と関連サービスへのリクエストは非同期化しました。
非同期化にはGCPのCloud Tasks を利用して1付与あたり1タスクとなるようにしています。
Cloud Tasks には、タイムアウトまでに正常なレスポンスを返さない場合はリトライを行う機能があります。
1付与1タスクで処理すると、付与用のレコード作成や関連サービスへのリクエストが失敗しても、Cloud Tasks の仕組みの上で失敗したタスクだけリトライすることが可能です。
冪等性
メルカリShopsではマイクロサービスアーキテクチャを採用しており、販売利益の付与でも複数のサービスが連携して機能を実現しています。
このようなマイクロサービスアーキテクチャでは、一時的なエラー等に対するリトライなどで同じ処理が複数回走ることがあります。そのような場合でも販売利益の2重付与が起こらないように冪等性に関して注意する必要があります。
例えば以下のようなケースが考えられます。
- Cloud Tasksにenqueue する処理が途中で失敗し、処理全体をやり直し、スプレッドシートの同一の行に対して複数CloudTasks が enqueueされた場合
- Cloud Tasks経由で実行された販売利益付与処理で関連サービスへのリクエスト等が失敗した場合
- 同一ショップに対してeventIDが使いまわされた場合
スプレッドシートには一括処理ごとに固有のeventIDと呼ばれる「日付_キャンペーン名」というフォーマットの文字列の入力が必須となっています。
付与処理の際に付与処理の実行時に「${shopID}_${eventID}」(以下、冪等キー)を保存します。
冪等キーに対して、処理済みかどうかのチェックを行うことで重複の実行を防いでいます。
全く同じリクエストが重複されて実行される1と2のケースは、この方法で重複が起こらなくなりますが、3のケースではまだ不十分です。
例えば、以下のように同一のeventIDで金額の異なる付与を実行された場合はどうなるでしょうか。
冪等キー | 金額 | 実行順序 |
---|---|---|
id1_20221101_キャンペーン1 | 100 | 初回の実行 |
id1_20221101_キャンペーン1 | 500 | 2回目の実行 |
初回の実行で、冪等キー「id1_20221101_キャンペーン1」が処理済みとして保存されます。
2回目の実行は冪等キー「id1_20221101キャンペーン1」に一致するデータが存在するため処理済みと判定されてしまいます。
本来なら、「${shopID}${eventID}」の組み合わせは一意となる想定のため、2回目以降で金額等が異なる場合は処理済みではなくエラーとして処理されるべきです。
そこで、冪等キーと一緒に金額等の付与に関わるパラメータを保存し、パラメータのhash値を比較し同一でない場合はエラーとして処理することによって冪等性を担保します。
付与処理の監視と結果の通知
販売利益の付与処理は非同期で行っているので、シート全体を処理し終えたかを判断するための仕組みを用意する必要があります。
Cloud Tasks経由で実行される販売利益の付与タスクでは付与が完了すると、DBに処理が完了したことを保存します。
各タスクの処理状況の監視は別でAPIを用意し、Cloud Scheduler から定期的に実行するようになっています。
Cloud Schedulerから起動された監視用のAPIはDBからステータスが処理中のスプレッドシートを取得し、スプレッドシートに紐づく各タスクの付与状況を確認します。
全てのタスクが成功または失敗のどちらかのステータスであれば、SlackにスプレッドシートのURLと共に成功の通知を投稿します。
1件でも販売利益の付与に失敗しているタスクがあれば、失敗したデータとエラーの内容を記載したスプレッドシートを作成します。
このスプレッドシートはリカバリ処理を行う必要がある場合に役立ちます。
おわりに
ここまで、 販売利益一括付与システムの紹介をさせてもらいました。
個人的には8月にソウゾウにjoinし、初めてのプロジェクトで販売利益一括付与システムの開発をさせていただきました。これまで経験したことのない大規模なマイクロサービスアーキテクチャ上での開発やCloud Tasks、 Cloud Schedulerの設定、冪等性の担保等々かなり学びになりました。
ソウゾウでは、お客様に直接使っていただける機能の開発だけではなくサービスの成長のため社内ツール等も開発しています。
これからも、サービスの成長のため様々な開発に邁進していきたいと思います。
株式会社ソウゾウではメンバーを大募集中です。
メルカリShopsの開発やソウゾウに興味を持った方がいればぜひご応募お待ちしています。
詳しくは以下のページをご覧ください。
Software Engineer
Software Engineer, Site Reliability
Software Engineer (Internship) – Mercari Group (※新卒採用に応募するにはまずインターンへの参加をお願いしています。)
またカジュアルに話だけ聞いてみたい、といった方も大歓迎です。こちら の申し込みフォームよりぜひご連絡ください!