何の話かというと
Cloud Datastoreに対するQueryは、"Ancestor Query" を使用する事でStrong Consistencyが保証されます。逆に Ancestor Query を使用しなかった場合にどのような現象が発生するのかを雑多にメモしておきます。ここでは、GCEのVMからDatastoreにアクセスする前提で、google.cloudクライアントを使用します。(現在、ndbクライアントはGAE限定なので。)
Ancestor Queryとは?
Cloud Datastoreに格納するEntityは、それぞれに「親」Entityを指定することで、ツリー状のグループを構成します。1つのツリーを「Entity Group」と呼びます。Ancestor Queryは、Queryの検索条件として「Ancestor Key」を指定して、検索範囲をその下にぶら下がったEntityに限定することを言います。(親Entityは、自分と同じKindである必要はありませんので、Entity Gorupには、さまざまなKindのEntityが入り交じる点に注意してください。)
一方、Ancestor Keyを指定しない場合は、Datastoreに格納されたすべてのEntityが検索対象となります。これを「Global Query」と呼びます。
プロパティによるGlobal Query
まず次のスクリプトで1秒ごとにEntityを生成します。EntityのID(Key name string)の他に、'myid' というプロパティに同じIDの文字列を格納します。プロパティ 'timestamp' にはEntityを生成したタイムスタンプを入れておきます。
writer.py
#!/usr/bin/python from google.cloud import datastore import time, datetime, os, uuid project_id = os.environ.get('PROJECT_ID') ds = datastore.Client(project_id) os.remove('/tmp/tmp0') for i in range(1000): myid = str(uuid.uuid4()) key = ds.key('Kind01', myid) ent = datastore.Entity(key) ts = datetime.datetime.now() ent.update({'timestamp': ts, 'myid': myid}) ds.put(ent) with open('/tmp/tmp0', 'a') as file: file.write('%s\n' % myid) print ts, myid time.sleep(1)
実行するとこんな感じ・・・
$ ./writer.py 2016-10-26 00:50:27.589180 36a1fca7-a4f8-4d74-b2e0-5a8521f68cff 2016-10-26 00:50:28.749832 e1cb00a0-9ccc-4558-b7d6-2bb41b10be56 2016-10-26 00:50:29.866750 8245581d-2b0c-4888-90e4-710f683837e9 2016-10-26 00:50:30.950333 b1058f07-3c58-4ad1-8ea1-c4191be2bcbe 2016-10-26 00:50:32.061670 9874503a-7318-4d96-a0ca-78668f736205 2016-10-26 00:50:33.229060 be9fd3f0-d2e6-4e53-a748-4d26b766e7d8 ...
この時、ファイル /tmp/tmp0 にEntityのIDが追記されていきます。
同時に次のスクリプトを実行して、/tmp/tmp0に書き込まれたIDを用いて、'myid' = ID という条件でQuery(Global Query)を実行します。
reader.py
#!/usr/bin/python from google.cloud import datastore import time, datetime, os import subprocess project_id = os.environ.get('PROJECT_ID') ds = datastore.Client(project_id) f = subprocess.Popen(['tail','-F','/tmp/tmp0'], stdout=subprocess.PIPE,stderr=subprocess.PIPE) while True: myid = f.stdout.readline().strip() query = ds.query(kind='Kind01') query.add_filter('myid', '=', myid) ts = datetime.datetime.now() print ts, 'Trying to find ', myid while True: iter = query.fetch() ent, _, _ = iter.next_page() if len(ent) == 0: print 'Failed and retry...' continue ts = datetime.datetime.now() print ts, 'Succeeded.' print ent[0]['timestamp'], myid break
実行するとこんな感じ・・・
2016-10-26 00:50:27.748414 Trying to find 36a1fca7-a4f8-4d74-b2e0-5a8521f68cff Failed and retry... Failed and retry... Failed and retry... Failed and retry... 2016-10-26 00:50:27.948764 Succeeded. 2016-10-26 00:50:27.589180+00:00 36a1fca7-a4f8-4d74-b2e0-5a8521f68cff 2016-10-26 00:50:28.865377 Trying to find e1cb00a0-9ccc-4558-b7d6-2bb41b10be56 Failed and retry... Failed and retry... Failed and retry... Failed and retry... 2016-10-26 00:50:29.066650 Succeeded. 2016-10-26 00:50:28.749832+00:00 e1cb00a0-9ccc-4558-b7d6-2bb41b10be56 2016-10-26 00:50:29.948932 Trying to find 8245581d-2b0c-4888-90e4-710f683837e9 Failed and retry... 2016-10-26 00:50:30.004495 Succeeded. 2016-10-26 00:50:29.866750+00:00 8245581d-2b0c-4888-90e4-710f683837e9 2016-10-26 00:50:31.060321 Trying to find b1058f07-3c58-4ad1-8ea1-c4191be2bcbe 2016-10-26 00:50:31.177282 Succeeded. 2016-10-26 00:50:30.950333+00:00 b1058f07-3c58-4ad1-8ea1-c4191be2bcbe
タイミングによって、Queryに失敗していることがわかります。これは、Global Queryでは、作成直後のEntityが発見されない可能性があることを示しています。(この例では、1秒以内には発見されています。)
(ちなみに、Ancestor Queryだとなぜこのような事が起こらないかというと、Datastoreの実装として、Ancestor Queryが発行された場合は、該当のEntity Groupに対して内部的にデータ同期状態のチェックが入るようになっているからです。Global Queryの場合、検索対象がすべてのEntityになるため、データ同期状態のチェックはコストが高すぎて実行できません。そのため、Eventual Consistencyな検索となります。)
ID指定によるEntityの取得
次のスクリプトは、プロパティではなく、明示的に ID を指定してEntityを取得します。
reader2.py
#!/usr/bin/python from google.cloud import datastore import time, datetime, os import subprocess project_id = os.environ.get('PROJECT_ID') ds = datastore.Client(project_id) f = subprocess.Popen(['tail','-F','/tmp/tmp0'], stdout=subprocess.PIPE,stderr=subprocess.PIPE) while True: myid = f.stdout.readline().strip() key = ds.key('Kind01', myid) ts = datetime.datetime.now() print ts, 'Trying to find ', myid while True: ent = ds.get(key) if not ent: print 'Failed and retry...' continue ts = datetime.datetime.now() print ts, 'Succeeded.' print ent['timestamp'], myid break
こちらを実行すると、次のようになります。
$ ./reader2.py 2016-10-26 00:59:28.266182 Trying to find 2d4ad50f-488c-466d-8273-3533c95de6ed 2016-10-26 00:59:28.378003 Succeeded. 2016-10-26 00:59:28.065682+00:00 2d4ad50f-488c-466d-8273-3533c95de6ed 2016-10-26 00:59:29.422306 Trying to find 1e1b6e94-0697-4a8f-9b2c-96696085e429 2016-10-26 00:59:29.560694 Succeeded. 2016-10-26 00:59:29.267432+00:00 1e1b6e94-0697-4a8f-9b2c-96696085e429 2016-10-26 00:59:30.570010 Trying to find a159836f-cdf5-4148-a305-9b2f01e8f7d0 2016-10-26 00:59:30.679568 Succeeded. 2016-10-26 00:59:30.423487+00:00 a159836f-cdf5-4148-a305-9b2f01e8f7d0 2016-10-26 00:59:31.701340 Trying to find add815ed-7cb9-4e61-8377-38eca8dac14d 2016-10-26 00:59:31.817533 Succeeded. 2016-10-26 00:59:31.571264+00:00 add815ed-7cb9-4e61-8377-38eca8dac14d 2016-10-26 00:59:32.844253 Trying to find 9ce71c69-d5e0-48c1-a065-ec41aad386d3 2016-10-26 00:59:32.919830 Succeeded. 2016-10-26 00:59:32.702581+00:00 9ce71c69-d5e0-48c1-a065-ec41aad386d3 2016-10-26 00:59:33.908359 Trying to find 5c250b18-4b03-45c7-bbc4-c8a703737148 2016-10-26 00:59:33.926057 Succeeded. 2016-10-26 00:59:33.845533+00:00 5c250b18-4b03-45c7-bbc4-c8a703737148
この場合は、Strong Consistencyとなり、かならず、Entityの最新の内容が取得されます。
Ancestor Queryの場合
Entityグループを構成して、親Entityを指定したAncestor Queryを実施する場合は、作成直後のEntityも必ず取得されるはずですが、念のために確認しておきましょう。
ここでは、次のようなEntityグループを構成します。
Kind00: 'grandpa' | | Kind00: 'father' | --------------------- | | Kind01: uuid Kind01: uuid ・・・
Entityを作成するスクリプトは、こんな感じ。(Kind00: 'grandpa' と Kind00: 'father' に対応するEntityは実際には作成していませんが、これは問題ありません。「Parent Key」は実際のところ、Entityグループのローカルインデックスの検索Keyとして使われるだけですので・・・)
writer3.py
#!/usr/bin/python from google.cloud import datastore import time, datetime, os, uuid project_id = os.environ.get('PROJECT_ID') ds = datastore.Client(project_id) os.remove('/tmp/tmp0') for i in range(1000): myid = str(uuid.uuid4()) parent_key = ds.key('Kind00', 'grandpa', 'Kind00', 'father') key = ds.key('Kind01', myid, parent=parent_key) ent = datastore.Entity(key) ts = datetime.datetime.now() ent.update({'timestamp': ts, 'myid': myid}) ds.put(ent) with open('/tmp/tmp0', 'a') as file: file.write('%s\n' % myid) print ts, myid time.sleep(1)
Entityを検索する方はこんな感じ。ここでは、直近の親 ds.key('Kind00', 'grandpa', 'Kind00', 'father') を検索の起点としていますが、もちろんその上の ds.key('Kind00', 'grandpa') を起点にしても構いません。
#!/usr/bin/python from google.cloud import datastore import time, datetime, os import subprocess project_id = os.environ.get('PROJECT_ID') ds = datastore.Client(project_id) f = subprocess.Popen(['tail','-F','/tmp/tmp0'], stdout=subprocess.PIPE,stderr=subprocess.PIPE) while True: myid = f.stdout.readline().strip() ancestor_key = ds.key('Kind00', 'grandpa', 'Kind00', 'father') query = ds.query(kind='Kind01', ancestor=ancestor_key) query.add_filter('myid', '=', myid) ts = datetime.datetime.now() print ts, 'Trying to find ', myid while True: iter = query.fetch() ent, _, _ = iter.next_page() if len(ent) == 0: print 'Faild and retry...' continue ts = datetime.datetime.now() print ts, 'Succeeded.' print ent[0]['timestamp'], myid break
実行結果は次の通りで、予想通り、取りこぼしはありません。
$ ./reader3.py 2016-11-02 07:21:02.236359 Trying to find 6b77f8da-0775-4f77-980e-14c5c59a451f 2016-11-02 07:21:02.392515 Succeeded. 2016-11-02 07:21:01.482085+00:00 6b77f8da-0775-4f77-980e-14c5c59a451f 2016-11-02 07:21:02.634073 Trying to find 3681841d-72e1-43ac-9d91-0dfcbf137713 2016-11-02 07:21:02.662415 Succeeded. 2016-11-02 07:21:02.582529+00:00 3681841d-72e1-43ac-9d91-0dfcbf137713 2016-11-02 07:21:03.682064 Trying to find 164ff921-bf65-4835-9a3b-6c0a5e7948d4 2016-11-02 07:21:03.719449 Succeeded. 2016-11-02 07:21:03.635356+00:00 164ff921-bf65-4835-9a3b-6c0a5e7948d4 2016-11-02 07:21:04.777847 Trying to find eeb6702b-2f4b-4254-8957-1db2ae52a23e 2016-11-02 07:21:04.805840 Succeeded. 2016-11-02 07:21:04.683344+00:00 eeb6702b-2f4b-4254-8957-1db2ae52a23e 2016-11-02 07:21:05.852476 Trying to find a514fe27-37df-4639-b269-5192b926624b 2016-11-02 07:21:05.946797 Succeeded.