マルチプットとクエリを使ったGridDBの最適化

このブログでは、GridDBのバッチ操作(マルチプットとマルチクエリー)を使ってアプリケーションのパフォーマンスを向上させる方法を紹介します。

マルチプット

バッチ書き込み操作は、過去に記録されたデータの一括読み込み、エッジデバイスからの複数のセンサー読み取り値の一度の入力、データコレクタ内での入力データの効率化のための意図的なキャッシュなど、さまざまな場面で使用されます。バッチクエリ操作は、マルチコンテナからデータをマージしたり、複数の集約操作を効率的に実施するのに使用されます。

シングルコンテナ

まず最初の例では、シングルコンテナに10,000行を一度に書き込んでみましょう。

blob = bytearray([65, 66, 67, 68, 69, 70, 71, 72, 73, 74])

conInfo = griddb.ContainerInfo("col01",
    [["name", griddb.Type.STRING],
    ["status", griddb.Type.BOOL],
    ["count", griddb.Type.LONG],
    ["lob", griddb.Type.BLOB]],
    griddb.ContainerType.COLLECTION, True)
col = gridstore.put_container(conInfo)

i=0
start =  datetime.datetime.utcnow().timestamp()
while i < 10000:
    row = [str(uuid.uuid1()), False, random.randint(0, 1048576), blob]
    col.put(row)
    i=i+1
print("single put took "+  str(datetime.datetime.utcnow().timestamp() - start) +" seconds")

シングルプットでは上記のようになりますが、マルチプットでこれを最適化することができます。シングルプットで1つの行を10,000回書く代わりに、マルチプットで1,000行を10回書きます。

col = gridstore.put_container(conInfo)
i=0
start =  datetime.datetime.utcnow().timestamp()
rows=[]
while i < 10000:
    rows.append([str(uuid.uuid1()), False, random.randint(0, 1048576), blob])
    if i != 0 and i% 1000 == 0:
        col.multi_put(rows)
        rows=[]
    i=i+1
print("multiput put took "+  str(datetime.datetime.utcnow().timestamp() - start) +" seconds")

シングルプットでは0.92秒だったのに対し、マルチプットでは0.32秒となり、290%もの改善が見られました。

マルチコンテナ

一般的に、マルチプットでマルチコンテナに書き込むのは、Kafkaのようなストリーミングプラットフォームを利用する時で、SinkコネクタやConsumerアプリケーション内で複数のセンサーのデータがコレクションとしてKafkaからフェッチされる場合です。

start =  datetime.datetime.utcnow().timestamp()
for no in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]:
    col = cols[no]
    i=0
    rows=[]
    while i < 10000:
        rows.append([str(uuid.uuid1()), False, random.randint(0, 1048576), blob])
        if i != 0 and i% 1000 == 0:
            col.multi_put(rows)
            rows=[]
        i=i+1

print("single container multi put took "+  str(datetime.datetime.utcnow().timestamp() - start) +" seconds")

マルチコンテナをマルチプットで記述するには、gridstore.multi_put()で、コンテナ名をキーにしたマップを引数にします。

start =  datetime.datetime.utcnow().timestamp()
i=0
entries={}
for no in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]:
    rows=[]
    i=0
    while i < 10000:
        rows.append([str(uuid.uuid1()), False, random.randint(0, 1048576), blob])
        i=i+1
    entries["col0"+str(no)] = rows
gridstore.multi_put(entries)

print("multi container multi put took "+  str(datetime.datetime.utcnow().timestamp() - start) +" seconds")

シングルコンテナのマルチプットでは3.92秒でしたが、マルチコンテナのマルチプットではパフォーマンスが25%向上し、3.13秒となりました。

では、なぜマルチコンテナのテストはこれほど遅く、改善率も高くないのでしょうか。まず第一に、10倍のデータを書き込んでいるということで全体的な差は説明できます。改善率の低下はデータ転送のサイズが理由であると考えられます。最初のシングルコンテナ、シングルプットのテストでは、データ転送サイズが非常に小さいため、レコードあたりのオーバーヘッドが非常に大きくなっていますが、シングルコンテナ、マルチプットではデータ転送サイズが大きく、レコードあたりのオーバーヘッドはすでに小さくなっています。マルチコンテナにマルチプットしても、オーバーヘッドは大きく減少しません。

マルチクエリ

マルチクエリは通常、マルチコンテナのデータを1つのレポートで使用する場合に使用します。

10個のマルチコンテナ

一度にシングルコンテナを実行すると、次のようになります。

start =  datetime.datetime.utcnow().timestamp()
for no in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]:
    col = gridstore.get_container("col0"+str(no))

    query = col.query("select *")
    rs = query.fetch(False)
    while rs.has_next():
        data = rs.next()
print("single container query took "+  str(datetime.datetime.utcnow().timestamp() - start) +" seconds")

gridstore.fetch_all() is used to query multiple containers:

queries=[]
col={}
start =  datetime.datetime.utcnow().timestamp()
for no in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]:
    col[no] = gridstore.get_container("col0"+str(no))
    query = col[no].query("select *")
    if query != None:
        queries.append(query)

gridstore.fetch_all(queries)

for query in queries:
    rs = query.get_row_set()
    while rs.has_next():
        data = rs.next()
print("multi container query took "+  str(datetime.datetime.utcnow().timestamp() - start) +" seconds")

シングルコンテナのクエリにかかった時間は0.34秒、マルチコンテナのクエリにかかった時間は0.28秒と、20%の改善が見られました。

100個のマルチコンテナ

100個のコンテナを1行ずつ返すクエリで検索する場合、マルチプットなしでは次のようになります。

start =  datetime.datetime.utcnow().timestamp()
for no in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9  ]:


    col = gridstore.get_container("col0"+str(no))

    query = col.query("select * limit 1")
    rs = query.fetch(False)
    while rs.has_next():
        data = rs.next()
print("single container query took "+  str(datetime.datetime.utcnow().timestamp() - start) +" seconds")

マルチクエリを使うと、

queries=[]
col={}
start =  datetime.datetime.utcnow().timestamp()
for no in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
           0, 1, 2, 3, 4, 5, 6, 7, 8, 9  ]:

    if col.get(no) == None:
        col[no] = gridstore.get_container("col0"+str(no))
    query = col[no].query("select * limit 1")
    if query != None:
        queries.append(query)

gridstore.fetch_all(queries) 

for query in queries:
    rs = query.get_row_set()
    while rs.has_next():
        data = rs.next()

print("multi container query took "+  str(datetime.datetime.utcnow().timestamp() - start) +" seconds")

シングルコンテナのクエリが0.04秒だったのに対し、マルチコンテナのクエリは0.01秒と、671%もの高速化を実現することができました。

集計

マルチクエリなしでは、

start =  datetime.datetime.utcnow().timestamp()
for no in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]:
    col = gridstore.get_container("col0"+str(no))
    for agg in [ "min", "max", "avg", "count", "stddev"]:
        query = col.query("select "+agg+"(count) ")
        rs = query.fetch(False)
        while rs.has_next():
            data = rs.next()
print("single aggregation query took "+  str(datetime.datetime.utcnow().timestamp() - start) +" seconds")

マルチクエリを使用すると、

queries=[]
col={}
start =  datetime.datetime.utcnow().timestamp()
for no in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]:

    if col.get(no) == None:
        col[no] = gridstore.get_container("col0"+str(no))
    for agg in [ "min", "max", "avg", "count", "stddev"]:
        query = col[no].query("select min(count)")
        if query != None:
            queries.append(query)

gridstore.fetch_all(queries)

for query in queries:
    rs = query.get_row_set()
    while rs.has_next():
        data = rs.next()

print("multi aggregation query took "+  str(datetime.datetime.utcnow().timestamp() - start) +" seconds")

シングル集計のクエリが0.08秒であったのに対し、マルチ集計のクエリは0.06秒と、39%の改善が見られました。

結論

マルチプットとマルチクエリを使うことで、GridDBアプリケーションのパフォーマンスを飛躍的に向上させることができます。どの程度パフォーマンスを向上させることができるかについては注意が必要です。

マルチプット、マルチクエリがパフォーマンスをどの程度向上させることができるかは、プットとクエリのデータの量によって決まります。それぞれのプットとクエリが大量のデータを転送する場合、得られるパフォーマンスの量は減少します。これまでの例を見ると、GridDBのデータブロックサイズ(デフォルトでは64kb)を超えるデータを転送するリクエストでは、パフォーマンスの向上が見込めなくなることが分かりました。また、余分な計算を必要とするクエリ(where-claus、order by、aggregationなど)も、マルチクエリのパフォーマンスに影響を与えます。

If you have any questions about the blog, please create a Stack Overflow post here https://stackoverflow.com/questions/ask?tags=griddb .
Make sure that you use the “griddb” tag so our engineers can quickly reply to your questions.

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください