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

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

Datastoreのtips

Datastoreのパフォーマンス

  • 対応策
    • エンティティグループを分散させる
      • エンティティグループが個別ならば、同時に何千でも並列処理できる
    • memcacheやTask Queueを活用する
      • リクエスト処理ではTask Queueへの登録だけ行い、処理結果は後で表示する
      • リクエスト処理ではmemcacheに書き込み、Task Queueのバックグラウンド処理でエンティティに保存する

Datastoreにできないことと対応策

select * from PERSON p, ADDRESS a
where a.person_id = p.id and p.age > 25 and a.country = “US”
↓
select from com.example.Person where age > 25 and country = “US”
    • 複数のクエリに分割する
    • List Propertyを使う
  • 集約関数がない(group byできない)
    • count()で全件カウントできない
      • 毎回対象データをすべて取得してループで集計するのは非効率(また最大1000件の制限がある)
      • 集約したい値は、集約用のエンティティを用意して集計
        • sharding counter: 書き込みが集中しないように複数のエンティティに分散して書き込みし、後で集計
        • memcache counter: memcacheに書き込みし、Task Queueでエンティティに保存
    • max()/min()で最大値/最小値を得られない
      • 対象プロパティで降順/昇順でソートして、1件目の値を得る
  • 関数やストアドプロシージャはない
    • toUpper/toLowerなどがない
    • 別のプロパティを設け、toUpper済みの値を入れておく
    • 書き込み時にできるだけ事前処理を行っておくことで、読み込みを高速化できる

そのほかのtips

  • インデックスの更新
    • エンティティの更新処理ではインデックスも更新されるので、インデックスは最小限に抑えないと性能が落ちる
    • GAEではクエリの利用を最小限に抑えた方がよい
  • インデックス更新の回避方法
    • 頻繁に変更されるデータに対し、範囲指定検索するにはどうすればよいか? 変更のたびにたくさんのindexを更新したくない。
    • 検索用のデータを適宜計算して持たせる。
    • オークションサイトの例:価格帯ごとのオークション一覧を表示したい。あるオークションの価格が変化したら、「0〜1000円のオークション」のフラグをそのオークションのLPに追加しておく。範囲指定検索が不要になり、LPに対するequality filterですばやく検索できる
  • 以下のクエリの例では、N+1検索の問題が起きないか?
indexes = db.GqlQuery("SELECT __key__ FROM MessageIndex WHERE receivers = :1", me)
keys = [k.parent() for k in indexes]
messages = db.get(keys)
    • 起きない。indexesを取得する際には1回のクエリが走るが、messagesの取得は個々のキーからentityをハッシュテーブルで直接取得する。個別のクエリは実行されず、高速に処理される。
      • (でも個別にディスクアクセスしないか?)
  • そのほか
    • データの物理的(地理的)位置はコントロールできない

GAEへのマイグレーション

  • プライマリーキーの扱い
    • 単一カラムの場合はそのまま使える
    • 複合キーの場合はentityの親子関係に置き換える
    • N:N用のマッピングデーブル(ジョインテーブル)はList Propertyに置き換える