
March 23, 2011 18:03 by
Sekine
Windows Azure Table のトランザクションについて。
Azure テーブルは、端的には自由度が高いと言える。
その反面、SQLのDBテーブルのように、実質的なスキーマは無く、
また、テーブル間のリレーション(外部制約)も無いため、逆に扱いにくく感じる面もあるでしょう。
Windows Azure Storage Table で、知っておくべき重要な点は、
・一意性を確保するためのキー項目。PartitionKey、RowKey の2つの値で一意。
・レコードのエンティティ(項目)数の上限は、255個まで(キー項目も含む)。
・1レコードのデータ上限は、1Mバイトまで。
・レコードの更新には、Timestamp値による、楽観的同時実行制御が掛かる。
テーブルを利用するにあたっては、データ型も含めて、こんなところでしょう。
複数のテーブル間(ビジネス的な)のリレーション、およびトランザクションについて考えておくべき事項は、
・文字列型は、64KBまで。PartitionKey と RowKeyは文字列型という事。
・RowKey の値により、自動的にソートされる。
・1回のバッチ処理(複数レコードの一斉書込)は、100レコード且つ4Mバイトまで。
・バッチ処理では、Entity Group Transactions(EGT)のトランザクションが利用できる。
・EGTの範囲は、同一のPartitionKey、の同じテーブルに対してのみ。
という事を、常に考えながらテーブル設計をしないといけません。
まず、簡単な説明から
ここで、→ 前回の記事
Azure Storage Tableでオートナンバーを実現する採番テーブル
で、一部利用している所がある。
10レコードを追加した後に、
> context.SaveChanges(SaveChangesOptions.Batch);
を呼び出しています。
SaveChangesメソッドには、Batchオプションをつけて呼び出している。
これは、10レコードを一気に書き込みをしていますが、途中でエラーがあった場合には、
1行も書き込まれない事を意味します。
前回の記事での書き込み例では、RowKeyの重複は無いように作成するので、
途中でエラーが起こることは通常ありえないが、
もし偶然に、他のスレッドからRowKeyが重複するレコードが書き込まれた場合には、
一意制約でエラーとなり失敗します。
業務処理をする上では、RowKeyの重複が無い前提での処理を行う場合には、
偶然的にも、あり得ないように処理をするように心がけるべきです。
次に、インデックスの観点より、
Azure Table を使ったサンプルコードを見ると、
RowKeyの生成に、
DateTime.MaxValue.Ticks - DateTime.UtcNow.Ticks
というのを良く見かけます。
例:RowKey = string.Format("{0:10}", DateTime.MaxValue.Ticks - DateTime.UtcNow.Ticks)
実際には、この後にも文字が続く..
Storage Table は、RowKey の値は、自動的にソートされます。というのを念頭に、
このようにすることで、新しいレコードは上位に来るという事になります。
具体的に言うと、例えば、テーブルに唯一のレコードが存在するとして、
RowKeyは、仮に、{"10文字_店舗コード5桁_店舗名"} と取り決めたとすると、
こんなデータになります。
"0000000001_00001_ショップ●●"
ショップコード"00001"のデータの取得は、
{RowKey抽出式 = SubString(11, 5).CompareTo("00001") >= 0 }.Take(1)
とすることで、最新のデータが取れます。
仮に、店舗登録が同時アクセスなどで、不整合なデータが出来たと仮定すると、
"0000000005_00001_ショップ■■"
"0000000004_00001_ショップ□□"
"0000000003_00001_ショップ△△"
"0000000002_00001_ショップ××"
"0000000001_00001_ショップ●●"
※先頭10文字は分かりやすいように記述してますが、実際は大きな数字です。
こんなデータが上から並んでいることになりますが、
最後に書き込まれたレコードは、"ショップ■■"として見なすことができます。
並び順は、上からなので効率の良い並び順となります。
これは、概念的な事を言ってますので、
たとえば、最新10件が業務に必要なデータとして、常に追加されるという様なケースでも、
利用できます。
溜まった不要データは、後でWorkerなどから削除すれば良いのです。
次に、トランザクションの話をします。
・EGTの範囲は、同一のPartitionKey、の同じテーブルに対してのみ。
という制約があります。
では、実際はどういう事なのかという例として、
2つのエンティティクラスを定義します。
論理的には、JOb(親) <-> Employee(子)のリレーションを考えたテーブルになります。
using System;
using System.Data.Services.Common;
namespace PubsStorageStore.TableEntitys
{
[DataServiceKey("PartitionKey", "RowKey")]
public class JobsEntity
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public DateTime Timestamp { get; set; }
public string job_desc { get; set; }
public int min_lvl { get; set; }
public int max_lvl { get; set; }
}
[DataServiceKey("PartitionKey", "RowKey")]
public class EmployeeEntity
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public DateTime Timestamp { get; set; }
public string fname { get; set; }
public string minit { get; set; }
public string lname { get; set; }
public int job_id { get; set; }
public int job_lvl { get; set; }
public string pub_id { get; set; }
public DateTime hire_date { get; set; }
}
}
RowKeyの定義は、
{"JobIDとして10桁_EmployeeIDとして10桁"}
とします。
JObのレコードを作成するには、
JobsEntity.PartitionKey = "test-pubs1";
JobsEntity.RowKey = experienceDac.CreateNumber2("Job").ToString().PadLeft(10, '0');
..その他データ
Employeeのレコードを作成するには、
employeeEntity.PartitionKey = "test-pubs1";
employeeEntity.RowKey = JobsEntity.RowKey + "_" + experienceDac.CreateNumber2("Employee").ToString().PadLeft(10, '0');
..その他データ
※ID生成には、前回の採番処理を使っています。
1つのJobのAdd
context.AddObject("Employee", JobsEntity);
for 等で、複数のEmployeeのAdd
context.AddObject("Employee", employeeEntity);
書き込み
context.SaveChanges(SaveChangesOptions.Batch);
これにより、
| PartitionKey |
RowKey |
job_desc |
min_lvl |
... |
fname |
minit |
... |
| test-pubs1 |
0000000001 |
入力値 |
入力値 |
... |
|
|
|
| test-pubs1 |
0000000001_0000000001 |
|
|
|
入力値 |
入力値 |
... |
| test-pubs1 |
0000000001_0000000002 |
|
|
|
入力値 |
入力値 |
... |
| test-pubs1 |
0000000001_0000000003 |
|
|
|
入力値 |
入力値 |
... |
| test-pubs1 |
0000000001_0000000004 |
|
|
|
入力値 |
入力値 |
... |
というようなレコードが出来上がります。
具体的には、RowKeyはもっと考慮すべき事になると思いますが・・。
AddObjectでは、両方の書き込み対象として、"Employee"に書き込まれていることで、
EGTを利用することができます。
SaveChangesのバッチ動作は、中途にエラーがあれば、全て無効ということになります。
アプリケーションは、FULL REST設計でなければ、エンティティクラスを用いるでしょうから、
物理的に、Azure Table上の同一テーブルかどうかは、意識しないはずです。
関根:
9c400514-357f-4925-8cca-74df91e6a235|0|.0