GAE的数据库额度存在3个关键:

  1. Small Op目前是免费的,keys_only=True可以随便用。
  2. get()和get_multi()查询会被自动memcache。
  3. indexed会倍增write Op

NOTE1:提取单条数据,使用get_by_key_name(),而不是fetch(1) / first()

user = User.query(User.username = "tom").first()

替换为

user = User.get_by_key_name("tom")

原方法会消耗1 Fetch Op + 1 Query Op = 2 read Op,修改后,会产生1 Small Op + 1 Read Op,而且这个Read Op会被自动cache。

NOTE2:提取多条数据时,使用keys_only + get_multi()

比如一个表有,我想一次取出N条数据时,常规ORM的写法:

feeds = Feed.query().fetch(N)

每次查询,都会消耗1+N Read Op,为了优化额度,可以修改成:

q = Feed.query()
feeds = ndb.get_multi(q.fetch(N,keys_only=True))

首次查询,消耗1 Small Op + N Read Op,但是在重复查询是,则只消耗1 Small Op + m*N Read Op,m是memcache未命中的概率,理想情况是0。

至于性能,可以参看这里,大概75%缓存命中是性能的分界线。

Memcache hit ratio: 100% (everything was in cache)

  Query for entities:              3755 ms
  Query/memcache/ndb:              3239 ms
    Keys-only query:       834 ms
    Memcache.get_multi:   2387 ms
    ndb.get_mutli:           0 ms

Memcache hit ratio: 75%

  Query for entities:              3847 ms
  Query/memcache/ndb:              3928 ms
    Keys-only query:       859 ms
    Memcache.get_multi:   1564 ms
    ndb.get_mutli:        1491 ms

Memcache hit ratio: 50%

  Query for entities:              3507 ms
  Query/memcache/ndb:              5170 ms
    Keys-only query:       825 ms
    Memcache.get_multi:   1061 ms
    ndb.get_mutli:        3168 ms

Memcache hit ratio: 25%

  Query for entities:              3799 ms
  Query/memcache/ndb:              6335 ms
    Keys-only query:       835 ms
    Memcache.get_multi:    486 ms
    ndb.get_mutli:        4875 ms

Memcache hit ratio: 0% (no memcache hits)

  Query for entities:              3828 ms
  Query/memcache/ndb:              8866 ms
    Keys-only query:       836 ms
    Memcache.get_multi:     13 ms
    ndb.get_mutli:        8012 ms</pre>

NOTE3:尽可能的禁用索引。

  • 为所有不需要的被query()和order()的字段,使用indexed=False

  • 当你插入一条数据时,每个索引字段都会产生write Op,特别当操作对象是ListProperty,会根据list的数量,倍数消耗写配额。

  • 对于一些查询,有些和实际逻辑需求相左,但能大幅节约Op的手段。。

对于下面这个EntryCollect对象

class EntryCollect(ndb.Model):
    published = ndb.DateTimeProperty()
    need_collect_word = ndb.BooleanProperty(default=True, indexed=False)
    key_word = ndb.StringProperty(repeated=True, indexed=False)

in(List)的查询:

keys = EntryCollect.query().order(-EntryCollect.published)
entrys = ndb.get_multi(keys.fetch(PER_PAGE*2, keys_only=True))
new_entry = []
for entry in entrys:
    if keyword.decode('utf-8') in entry.key_word:
        new_entry.append(entry)

list.IN(other_list)的查询:

keys = EntryCollect.query().order(-EntryCollect.published)
entrys = ndb.get_multi(keys.fetch(PER_PAGE*2, keys_only=True))
top_entry = []
for entry in entrys:
    if set(other_list).intersection(set(entry.key_word)):
        top_entry.append(entry)

Boolean的字段:

keys = EntryCollect.query().order(-EntryCollect.published)
entrys = ndb.get_multi(kesy.fetch(CONT*2, keys_only=True))
for entry in entrys:
    if entry.need_collect_word:
        # do something

NOTE4:projected()的利弊权衡

  • 使用projected()的字段,必须被indexed。
  • 使用projected()的查询,算一次small Op。

这里就有个权衡,如果read Op紧张,write Op富裕,那么就可以使用projected()。

NOTE5:使用Memcache

  • Memcache是免费的! Memcache是免费的! 这个必须说两遍,Query太贵了。
  • Query.get()会自动被缓存。
  • 将查询的参数作为key,取md5,查询的结果用json存储起来。
json_data = memcache.get('{}:XXXXXXX'.format(md5sum))
if json_data is None:
# do something....
json_data = json.dumps(data)
memcache.add('{}:Analyse'.format(md5sum), json_data, MEMCACHE_TIMEOUT)

TextProperty 和 StringProperty的区别

  • 在管理后台,你无法添加TextProperty的字段,StringProperty可以。
  • TextProperty无法生成索引,StringProperty可以。
  • StringProperty的最大长度是 1500 bytes。