When it’s ready.

出来るまで出来ない

gae上でDataStore使わずにmemcacheで転置インデックス作ってみた。

あまりテストをしてないけど、少なくとも100倍くらい速いし、まず時間かけ過ぎで落とされない。これはとても大きい。
以下、コード

def uniqueInverseIndex(feed_id, text):
  try:
    ngram = NgramTokenizer(feed_id, text)
    ngram.setNgramArr(2)
  except:
    response.out.write('NgramTokenizer make instance is faild')
  try:
    for i in ngram.getNgramArr():
      InvIndex = InverseIndex.get_or_insert(
          "_" +i['word_text'], 
          word_text=i['word_text'])
      InvIndex.feed_id.append(db.Text('%s:%s'%(i['feed_id'], i['word_pos'])))
      InvIndex.put()
  except Exception, e:
    logging.info(' inverse index save faild')
    logging.info(e)
    response.out.write(' inverse index save faild')

と言うコードを以下のように変えてみた。

def memCacheInverseIndex(feed_id, text):
  try:
    ngram = NgramTokenizer(feed_id, text)
    ngram.setNgramArr(2)
  except:
    response.out.write('NgramTokenizer make instance is faild')
  try:
    for i in ngram.getNgramArr():
      key = '_' + i['word_text']
      gram = memcache.get(key)
      data = "'%s:%s'"%(i['feed_id'], i['word_pos'])
      if gram is not None:
        gram = "%s, %s]"%(gram[:-1], data)
        memcache.replace(key, gram)
        logging.info('replace memcache key: [%s] data:[%s]'%(key, gram))
      else:
        logging.info('add memcache key: [%s] data:[%s]'%(key, data))
        memcache.add(key, "[%s]"%data)
  except Exception, e:
    logging.info(' inverse index save faild')
    logging.info(key + ' to ' + data + ' and ' + data)
    logging.info(e)

説明

DataStore版

DataStore内に保存する、転置インデックスは、以下のようなモデルであるが

class InverseIndex(db.Model):
  word_text = db.StringProperty(required=True)
  feed_id = db.ListProperty(db.Text)

keynameを使用して、gramのユニーク性を保証する。feed_idは、ListPropertyで、gram(単語)が出現するFeedIDと、そのFeed内で出現するPositionを示したコロン切りの情報が保存される。
Ex) feed_id = [db.Text([1:0, 100:234])

これを、Indexを作成する度に以下の用にしてアップデートしていた。

      InvIndex = InverseIndex.get_or_insert(
          "_" +i['word_text'], 
          word_text=i['word_text'])
      InvIndex.feed_id.append(db.Text('%s:%s'%(i['feed_id'], i['word_pos'])))
      InvIndex.put()

実際他の作業で、DataStore内にデータを突っ込むだけであれば、タイムアウトするほど遅くないのだが、上記の場合呼んできて>アペンド>再び突っ込むと言うことをすると、極端に遅くなる。
100個くらいを一気に処理しようとすると、30秒以上かかってタイムアウトで殺される。
そこで、70個くらいを上限としてたくさん呼ぶという作戦に切り替えたがあまりにも遅すぎるので、memcacheを使うように変えてみた。

memcache版
      key = '_' + i['word_text']
      gram = memcache.get(key)
      data = "'%s:%s'"%(i['feed_id'], i['word_pos'])
      if gram is not None:
        gram = "%s, %s]"%(gram[:-1], data)
        memcache.replace(key, gram)
        logging.info('replace memcache key: [%s] data:[%s]'%(key, gram))
      else:
        logging.info('add memcache key: [%s] data:[%s]'%(key, data))
        memcache.add(key, "[%s]"%data)

memcacheはKeyValueなのでKeyでユニークが保証されている。めんどくさいだけの>get_by_key_nameとか使わなくても、getだけでOK、お手軽ちゃん。新規追加はadd(key, val)で、更新は、replace(key, val) である。

実際memcacheのValueとしては、Int, long, stringが入るポイので、文字列のList風に書いてあるStringとしてセットすることにした。サーチで呼び出す時にeval()してる・・・・

結果

この変更で、80Loopくらいでいっぱいいっぱいだったのに1600loopでも、楽々返す感じになってしまった。これはいい。
Old : New = 80Loopが20sec : 1600Loopが4sec = 4loop/sec : 400loop/sec

問題

転置インデックスは、このままでは、寿命が尽きて消えてしまう。
永続化の為にmemcacheに入ってるデータをDataStoreにどうやって移動するか?

GAE check point

  • keynameには、数字先頭文字が使えない "_"を頭に付けて回避
  • db.Text() はIndexが作られないので軽量且つ速い
  • 30秒以上処理がかかると殺される
  • _apphosting_runtime___python__apiproxy.Waitは、それ以上細かく終えない