概要
状況としては以下の様な場合を想定
- 各グループ毎にインクリメンタルなIDを管理する
- グループの設定を管理するテーブルに最新のIDを保存
結論
-
更新式でADDを使ってインクリメントしてIDを生成する
※ただしIDの間が抜ける可能性がある -
IDの抜けが許容できない場合はトランザクションでUpdateとPutする
※Lockを考慮する必要がある
1. インクリメントするやり方
サンプルはGoとguregu/dynamoを使用したコードとなります
var domainConfig DomainConfig
err := configTable.Update(hashkey, target).
Add("CurrentID", 1).
Value(&domainConfig)
if err != nil {
// error処理
}
data.ID = domainConfig.CurrentID
err = dataTable.Put(data).Run()
if err != nil {
// error処理
}
上記の書き方で現在のIDに+1した値が確実に取得できるので、このIDをデータにセットすればグループ毎にインクリメンタルなIDを使うことができる
ただし、取得した後のPut処理が失敗した場合、取得したIDが使われないケースがある
2. トランザクションを使って確実にIDをセットするやり方
DynamoDBのトランザクションはGet系と更新系でしかまとめられないので、Getしてから設定をUpdate、データをPutするところまで全てを同一のトランザクションに入れることはできない
なので条件付きのUpdate処理を使うことでデータが既に更新されている場合は更新しないようにする
以下の様にトランザクションを使ってUpdateとPutを実行し、かつ設定をUpdateする条件にCurrentIDが変更されていない時のみとすればIDが先に更新されてしまった場合はエラーとなるのでIDがずれることはなくなる
ただし、先に他の処理に更新されてしまった場合や、トランザクション処理中にDBアクセスした時はエラーとなるため、リトライ処理を検討する
大量のバッチ処理時にはロックをかけて複数の処理が同時に実行されないような工夫をしないと、大量にエラーとなってしまうので注意する
err := configTable.Get(hashkey, domainID).One(&domainConfig)
if err != nil {
// error処理
}
data.ID = domainConfig.CurrentKey + 1
tx := db.WriteTx() // dbは *dynamo.DB
tx.Update(configTable.Update(hashkey, domainID).
Set(currentID, key).
If("$ = ?", "CurrentID", domainConfig.CurrentID)) // CurrentIDが変更されていない時のみ更新する
tx.Put(dataTable.Put(data))
err = tx.Run()
if err != nil {
// error処理
}