スティルハウスの書庫の書庫

はてなダイアリーで書いてた「スティルハウスの書庫」を移転してきました。

トランザクションとエンティティグループ

Datastoreのトランザクション

  • エンティティグループ単位でACIDを保証
    • Bigtableは行単位のACIDしか保証しない。Datastoreではエンティティグループ単位でのACIDを保証している
  • 楽観的排他制御(optimistic lock)を実装
    • エンティティグループのrootエンティティにて、トランザクションの最終コミット時間のタイムスタンプを記録
    • トランザクションの開始時に同タイムスタンプを確認し、コミット時にタイムスタンプを再度確認する
    • RDBの悲観的排他制御(SELECT FOR UPDATE)のようにエンティティをロックしないので、スループットは高いが、競合時のリトライが必要となる(Python版は3回まで自動リトライし、Java版は自動リトライしない)

エンティティグループとは

  • 1つのトランザクションに含めたいエンティティの集まりを示す
  • Datastoreでは、通常のDBと同様に、トランザクションの開始(begin)と終了(commit/rollback)を指示する
    • 同じエンティティグループに属するエンティティについては、ACID特性が保証される
    • 異なるエンティティグループ間では、ACID特性が保証されない
  • エンティティグループを明示的に指定しない場合、個々のエンティティは個別のエンティティグループを形成する
  • エンティティグループは、JDOのowned関係を指定することで、自動的に形成される
    • 例えば、UserとAddress間で親子関係を定義すると、AddressはUserのエンティティグループに属する
    • owned関係にない場合も、明示的に指定できる
    • unowned関係はサポートしていない
      • エンティティグループが個別になるのでACIDを保証できないため

エンティティグループ設計時の注意点

  • 1つのエンティティグループにトランザクション負荷が集中しないように注意する
    • エンティティグループの利用は必要最小限に抑えた方が性能は向上する
  • リレーショナルモデルやオブジェクト指向の関連をそのままあてはめると問題が生じることもある
    • 例:1つのDepartmentと1000のEmployeeがある場合:両者を同じエンティティグループとすると、1つのDepartmentに属する1000のEmployeeのうち、いずれか1つのEmployeeしか同時にトランザクションを実行できない(他はエラーとなりリトライが必要となる)
    • 例:1つのUserと数個のAddressがある場合:1人のユーザーの住所に対して複数のトランザクションが同時実行される頻度は低いため、負荷は集中しない
  • 連番の採番はどうする?
    • 例:大量に書き込まれるメッセージの1つ1つに、書き込み順の連番を振りたい
    • 単純な方法:採番用エンティティのMessageIndexをルートエンティティとし、Messageを子とする
      • MessageIndexとMessageが同じトランザクションで更新処理され、ACIDを確保できる
      • しかし大量のメッセージ書き込みには対応できない
    • 対処方法:採番用エンティティを分散化する
      • ユーザーごとの採番用エンティティUserIndexをルートエンティティとし、Messageを子とする
      • MessageのIDには「タイムスタンプ+ユーザーID+UserIndexで採番した値」を設定する
      • UserIndex単位のトランザクションとなり、大量の書き込みが可能となる
      • タイムスタンプ順でソート可能で、かつユニークなIDとなる
    • 別の対処方法:GUIDベースとする
      • 「タイムスタンプ+長い乱数」など
    • 参照:Google I/O 2008 - Building Scalable Web Apps with App Engine