getとstreamって何が違う?
この記事は、HUIT アドベントカレンダー 2021 の10日目の記事です。
今回は初めての技術系記事です。色々と足りないところはあるかもしれませんが、どうか温かい目で見てください。改善点など大歓迎です!
この記事はFirebaseからPythonでデータを取得する際に用いるget()
とstream()
の違いについて考察したものです。
そもそもDBとは
みなさんは普段どんなDBを使っているのでしょうか。MySQL、PostgreSQL、MongoDBなど様々な種類がありますが、私はまだ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 | *get型* |
1 | *stream型* |
せいぜい少しだけ内部の操作が異なるのかなあとか思ってましたがまさかここまで視覚に訴えかけてくるものだとは……
それでは早速私の稚拙な知識で分析を試みていきます。
分析フェーズ
get型
特徴的なのはやはり27行にわたる出力結果ですが、この27という数字はFirestore上のドキュメント(レコードと同じ)の数です。データを取得するためにあらかじめ投稿しておきました。get型はそのそれぞれについてDocumentSnapshotを作成し、配列型としてdocsに入れてくれているんですね。
さて、DocumentSnapshotとは何なのでしょうか。
大変分かりやすいサイトがありましたので紹介します。
→FirestoreのReferenceとSnapshotの大まかな理解(Tips)
こちらをちゃんと読んでもらえればきっと大まかに分かるんだと思います(私も今はさらっと読みましたが後ほどしっかりと読みます。新情報が入荷次第ここに更新していきたいですね)以下、大変ざっくりまとめた図です。
1
2
3
4
5
6
7
8
9
10Firebaseのオブジェクト群
- Reference
オブジェクトが存在する*場所*
- DocumentReference
- CollectionReference
- Snapshot
オブジェクトの*データ*
- DocumentSnapshot
- QuerySnapshot
- QueryDocumentSnapshotなんとなく分かったのでOKです。
stream型
こちらはget型とは打って変わってシンプルに一行!
まずgeneratorとは何か。generatorと大体セットで出てくるものにiteratorがある、ということくらいなら私でもなんとなく分かっています。
開発中何度も見なかったふりをしましたiteratorは繰り返し?generatorはiteratorを作成する関数?
色々書いてありましたがサッと見ただけじゃよくわからなかったし、もう少しちゃんと調べてみたい気もしたので後日別記事として出すかもしれません。とりあえず今回は保留ということで: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 | *get型* |
1 | *stream型* |
異なったのは末尾から6桁の文字列のみで、それ以外は同じ形式でした。docsの形はあれだけ違うのにfor文で回した途端ほとんど同じものが出力されるってどういうことなんでしょうか。こちらについてはこの記事では検証せず、将来的な展望としておきます。
まとめ
結論:おそらくいちいちドキュメントをSnapshotにするget型はメモリを浪費することになるため、stream型にした方が良いだろう
ということになりました。未検証な部分もいくつかありましたが、それについては追々調べていこうと思います。
技術系記事と打って出た割には雑なものに仕上がった気がしますが大目に見ていただければ幸いです。
明日のアドカレ記事はまたも未定です(3回目)
誰か書いてください!
終わり
getとstreamって何が違う?
https://usk314.github.io/blog_static/2021/12/11/getとstreamって何が違う?/