Windows Azure Storage Service を用いた在庫管理の設計
Azure フォーラム に質問があった在庫管理の設計例を題材にしたWindows Azure Storage Service(主にKVS)による設計例をここで考えていきたい。問題の詳細は上記のリンクを参照してください。
1. 商品在庫管理テーブル
商品コード |
商品名 |
在庫エリアコードAREA01在庫数 |
在庫エリアコードAREA02在庫数 |
在庫エリアコードAREA03在庫数 |
1 |
液晶テレビ |
7 |
0 |
0 |
2 |
テレビラック |
3 |
0 |
0 |
3 |
7.1chオーディオ |
0 |
0 |
100 |
商品在庫管理テーブルを定義します。在庫エリアがこの問題では3箇所になっていますが、より多くの在庫エリアコード(倉庫)があって、商品に必要な属性数との和がAzureテーブルのプロパティ数の上限数(255以内*)に達してしまう場合は、在庫エリアコード毎に商品の在庫管理テーブルを作り、それらから商品毎にまとめた在庫数を管理する集計用の在庫管理テーブルを用意して、注文処理ではその集計用のテーブルを使い在庫を引き当てるようにします。各在庫エリアコードの在庫管理テーブルと集計用の在庫管理テーブルはeventual consistencyで変更を伝搬します。
* https://msdn.microsoft.com/en-us/library/dd179338(v=MSDN.10).aspx
注文処理では、この在庫管理テーブルの商品から在庫量を読み取り、必要な注文数が確保できるかどうかを確認します。必要な注文数が確保できるならば、その注文数を在庫量から減らして在庫管理テーブルを更新します。更新するとき楽観的同時制御(optimistic concurrency)を使い、同一商品の在庫がこの注文処理中に更新があったかどうかをチェックします。もし、同一商品の在庫がこの注文処理中に変更したときは、再度、同一商品の在庫数を読み取り注文数が確保できるかどうかを確認して再処理を試みます。なお、複数の在庫エリアコードのうち、注文数を満たすためにどこの在庫エリアコードを使うかなどのロジックはここでは考えません。
在庫管理テーブルはこのように注文により減算される一方、倉庫における入庫(新規の補填や返品などによる)によって在庫量が増加します(棚卸などでの欠損やその他の理由により倉庫オペレーションで在庫が減ることは例外事項としてここでは考えません)。倉庫オペレーションによる在庫量の増加も同じ在庫管理テーブルに対して注文と同じように更新を行います。更新の際の楽観的同時制御の利用により、入庫処理中に注文があり、在庫数が減産される場合は再度、再処理を試みます。
Webアプリケーションでのカタログの在庫数の表示の変更は、注文での在庫の引当てが成功し在庫管理テーブルの更新された後、また、入庫により在庫管理テーブルの更新が成功した後に表示の変更を行います。
注文および入庫は在庫管理テーブルの非正規化(RDBの第1正規化相当)により、1行に対する更新操作なので操作の実行はACIDトランザクションで保護されます。しかし、ここでの問題は、
(a) ある注文処理中の、別の注文処理、入庫処理の割り込み
(b) 入庫処理中に、(複数の)注文処理の割り込み
にあります。これを楽観的同時制御で解決しています。
2. 注文テーブル
注文コード |
注文日時 |
会員コード |
会員名 |
商品コード |
商品名 |
注文数量 |
取引単価 |
小計 |
在庫エリアコード |
在庫エリアコード... |
注文テーブルを定義とします。1回の注文で複数の商品の注文があったときはそれぞれを1行に分けて挿入します。ここでも基本はRDBの第1正規化です。注文コードをPartitionKeyとすれば、各注文明細はRowKeyにして同一の物理サーバに配置されるので、同一の注文が別の物理サーバに配置されないようにしておくのがいいでしょう。また、同一PartitonKeyを使うことで明細を構成する複数の行を一括して挿入するEntity Groupトランザクションが利用でき性能が向上します(1操作100行まで)。一方、注文の集計用としては、この注文テーブルをそのまま使うよりは、別に集計用に注文テーブルを用意するのがいいでしょう(後述のCommand and Query Responsibility Segregationの一種)。集計用の注文テーブルではPartitionKeyを会員の分類毎、在庫エリアコード毎、注文日毎など、分析の次元毎に用意しておきます。集計用の注文テーブルの作成は、注文データの挿入と同時に集計用の注文データを挿入するため、worker roleのqueueに入れて非同期に処理してもいいですし、後にバッチ的にworker roleを動かして注文テーブルから集計用の注文テーブルを作成してもいいです。いずれしても、集計結果は注文状況をリアルタイムに反映していなくてもよいのでqueueを使った非同期処理の適用な可能です。
3. トランザクションのアーキテクチャ
注文処理では、在庫引当て、決済処理、注文の挿入、出荷処理をこの順序で行います。
最初の在庫引当て(1の処理)で在庫が確保できなければ注文は在庫量不足により受け付られませんので在庫量の変更はありません。在庫の引当てが成功すれば、次に決済処理を行います。決済処理は、外部の決済サービスへのqueueを使った送信を行います。送信が完了すればqueueのトランザクションにより確実に相手に届いていることが保証されます。後は決済サービスの責任範囲ですので、残高不足などは決済サービスの資金回収方法がカバーしてくれるでしょう。次は注文の挿入(2の処理)です。これもWindows Azure Storage Serviceのtableへの行の挿入だけですので、確実に成功するまでリトライすれば実行可能です。最後の出荷処理も、基本的には出荷のためのメッセージ送信をqueueに対して行うだけです。ここでも、出荷はeventualに処理されるでしょうし、もし何らかのエラーが発生したなら、倉庫に対してオペレータが電話などで指示を行えば対応可能でしょう。
4. その他のアーキテクチャ、分析設計上の考慮
上記の設計では2つの点で考慮が必要です。
(a) 実行の配置に関する原則: Windows Azureでいうworker roleの配置の決定法、あるいは、他のパブリッククラウド(Google、Amazonなど)を含めてアプリケーションアーキテクチャの決定法です。これは、Command and Query Responsibility Segregation (CQRS) patternと呼ばれるアーキテクチャです。これは以下のリンクで述べられているものです。
https://vepexp.microsoft.com/techedjapan09/ (LiveIDによるサインインが必要)
複数の依存関係のあるデータ間での実行時の変更の伝搬に関する原則となるアーキテクチャです。
(b) データ定義に関する原則: Windows Azure Storage ServiceのtableはいわゆるKVS(Key Value Store)です。KVSでの非正規化の原則は、上記にあるように、関係モデルにおける第1正規化相当です。また、データ分析設計段階では、「もの」「こと」を識別していきます。複雑なデータの間の依存関係がある場合は、従来の「もの」-「こと」-「もの」などのデータパターンに加えて、高度な分析設計手法が必要となります。今回の在庫管理の例では、それほど複雑な例ではありませんが、高度な分析設計手法として、「高階述語論理」による分析設計法がクラウドのKVS/no SQLでは有効であると考えています。詳細は、以下を参考としてください。
https://www.microsoft.com/japan/events/techdays/2010/session/session.aspx
「T1-401 クラウド コンピューティングのデータ、アプリケーション、開発手法のメタ アプローチ」(2010年4月中の公開予定)
また、「もの」「こと」分析に関しては「アーキテクトの審美眼」が参考となります。
この原則は、複数の依存関係のあるデータ間での設計時の変更の伝搬を解決します。
Comments
- Anonymous
March 26, 2010
お忙しい中、詳細なご説明ありがとうございます。 設計する上で指針となる考え方をお伺いする事ができ非常に勉強になりました。 自分の理解を下記にまとめました。 間違えがございましたらご指摘下さい。 RDBでは、 商品の注文・入庫 → 商品の在庫(量) <非正規化または個別の集計> 注文や入庫と実施された事に着目して在庫数を導き出しすという順番でトランザクションを設計していましたが、キーバリューストアでは、結果どうなるのかに着目しそちらに基軸を置く。 商品の在庫(量) <非正規化> → 商品を注文・入庫 合わせて下記の点も考慮する。 ・在庫の増減が100以内(エンティティグループの上限)に収まるように調整する。 (注文が100を超える場合は分割注文できないか、本当に100を超える同時注文が必要か検討する。) ・在庫の増減時には楽観的同時排他制御を使用する。 ・eventual consistency で変更が伝搬されるような設計を基本とする。 ・本来の処理と異なる分析用の集計が必要な場合も eventual consistency とする。 > 3. トランザクションのアーキテクチャ > > 注文処理では、在庫引当て、決済処理、注文の挿入、出荷処理をこの順序で行います。
- 在庫引当て (エンティティグループで楽観的排他制御。)
- 決済処理 (Azure Queueで決済システムに送信。)
- 注文の挿入 (Azure Table への追加。成功するまでリトライ。)
- 出荷処理 (基本的には Azure Queue。) こちらのトランザクションについて2点質問がございます。 ■質問1. トランザクションをコーディングする時について 1が成功して2、3の途中で例外(アプリケーションエラーやシステムエラー)が発生してしまう可能性を考慮する必要はございますでしょうか。 その場合は、どのような補正処理を検討すれば宜しいのでしょうか。 ■質問2. 4の出荷について 2の決済結果に関わらず直ぐに出荷、決済待って1日後に出荷する等ビジネスルールに合わせて変わりますので「基本的には」というご説明をされているという解釈で宜しいのでしょうか。 Tech・days 2010 早くもの公開予定がなんですね。 凄く楽しみです。 *投稿先を間違えて下記に掲載してしまいました。 恐れ入りますがこちらのリンクの投稿を削除して頂けませんでしょうか。 http://blogs.msdn.com/masayh/archive/2007/08/07/4273815.aspx 宜しくお願い致します。
- Anonymous
March 27, 2010
The comment has been removed - Anonymous
March 28, 2010
設計から実装に関する事まで幅広いアドバイスありがとうございました。 KVSを使う上で疑問が晴れ、視点も増やせたと思います。 もしTable Storageの100以内の制約を超えてしまう要件が発生した場合は、できる限ビジネスロジックの分離を検討します。 2つ目の対応法の一時的な要求の保持をどう設計するかも重要なポイントになると分かりました。アプリケーションで検知できる例外と検知できない例外それぞれ想定エラーに合わせてログや一時保存からのリトライも検討するように致します。 ビジネスルールや制約の上位層、あるビジネスプロセスの中間層に分類し注目する事は、 eventual consitency が必要になりそうかどうかのヒントとなるんですね。 設計時には、こちらも意識して行います。 ますます Tech・days 2010 の公開が楽しみです。 重ね重ねありがとうございました。