getとstreamって何が違う?

この記事は、HUIT アドベントカレンダー 2021 の10日目の記事です。

今回は初めての技術系記事です。色々と足りないところはあるかもしれませんが、どうか温かい目で見てください。改善点など大歓迎です!

この記事はFirebaseからPythonでデータを取得する際に用いるget()stream()の違いについて考察したものです。


そもそもDBとは

みなさんは普段どんなDBを使っているのでしょうか。MySQLPostgreSQLMongoDBなど様々な種類がありますが、私はまだFirestore DBしか用いたことがありません。DBの操作とかも興味があったりするのでこれから色々扱ってみたいですね。

念のためデータベースというものがよく分からないという方に説明すると、

データベースは、アプリケーションのデータを保存・蓄積するためのひとつの手段です。大量のデータを蓄積しておいて、そこから必要な情報を抜き出したり、更新したりということが柔軟に行えるため、多くのデータを扱うアプリケーションでは欠かすことができません。
(出典:キタミ式イラストIT塾 基本情報技術者 令和03年

とのことです。確かに過去2回のハッカソンにおいてもデータベースがあると無いとでは出来ることが全然違うなーと思ってました。


事の経緯

前回の記事でも紹介しましたがつい先日JPHacksに参加し、私はバックエンドを担当していました。



その際もFirestore DBを利用したのですが、DBからデータを持ってこようとしっかり公式ドキュメントを参照し、言うとおりにコードを書いていたところ、事件が起きました。stream型では上手く行っていたはずのコードがget型では実行されなかったのです。以下、どのように実行しなかったのかを再現したコードを載せます。











get型でも出来てしまいました……

「違う、君じゃない」

開発時はあれほど希望を与えてくれた200番台が、今では失望に変わってしまいました。「アドカレ当日になってまさかの没記事か……?」と背筋が凍りかけましたが、気にせずこのまま突っ走ろうと思います。


結局get()stream()のどっちを使えばいいのか?

無事(?)get型でも実装はできましたが、やはりその過程には違いがあるはずだと踏んで果敢に攻め入ります。

公式ドキュメントに気になる点がありました。

Note: Use of CollectionRef stream() is prefered to get()とありますが、これは他の言語には無い記述です。また、「複数」ではなく「単一」のドキュメントを取得する方法を示す箇所にはこのような記述はありませんでした。

というわけで、「POSTメソッドで登録したmemberの情報を全て取得する」というコードをget型stream型の2パターンで記述したものがこちらです。

違いはdb.collection("members")につけたメソッドのみです。ともにコード内でdocsを出力するようにしています。

以下、上のコードによって出力されたものです。

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
*get型*

docs : [
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA03BE50>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA03BE20>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277E9EF5130>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA03BC40>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA045550>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA0456A0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA045610>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA045040>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA0457C0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA045B20>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA045AC0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA045FA0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA045A90>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA045940>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA1644C0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA1640D0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA1645B0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA1645E0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA1643D0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA164670>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA164700>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA164790>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA164820>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA1648B0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA164940>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA1649D0>,
<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x00000277EA164A60>]
1
2
3
4
*stream型*

docs :
<generator object Query.stream at 0x00000277E9FEFC10>

せいぜい少しだけ内部の操作が異なるのかなあとか思ってましたがまさかここまで視覚に訴えかけてくるものだとは……

それでは早速私の稚拙な知識で分析を試みていきます。


分析フェーズ

  • get型

    特徴的なのはやはり27行にわたる出力結果ですが、この27という数字はFirestore上のドキュメント(レコードと同じ)の数です。データを取得するためにあらかじめ投稿しておきました。get型はそのそれぞれについてDocumentSnapshotを作成し、配列型としてdocsに入れてくれているんですね。

    さて、DocumentSnapshotとは何なのでしょうか。

    大変分かりやすいサイトがありましたので紹介します。
    FirestoreのReferenceとSnapshotの大まかな理解(Tips)
    こちらをちゃんと読んでもらえればきっと大まかに分かるんだと思います(私も今はさらっと読みましたが後ほどしっかりと読みます。新情報が入荷次第ここに更新していきたいですね)

    以下、大変ざっくりまとめた図です。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Firebaseのオブジェクト群
    - Reference
    オブジェクトが存在する*場所*
    - DocumentReference
    - CollectionReference
    - Snapshot
    オブジェクトの*データ*
    - DocumentSnapshot
    - QuerySnapshot
    - QueryDocumentSnapshot

    なんとなく分かったのでOKです。

  • stream型

    こちらはget型とは打って変わってシンプルに一行!

    まずgeneratorとは何か。generatorと大体セットで出てくるものにiteratorがある、ということくらいなら私でもなんとなく分かっています。開発中何度も見なかったふりをしました

    iteratorは繰り返し?generatoriteratorを作成する関数?
    色々書いてありましたがサッと見ただけじゃよくわからなかったし、もう少しちゃんと調べてみたい気もしたので後日別記事として出すかもしれません。とりあえず今回は保留ということで:man-bowing:

    次にQuery.streamについて。これについてはあまりいい情報を得られませんでした。streamは「データの流れ」を意味するみたいなので何となく言ってることは分かるような気がしますが……

    ただ、ネットの散歩中にこんなことが書かれた記事を見つけました。

    node.jsのStreamを使えばメモリを節約することができます。
    出典:node-mysqlとStreamで大量のデータを効率的に処理

    あくまでnode.jsについて言及したものであること、そしてメモリの節約については特に説明がなされていなかった気がするので確証の無い情報ではありますが、もし仮にこれが正しく、かつPythonでも成り立つのであれば公式ドキュメントが「getよりもstreamの方がいいよ!」と言っていたことの裏付けになるかもしれません。

    たしかにget型はドキュメントのそれぞれについてDocumentSnapshotを作成する仕様だったのでドキュメント数が増えれば増えるほど大変そうだなあとは思いました。


他に気になったこと

先ほど2パターンの記述方法でコードを記載しましたが、どちらもdocsという変数を設定した後にfor文で回してdocを取ってきています。実はこちらもそれぞれget型stream型の2パターンについてprint(doc)したのですが、これが面白いことにほぼ一致したんですね。

1
2
3
*get型*

<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x0000019B9F641C10>
1
2
3
*stream型*

<google.cloud.firestore_v1.base_document.DocumentSnapshot object at 0x0000019B9F50F520>

異なったのは末尾から6桁の文字列のみで、それ以外は同じ形式でした。docsの形はあれだけ違うのにfor文で回した途端ほとんど同じものが出力されるってどういうことなんでしょうか。こちらについてはこの記事では検証せず、将来的な展望としておきます。


まとめ

結論:おそらくいちいちドキュメントをSnapshotにするget型はメモリを浪費することになるため、stream型にした方が良いだろう

ということになりました。未検証な部分もいくつかありましたが、それについては追々調べていこうと思います。

技術系記事と打って出た割には雑なものに仕上がった気がしますが大目に見ていただければ幸いです。


明日のアドカレ記事はまたも未定です(3回目)
誰か書いてください!

終わり

Author

Haru

Posted on

2021-12-11

Updated on

2021-12-11

Licensed under

You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.