DataStore



DataStoreとは

本記事内のDataStoreは、GoogleAppEngineのデータストアのことを指す。GoogleAppEngineは、ファイルの書き込みが一切できないため、DataStoreを使う以外にデータを永続化する方法はない。短期であればmemcachedに保存することもできるが、memcachedに保存されたデータは保存期間が保証されないため、一時データをキャッシュして高速化(or 負荷軽減)以外には実質つかえない。

DataStoreの特徴

DataStoreの特徴は、スケーラブルであること。データ数が1万件でも1億件でもほぼ同じ時間で結果が返ってくる(らしい)。ただし、通常のRDBでは簡単にできる操作がDataStoreでは非常に重い処理だったり、不可能だったりするので注意。

苦手な処理

  • 件数のカウント
    ⇒件数のカウントは、データ全体を取ってくる処理に近いだけの処理時間が掛かります。
    python-blog-systemでは、1ページ当たりの件数+1を取得して、次のページがあるかどうか判定してます。
  • 後方にあるデータの取得
    ⇒MySQLなどでは、limit 1000,10などと、気軽にかいてしまいますが、DataStoreでは注意が必要です。インデックスが用意されていない項目は1000件目までしか取り出せません。また、インデックスが付いていても、offsetに10000などの、大きな数字を設定するとレスポンスが悪化します。

出来ない操作

  • Joinできない
    ReferencePropertyを使うとデータ間の関連を表現できます。リレーションを実現するためには、あらかじめModelにReferencePropertyを設定する必要があります。
  • 複数のデータに対してDelete、Updateが使えない
    更新するデータ、削除するデータを取り出して、1つずつ更新(put)もしくは削除(delete)するしかない。
  • auto_incrementフィールドを作れない
    いわゆる「連番フィールド」を作れません。欲しければ、「GAE/Python で auto_increment」などを参考に実装してください。
  • 部分属性だけを取り出せない
    RDBのように、属性を指定して取り出すことはできません。必ず SELET * FROM になります。

db.Modelとdb.Expandoとpolymodel.PolyModel

DataStoreには、以下の3つのクラスを継承したクラスのインスタンスを保存できます。

  • db.Model
    あらかじめ定義されたプロパティ(固定プロパティ)しか保存できない。
  • db.Expando
    事前に定義されていないプロパティ(動的プロパティ)も保存できる。
  • polymodel.PolyModel
    モデル間の継承関係を実現できる。動的プロパティには対応しない。本記事では省略。

使用例

db.Modelを使ったBBSプログラムの例を示す。自動生成されるテンプレートに以下の変更を加えた。

  • 5行目
    dbモジュールをインポートした。これでdb.Modelやdb.Expandoを利用できる。
  • 7行目~8行目
    DataStoreに保存されているMessageクラスのデータをすべて取り出し表示。
  • 9行目
    メッセージ入力用のHTMLフォームと、送信ボタンを表示。
  • 14行目
    postで受け取ったデータをDataStoreに保存する。getで渡されたデータも同じ方法で取得できる。
  • 15行目
    元のURIにリダイレクトする。
  • 22行目~23行目
    DataStoreに保存するオブジェクトを定義。

ソースコード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python</p>
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext import db

class MainHandler(webapp.RequestHandler):
def get(self):
for message in Message.all():
self.response.out.write("
<div>%s</div>
"
% message.msg)
self.response.out.write('

<form method="post"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" /><input name="msg" /><input type="submit" /></form>'
)

def post(self):
Message(msg = self.request.get('msg')).put()
self.redirect('/')

def main():
application = webapp.WSGIApplication([('/', MainHandler)],
debug=True)
util.run_wsgi_app(application)

class Message(db.Model):
msg = db.StringProperty()

if __name__ == '__main__':
main()

実行画面

BBS

BBS

Relation

冒頭で述べたように、DataStoreではJOIN演算を使えない。Model同士を結びつけるには、あらかじめ、Model自体にReferencePropertyもしくはSelfReferencePropertyを定義しておく。

  • db.ReferenceProperty
    他のモデルへの参照。モデルA⇒モデルBの参照を作ると、モデルB⇒モデルAへの逆参照も自動生成される。
  • db.SelfReferenceProperty
    同一モデルへの参照。

1:nのリレーション

1対nのリレーションを使った例を示す。先ほどの例との主な差分は以下の通り。

  • 10行目~13行目
    コメントの一覧表示と、コメント投稿用のHTMLフォームの表示
  • 18行目~21行目
    コメントをDataStoreに保存。22行目で、親となるMessageモデルのインスタンスを設定している。
  • 31行目~33行目
    Commentモデルの定義。collection_nameを使って、親モデルから参照できる。10行目参照。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#!/usr/bin/env python
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext import db

class MainHandler(webapp.RequestHandler):
    def get(self, key):
        for message in Message.all():
            self.response.out.write("<div>%s</div>" % message.msg)
            for comment in message.comments:
                self.response.out.write("<div> - %s</div>" % comment.cmt)
            self.response.out.write('<form method="post" action="/%s"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" /><input name="cmt"><input type="submit" value="cmt"></form>' % message.key())
        self.response.out.write('<form method="post"><input type="hidden" name="phpMyAdmin" value="cfc2644bd9c947213a0141747c2608b0" /><input name="msg"><input type="submit" value="msg"></form>')
    def post(self, key):
        if self.request.get('msg'):
            Message(msg = self.request.get('msg')).put()
        elif self.request.get('cmt'):
            Comment(
                cmt = self.request.get('cmt'),
                msg = Message.get(key)
            ).put()
        self.redirect('/')

def main():
    application = webapp.WSGIApplication([('/(.*)', MainHandler)], debug=True)
    util.run_wsgi_app(application)

class Message(db.Model):
    msg = db.StringProperty()

class Comment(db.Model):
    cmt = db.StringProperty()
    msg = db.ReferenceProperty(Message, collection_name = 'comments')

if __name__ == '__main__':
    main()
1:nのリレーション

1:nのリレーション

n:nのリレーション

n:nのリレーションはデータモデルの部分の例だけ紹介する。
以下のソースコードpython-blog-systemで実際に使っているモデルだ。ブログの記事と、タグは多対多の関係になる。

1
2
3
4
5
6
7
8
9
10
class Entry(db.Model):
  title = db.StringProperty(default = "")
  body = db.TextProperty(default = "")
  tags = db.ListProperty(db.Key) #タグのリストを保持
  datetime = db.DateTimeProperty(auto_now_add = True)
class Tag(db.Model):
  tag = db.StringProperty()
  @property
  def entries(self): #実体を持つのではなく、毎回クエリを実行する。
    return Entry.all().filter('tags', self.key()).order('-datetime')

1:1のリレーション

1:1のリレーションは、1:nのリレーションの特殊な形なので、モデルの部分だけ紹介します。GAEでは、モデル内の特定フィールドだけを取り出せないため、頻繁に取り出すモデルから重いデータは除外しておきましょう。

重いModel: 必ず写真をロードしてしまう。

1
2
3
class Person(db.Model):
  name = db.StringProperty()
  picture = db.BlobProperty() #重いデータ

分割例: 写真は必要になった時にロードすればよい。

1
2
3
4
5
6
class Person(db.Model): #Personは軽い
  name = db.StringProperty()

class Picture(db.Model): #写真は必要なときだけ
  person = db.ReferenceProperty()
  picture = db.BlobProperty()

トランザクション

GAE/Python で auto_incrementで、トランザクションを使ったシンプルなプログラムを紹介している。この例では、1つのエンティティに対する読み書きをトランザクション内で実行している。複数のエンティティに対するしょりを、トランザクション内で実行するためには、あらかじめエンティティ同士を関連付けておく必要がある。詳細はトランザクションをご覧ください。


Facebook comments:

comments

目次

Leave a Reply


Get Adobe Flash player