発病活動 – Understanding SQL and RDB

もうちょっとデータベースというか RDB なり SQL なりのクエリの実行というものをきちんと理解したい気がする。DuckDB をいじっていた時に感じた物足りなさ、覚束なさや ZetaSQL を読んでわからない感じは、そういうリテラシの不足に起因している気がするので。

データベースといっても B-Tree みたいなストレージの話やトランザクションなど話題は色々あるけれど、ここでの関心はクエリの評価。結局自分にとっての SQL は Dremel や BQ のような OLAP で、そこで SQL という宣言的なクエリがデータを読むコードになって評価される魔法を理解したい欲求がある。

というわけで問題を理解するにあたり必要なことを考える。

  • 教科書を読む。長くてかったるいのでできれば論文を読んで済ませたいが、どうかね。大学のコース資料をいくつかあたって、必要な reading list を作るところからスタートだろうか。
  • しょぼい toy query processor を書く。オンメモリのデータか、ごく単純なフォーマットのファイル (mmap で使えるようなやつ) を相手にいくつかの素朴な代数的クエリが書ければよい。といってもそれなりに難しそうだけど。

しかしデータベースとか何で書けばいいのかね。C++ とか Java とかが堅いのはわかるが、そんなもの課外活動で書きたくない。一方でさすがに Python とか JS とかいう類の題材でもない。モダンプログラマなら Go なり Rust なりで書くんだろうけど。いい機会だから Rust 軽く勉強しようかなあ。全然本題が進まなそうだけど・・・まあ本題が進まなくても Rust を触れるならそれはでいい気はする。query processor 書きは題材ということで。

つまり、昼休みは RDB の教科書なり論文なりを読み、朝は Rust のチュートリアルをやる。これだな。可視化の教科書は読みかけだけど中断。SQL/RDB に詳しくなる方が楽しそうなので。課外活動は中断挫折を気にせずやってきます。

読譜活動 – ZetaSQL – Algebrizer

さて間が空いてしまったが続けます。04:07. ベッドから直行してしまったが、あとで着替えコーヒー休憩が必要。

  • evaluator.h および evaluator_base.h を見ると、評価は PreparedStatement という形で API になっていることがわかる。Execute() というメンバ関数がある。
  • そして internal::Evaluator という非公開クラスに移譲される。
  • このクラスは SQL を Prepare() のち ExecuteAfterPrepare() する。
  • Prepare() は要するに Analyzer すなわち resolved AST を作るんでしょ・・・と思いきや、それに加えて Algebrizer というのがあるな。
  • Algebrizer::AlgebrizeQueryStatementAsRelation() という関数が呼ばれているので見てみるとジャンプ先の algebrizer.cc は突然 5000 具合あってギャーっとなる。関係代数に変換するということなのだろうか・・・。
  • 関数の出力は RelationalOp クラス。これは operator.h に定義されている AlgebraOp のサブクラスで、Yet another tree を構成している。そういえば Duck にも Logical op だの physical op だのがあったね。つまり Algebrizer はいわゆる planner なのだな。
  • で Resolved AST を traverse して Algebra tree に変換していく。たとえば ResolvedTableScan は TableScanOp になる by AlgebrizeTableScan() この辺の方のデザインは duck もだいたい同じで duck と zeta が互いをコピーするのはタイミング的にどう考えても不可能なので、なんか元ネタというか系譜があるな。どこから来たのだろうねこのデザイン/アーキテクチャ。

ここから先は自分の関係代数というかデータベースリテラシ不足で読んでもわからん感。修行して出直す必要がありそう。

肝心の Evaluation はまだ読んでないけど、理解のない状態でこれ以上読み進めても不毛なので Zeta Reading はここまで。

工作活動 – Memory Upgrade

WSL2 で ZetaSQL をビルドしても困らないよう laptop のメモリを交換し 32GB へ。蓋を開けて中を見たら 16GB のメモリが一枚刺さっているだけでスロットが片方開いていた。実は 16GBx2 の 32GB 買うのではなく 16GB 一枚でもよかった疑惑があるが、動作周波数合わせるとかよくわからないし、動いたのでいいです。

起動早々ずいぶん速くなった気がする。プラシボだろうか。ベンチマークとか取っておけばよかったな。

読譜活動 – ZetaSQL – AST, Expression Resolution

05:03. 雨。

さて expression の評価は大物なので、その前にパースの最初のほうをチラっと見て置くべし。

  • gen_parse_tree.py AST はやはり自動生成で, 8000 行。AST への type-safe access (this->_field = (FieldType*)this->children[i] みたいな boilerplate を省く) のと、あとは Visitor の visit を生成している。こういうの Chrome とかだとマクロで頑張ってた気がするけど、生成なのだね。二周目、三周目ならではの過剰感。
  • bison_parser.y – 9800 行! あとちょっとだ! なお duck の y は複数ファイルに分割されているのでてっきり bison にそういう機能があるのかと思っていたが、これは Python で連結しているだけらしい。しかも libpg_query ではなく duck 勢の仕業。どっちがいいのかわからんな。普通に C preprocessor くらいでいいんじゃないか・・・。
  • なお bison_parser.py のコメントは文法の曖昧なケースをきちんと解説している。全体にコメントの質の高さは特筆すべきだね Zeta. 一方 shift-reduce conflict が何だったか完全に忘れているわたくし。
  • それにしても Modern C++ を受け入れると semantic action を割と綺麗に書けるのだね。Bison never dies (in C++ land)…

まあ AST を python で生成している以外は概ね普通の作りであった。Expression の resolution に戻りますかね・・・ resolver_expr.cc

  • Proto をネイティブサポートしており、それがかなりの複雑さを積み増している。型システムの解釈には proto の descriptor が直接使われており、大変ですね感。
  • ResolveFunctionCallImpl: 関数呼び出しを眺めます。なんか flatten といのが特別扱いされているね。
  • named argument あんの?ほんとに?ほんとだ!Zeta 活動が終わったころには BQ マスターになれるかもしれないわたくし。しかし “=>” かよ・・・。
  • ResolveExpresssionArguments – そして引数の解決を見ます。これは変数の解決となんか違うのだろうか・・・というと余計なチェックが入って、あとは変数の解決である。
  • Lambda あんの?BQ にはないっぽいが、そもそも lambda を引数にとれる higher-oder function (“こうかい” が変換できない MS IME) というのは SQL の世界にあるのだろうか・・・そして SQL をパースするのはいいとしてそれを database は実装するのだろうか・・・。やばいな。まあ CREATE FUNCITON とかで作った pure SQL の関数だったらバックエンドの手前でいろいろできるのかもしれないが。
  • 全体的に警告やエラーメッセージにかなりの行数を割いていて引き続きえらいね、というかんじ。他の真面目な静的型言語のパーサも一度ちゃんと読んでみるべきだろうな。
  • 関数は overload できるので signature がある(FunctionSIgnature)。Catalog の Function は list of signatures を持っている。
  • とにかくめちゃめちゃエッジケースがあって厳しい。もはやエッジケースというレベルじゃない。だいたいこういう雑な言語は実行時(実際の評価時)にエラーを出して済ませる動的型の言語であることが多いし、世の中の SQL もそういう実装がそれなりにあるはずだが、Zeta はパースだけするというデザインの都合からそれを静的に解決しようとして複雑さを高めている。その複雑さの集積がこの 8000 行のファイルなわけだな・・・。
  • そしてこの手の巨大なファイルを読むのに VS Code は向いてないな。IDE でクラスの構造化情報が左側の pane に必要。これは VS Code のせいというよりクソデカファイルのせいだが、そういうのと戦う道具というのも必要なのだよ・・・。ただほぼ何も設定してないのに Jump to definition がなんとなく動くのはすごいよね vs code. これは traditional VS C++ toolchain の資産だよな。

はー今日はここまで。式の resolution はもうこのくらいでいいかな。無限の細部があるけど SQL 詳しくないので appreciate できる気がしない。次回は式の評価の参照実装でも覗いてみますわ。

あと宿題としてはドキュメントに目を通すというのもあるね。

読譜活動 – ZetaSQL – SELECT

顔洗ってひげ剃ってコーヒー淹れて 5:10. 読んでくぞ。

  • まず FROM を解決するらしい。主要な入力である NameScope と主要な出力である ResolvedScan を覗いてみるべし。
  • NameScope は基本的には column (ResolvedColumn) を参照するものらしい。まあそうですね。ただ column の field (proto, struct) を参照できるのが特徴的といえる。Duck は field とかどうしてたっけな。
  • ResolvedColumn は globally unique な column_id を持っている。これで何を lookup できるのだろうか、というとテーブルとかなはずだが、あとで出てくることでしょう。
  • ResolvedScan は ResolvedColumn のリストである。
  • ResolveFromClauseAndCreateScan(). いやーコードはクリーンだしエラーメッセージは丁寧だしたいしたもんだわな(エラーメッセージの国際化はゼロだが)。むしろ初代 Dremel SQL がどのくらい雑だったのかに興味が出てくるが、そういうものはオープンソースではないのだった。Dremel はともかく世の中のオープンソースな SQL 実装を比べていくのはパーサーソムリエ的な楽しみがあるかもしれない。
  • FROM clause から現れる “scan” は何種類かあり、代表的なのは “array scan” と “table scan”. あとはサブクエリとか table value function とか。
  • “array scan” はその名の通り array を FROM に指定できるらしい (BQ のリファレンス)。Array の扱い超難しいね・・・そしてここではまだ catalog が出てこない。Catalog どこいった。
  • ResolvePathExpressionAsTableScan() – catalog_->Find() しているね。しかしその周辺が複雑であることよ・・・。ところで ResolvedColumn::column_id は Resolver が (catalog の) Column を lookup するのに使っており、この関数の中で ID が割り振られている。つまり実際の Column の identity を保証するものではなく、FROM にテーブルが出てくるたびにその column に新しい ID が与えられる。(それは catalog のオブジェクトが担保する。)
  • コードのクリーンさを SQL という言語の魔窟さが打ち消しており、すごいね・・・。
  • まあ FROM でテーブルから ResolvedColumn や ResolvedScan が作られるところは見届けたので、次は select list の解決でも眺めていくかな。関心は column name の解決と、式。
  • ResolveSelectColumnFirstPass() – こういうのを読んでて思うこととして, select list は式を評価する仕事と評価結果に名前を付ける仕事を同時にやっており、しかし名前を付ける方は割と SQL が勝手にやっているということだよな。たとえば t.col1 とかやった場合の alias は col1 になることが多いが、こういう naming の挙動は標準化されているのだろうか。とてもそうとは思えないのだが・・・というのも式だけ書いた場合の alias とか GetAliasForExpression() が勝手につけてるんだけど、すごい恣意的じゃない?
  • 式を解決するぞ! ResolveExpr() … は resolve_expr.cc で定義されており、これは 7000 行です。式の解決が 7000 行あるのは特に驚きはないが、これとは別に resolve_query.cc が 8000 行なところに SQL のヤバさがあると言えよう。
  • といったところで時間切れ。ラップトップをでかい画面につなぎたくなってきた。あとメモリ・・・。

読譜活動 – ZetaSQL – ResolveQuery

子が日本語補習校なので土曜は謎の時間があります。土曜まで学校とか気の毒だが、それはさておき読んでくぞ。

  • resolver_stmt.cc: Zeta, Create XXX が異常にたくさんあり、エンタープライズ製品感強し。CREATE MODEL ってなんやねん・・・。(BQ の機能らしい)
  • そういうのは置いといて SELECT 見てくべし。というわけで resolver_query.cc … ギャー 8000 行! きびしい。
  • なお ResolveQuery() の出力は ResolvedScan。こうした resolved AST は スクリプトで生成しているらしい。こいつがまた 8000 行。もうちょっとなんとかならんのかロジックとデータを分離するとかさ (GYP につながる道)。superclass は ResolvedNode. 直列化をサポートしている。データベースは分散システムだからねえ。AST をどっかに送りたいこともあるのでしょう。Catalog とかも分散しないといけないので AST だけ直列化できても不十分だが、そういう usecase は興味深い。AST という木構造をどうやって proto に書いているのかは興味があるが、あとで。
  • ところでドキュメンテーションが割と充実してるね。ただユーザ向けのリファレンスと開発者向けのガイドが混ざってるのが厳しい。分けてもらえんもんかのう・・・。
  • コードを見ていて気付いたが、SELECT はネストできるのか。つまり・・・
$ bazel-bin/zetasql/tools/execute_query/execute_query "select (select 1 + 1) + 1"
+---+
|   |
+---+
| 3 |
+---+

Resolved AST をダンプしてみよう。

$ bazel-bin/zetasql/tools/execute_query/execute_query --mode=resolve "select (select 1 + 1) + 1"
QueryStmt
+-output_column_list=
| +-$query.$col1#2 AS `$col1` [INT64]
+-query=
  +-ProjectScan
    +-column_list=[$query.$col1#2]
    +-expr_list=
    | +-$col1#2 :=
    |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
    |     +-SubqueryExpr
    |     | +-type=INT64
    |     | +-subquery_type=SCALAR
    |     | +-subquery=
    |     |   +-ProjectScan
    |     |     +-column_list=[$expr_subquery.$col1#1]
    |     |     +-expr_list=
    |     |     | +-$col1#1 :=
    |     |     |   +-FunctionCall(ZetaSQL:$add(INT64, INT64) -> INT64)
    |     |     |     +-Literal(type=INT64, value=1)
    |     |     |     +-Literal(type=INT64, value=1)
    |     |     +-input_scan=
    |     |       +-SingleRowScan
    |     +-Literal(type=INT64, value=1)
    +-input_scan=
      +-SingleRowScan

Subquery が “SCALAR” であるという事実は静的に判定されている。複数の row を返すようなケースではどう resolve されるのだろうか・・・。なんとかテーブルをつっこむ必要があるな。この BUILD ファイル に CSV を食わせる方法がひっそり書いてあるので試すと動いた(が、コピペでは動かなかった。あとで bug をファイルすべし。)

$ bazel-bin/zetasql/tools/execute_query/execute_query  --table_spec="data=csv:$(pwd)/sample.csv" "select * from data"
+---+-----+----+
| a | b   | c  |
+---+-----+----+
| 5 | 1.2 | r1 |
| 6 | 1.5 | r2 |
| 7 | 3.3 | r3 |
+---+-----+----+

さて query のネストをしてみましょう。

$ bazel-bin/zetasql/tools/execute_query/execute_query  --table_spec="data=csv:$(pwd)/sample.csv" "select (select CAST(a AS INT64) from data) + 1"
ERROR: OUT_OF_RANGE: More than one element [type.googleapis.com/zetasql.execute_query.ParserErrorContext='\n.select (select CAST(a AS INT64) from data) + 1']

EXPLAIN すると…

morrita@noisia:~/src/zetasql$ bazel-bin/zetasql/tools/execute_query/execute_query  --table_spec="data=csv:$(pwd)/sample.csv" --mode=explain "select (select CAST(a AS INT64) from data) + 1"
RootOp(
+-input: ComputeOp(
  +-map: {
  | +-$col1.2 := Add(SingleValueExpr(
  |   +-value: $col1,
  |   +-input: ComputeOp(
  |     +-map: {
  |     | +-$col1 := Cast($a, ConstExpr(false))},
  |     +-input: EvaluatorTableScanOp(
  |       +-a#0
  |       +-b#1
  |       +-c#2
  |       +-table: data))), ConstExpr(1))},
  +-input: EnumerateOp(ConstExpr(1))))

“ParserErrorContext” といっていたが、EXPLAIN はできている。つまり実行時エラーである。実際 subquery を LIMIT 1 すると成功する。というわけで実行結果が vector か scalar かというのは型の一部ではない、と言えそう。

一方 string と int を + しようとすると・・・

$ bazel-bin/zetasql/tools/execute_query/execute_query  --mode=resolve --table_spec="data=csv:$(pwd)/sample.csv" "select (select a from data LIMIT 1) + 1"
ERROR: INVALID_ARGUMENT: No matching signature for operator + for argument types: STRING, INT64. Supported signatures: INT64 + INT64; UINT64 + UINT64; DOUBLE + DOUBLE; NUMERIC + NUMERIC; BIGNUMERIC + BIGNUMERIC; DATE + INT64; INT64 + DATE; TIMESTAMP + INTERVAL; INTERVAL + TIMESTAMP; DATE + INTERVAL; INTERVAL + DATE; DATETIME + INTERVAL; INTERVAL + DATETIME; INTERVAL + INTERVAL [at 1:8] [type.googleapis.com/zetasql.execute_query.ParserErrorContext='\n\'select (select a from data LIMIT 1) + 1']

Resolve に失敗する。というわけで + の評価には型が存在する as expected. なお parse には成功する。

$ bazel-bin/zetasql/tools/execute_query/execute_query  --mode=parse --table_spec="data=csv:$(pwd)/sample.csv" "select (select a from data LIMIT 1) + 1"
QueryStatement [0-39]
  Query [0-39]
    Select [0-39]
      SelectList [7-39]
        SelectColumn [7-39]
          BinaryExpression(+) [7-39]
            ExpressionSubquery [7-35]
              Query [8-34]
                Select [8-26]
                  SelectList [15-16]
                    SelectColumn [15-16]
                      PathExpression [15-16]
                        Identifier(a) [15-16]
                  FromClause [17-26]
                    TablePathExpression [22-26]
                      PathExpression [22-26]
                        Identifier(data) [22-26]
                LimitOffset [27-34]
                  IntLiteral(1) [33-34]
            IntLiteral(1) [38-39]
  • さて本題に戻り、末端の SELECT を評価するのは ResolveSelect(). これは・・・複雑。だがコメントが丁寧に書いてあって大企業えらいですね。こういうコメント見ると、コードで表現するのが常に正しいのか疑問になってしまうよな。理想的に正しいのはわかるが、現実として。
  • なお Duck も select の resolve はなかなかカオスだったので、そういうものなのでしょう。

読譜活動 – ZetaSQL – Resolver

昨晩遅かったので朝は走るだけ、と思ったら雨。コード読むかな。

  • execute_query 全体的に大げさなコードでいまいちである。
  • execute_query_tool.cc : ExecuteQuery() – が本題だと思われる。
  • まず ParseExpession で ASTNode を作ります
  • … と思ったが AnalyzeStatement()ResolvedNode を作ります、だな。なおこれを呼ぶには Catalog と type factory が必要。AnalyzeStatement() は free function で、謎のコンテクストとかは必要ない。クリーンでえらいね。AnalyzerOptions には arena (!) やら string id pool (interned string?) やら、それなりに context 的なオブジェクトも突っ込まれているらしいが。
  • で内部では結局 ParseExpression なり ParseStatement なりを呼んで、その AST を analyze するらしい。Parser はあとで詳しく見るとして、この AST の段階では catalog とかいらない。つまり型の解決とかは analyze の仕事らしい。ZetaSQL, SQL を statically typed ぽく扱っている節があるのでそれをどうやってるのがが気になるところ。
  • で、Analaysis は Resolver クラスがエントリポイントです。resolver.h は 4000 行くらいあって一瞬ギョっとするが、Android で鍛えた我々は 1 万行以下のファイルではビビらないのである。というかこのての parser とか analyzer とか、だいたいデカくなりがち。型単位で再帰的に解決したりするので。これを visitor とかで小さいクラスに分ければ読みやすくなるというものでもない。なお .cc ファイルは resolver_*.cc に分割されている。これ duck も似たような構成だったな。
  • 複雑さという意味で Resolver の private fields (状態) を眺めると、全然ない。つまりクラスにはなっているが概ね side-effect-free ぽく書かれていることが期待できる。
  • ResolveStatement の signature はこんなかんじ。(この改行をなんとか殺さねばならぬ・・・)
  // Resolve a parsed ASTStatement to a ResolvedStatement.
  // This fails if the statement is not of a type accepted by
  // LanguageOptions.SupportsStatementKind().
  // <sql> contains the text at which the ASTStatement points.
  absl::Status ResolveStatement(
      absl::string_view sql, const ASTStatement* statement,
      std::unique_ptr<const ResolvedStatement>* output);
  • つまり ASTStatement を ResolvedStatement に map している。他もまあ似たような構成になっていることが予想される。常識的な作りでよい。なお Duck もこんなんだったな。彼らの “resolve” は割と雑だったが、Zeta はどうでしょうか。
  • ところで Zeta には LanguageOptions というのがあって、これで言語機能をかなり細かく制御できる。statement の種類を絞ったりとか。これが世界(社内)を統べる SQL の力だが、同時に実装は複雑になるね。

といったところで時間ぎれ。execute_query がいまいちだったので心配していたが、ライブラリ本体はなかなかクリーンぽくて安心。

次回以降への疑問:

  • Query の解決。
  • Parser の実装はどうなってるのか。
  • Catalog はどうやって作るのか。Catalog への副作用は発行できるのか。つまりDDL はどうハンドルされるのか。

読書活動 – Information Dashboard Design Ch 4, Ch 5.

Chapter 4: Fundamental Considerations

Dashboard を作るときって色々気にすべきことあるよね

  • Update frequency – daily, hourly, realtime
  • User expertise – novice to expert
  • Audience size – one person, sharing the same purpose, having different purposes
  • Technology platform – Web, etc.
  • Screen Type – Large to small
  • Data Type – Quantitative Non-Quantitative

だからどうしろという話はこの章にはナシ。

Chapter 5: Tapping Into The Power of Visual Perception

  • The limits of working emory
  • Encoding data for rapid perception
  • Gestalt principles of visual perception

人間の知覚の特性を議論する。たとえば working memory みたいなよく知られた概念・・・がある故に、なぜスクロールがダメかを議論したり、データのエンコード先を分類し、それぞれが何に適しているかを議論したりする。

  • Color – hue, intensity
  • Form – line length, line width, orientation, size, shape, added marks, enclosure
  • Position
  • Motion

これらのうち人間が際を正しく判断できるのは line length と position だけであり、色、大きさ、角度とかは「違う」というのはわかっても「どのくらい違う」は誤解しがち (だから bar chart はダメ)だそうな。たしかに。countour とか色でがんばってるけど、あれは連続的に変化しているからなんとかなっているのだろうか。

Gestalt principle:

  • Proximity
  • Similarity
  • Enclosure
  • Closure
  • Continuity
  • Connection

Gestalt principle は、要するに見えないはずのものが見える人間の知覚の特徴みたいな話。こういうのを生かすと arrangement が subtle でも表示に意味を与えることができる。たとえばわざわざ枠を書かなくても距離を調節することでグルーピングを表現したりとか。

この章はよかった。どう現実に適用していくのか後の章が楽しみ。

読譜活動 – ZetaSQL – execute_query

朝起きるとコンパイラが死んでいた。昼休みにちょこちょことなんとかする試み。

  • メモリが足りないのでしょう、ということで wsl.config で 6GB だったキャップを 12GB に拡大したところビルドは成功。メモリ買うかな・・・。
  • execute_query のバイナリができたので README にあるとおり execute_query “select 1 + 1” を実行したら、なにかが返ってきた。
$ bazel-bin/zetasql/tools/execute_query/execute_query "select 1 + 1"
+---+
|   |
+---+
| 2 |
+---+
  • execute_query –helpfull をするといろいろフラグがある。最近の ABSL flags はフラグを定義しているファイル名が表示されるようになっており、ちょっと便利。 
$ bazel-bin/zetasql/tools/execute_query/execute_query --mode=explain "select 1 + 1"
RootOp(
+-input: ComputeOp(
  +-map: {
  | +-$col1 := Add(ConstExpr(1), ConstExpr(1))},
  +-input: EnumerateOp(ConstExpr(1))))
  • –mode=explain するとこのような実行計画的なのが表示されるのだが、SQL のパーサなのに実行計画とは?というかそもそもなぜ SQL を評価できるのか、不思議といえば不思議。なんらかの参照実装的なのが入っているんだろうけれど。
$ bazel-bin/zetasql/tools/execute_query/execute_query "create table hello (foo integer)"
ERROR: INVALID_ARGUMENT: Statement not supported: CreateTableStatement [at 1:1] [type.googleapis.com/zetasql.execute_query.ParserErrorContext='\n create table hello (foo integer)']
  • テーブル定義すらできないが、そもそも式を評価できる時点で不思議なので別にいいといえばいいです。はい。

次回はもうちょっとコード読みます。

読譜活動 – ZetaSQL

ちょっとばかし ZetaSQL のコードを読んでみるターン。DuckDB しか知らないと Duck 活動してもいまいち自分の SQL 力が高まらないなということで、ガチ勢の SQL パーサを読んでみる。ZetaSQL は Google 社内の SQL 方言をすべて駆逐した本気の二周目 SQL 実装である。これを読めば SQL への理解も高まるというものでしょう。オープンソースへのコードダンプがどのくらいちゃんとしているのかは怪しいが、それは見ればわかるでしょうということで。

  • まずビルドしてみるべく Bazel のインストールからスタート。
  • JDK もいるらしいので apt-get install openjdk-17-jdk
  • ビルドはじまった。WSL2 がんばってくれ・・・
  • ビルドをまちつつコードサイズを調べると・・・
$ find zetasql/ -type f | xargs wc
  ...
  852658  2684718 30878589 total

$ find zetasql/ -type f -name "*.test" | xargs wc
  ...
  408165  1213844 13169468 total

$ find zetasql/ -type f -name "*_test.cc" | xargs wc
 ...
  100019  276858 4024329 total

  • というわけでテストなしで 300k-400k くらい。パーサだけなのに DuckDB よりでかいんじゃないか大企業・・・。なお Java binding があるが、それは数えていない。
  • Python がないといってビルドが失敗している。そして pyenv が入っていない。やれやれ・・・そして Python 3.8+ がサポートされていない!(こんな感じの問題っぽい)ここで breaking change をつっこんでくる Python もどうかと思うが、一体社内の Python バージョンいくつなんだろうな気にしたこともなかったが・・・。
  • ビルドを待ちつつコードで読むか。
  • サンプルを見ると zetasql/public/analyzer.h が入口なのかな。他にもいっぱいファイルあるけど、”Analyzer Famework” を名乗っているので、この AnalyzerOutput に必要なものは全部入っているのかもしれない。しかしもうちょっと E2E なサンプルないかな。
  • と README を読むと execute_query というのがあるらしい。こいつを理解するのがちょうどよい starting point かもしれない。
  • Google C++ はなんか特殊だよな良くも悪くも。新卒とかで入ってきて C++ の、よりによってこんな特殊な dialect をやらされる若者とかちょっと気の毒な気もするが、クラウドとかだと G style の C++ も一定程度は知名度あるのだろうか。たとえば Envoy は Bazel でビルドされる C++ だが、いまちらっと見た感じここまでのヤバさは感じない。ABSL が厳しいのかもしれない。
  • さて Evaluator というのがあって、実際に式や文を評価でいるらしい。しかしこんな実行のコアの部分をフレームワークに握られるとイヤそうだが、どうなのかね。商用製品では別のレイヤでインテグレートしており、これはカジュアルユーザ(とは?)向けなのかもしれないけれど。
  • などといいつつビルドがまったく終わらないこのヤバさ。Duck よりでかいのは間違いない。時間切れにつき今日はここまで。

入門活動 – D3 #2

04:32. 目が覚めてダラダラインターネットして、コーヒーは今日はやめといて、そろそろやってくかな。

  • Learn D3: Scales / D3 / Observable
  • Observable notebook, すべての cell が reactive… というか “observable” なのか。Linear に読んでいくのに向いてない・・・。
  • observablehq/htl: Hypertext Literal
  • 突然 JS の Literal を使って生の SVG を書き始めた・・・D3 って jQuery みたいに SVG 作るものじゃなかったの・・・。しかもリテラルと式展開がネストしまくりで認知負荷高すぎの可読性ゼロ。正気とは思えない・・・。
htl.html`<svg viewBox="0 0 ${width} ${height}" style="max-width: ${width}px; font: 10px sans-serif;">
  <g fill="steelblue">
    ${fruits.map(d => htl.svg`<rect y="${y(d.name)}" x="${x(0)}" width="${x(d.count) - x(0)}" height="${y.bandwidth()}"></rect>`)}
  </g>
  <g fill="white" text-anchor="end" transform="translate(-6,${y.bandwidth() / 2})">
    ${fruits.map(d => htl.svg`<text y="${y(d.name)}" x="${x(d.count)}" dy="0.35em">${d.count}</text>`)}
  </g>
  ${d3.select(htl.svg`<g transform="translate(0,${margin.top})">`)
    .call(d3.axisTop(x))
    .call(g => g.select(".domain").remove())
    .node()}
  ${d3.select(htl.svg`<g transform="translate(${margin.left},0)">`)
    .call(d3.axisLeft(y))
    .call(g => g.select(".domain").remove())
    .node()}
</svg>`

はー・・・。感想:

  • 自分の JS 力が低いのでこまごまとしたところで戸惑いがある。
  • Observable はねーわ。これなしのプレインな D3 を触れるようにしないといけない。つまりフロントエンド基礎知識が必要。
  • D3 やたらたくさん機能があるので、ぜんぜん把握できない。サンプルを写経するなり、なんらかのデータを実際に可視化するなりが必要。

Python や R で EDA するとは違って D3 の可視化はウェブフロントエンドのニッチなのだろうな。たとえばデータサイエンティスト連れてきてほらこれで可視化しろよ、とかいってもできるとは思えない。

D3 の可視化は誰かが何らかの発見をして結論を出した後、その発見・結論を示せる JSON とかを事前に用意しておいて、そいつをかっこよく表示してあげるというもの。インタラクティビティも作者の意図を読者に伝えるためのギミック。

仮に D3 を EDA に使おうと思ったらデータソースをつなぎこむ必要があるので、そういうシステムを作る必要がある。大仕事。(Observable はそういうのを一定程度やってくれるっぽいが。)

自分には Colab で EDA しているときに Altair に足りない機能を D3 で補いたい期待があったが、それは難しそうに見える。できなくはないだろうけど・・・。もう一つの期待は社内 dashboard のしょぼさをドメイン固有なフロントエンドを用意して補う路線で、そっちのほうが(自分のフロントエンド力の無さを差し引けば)現実的かな。


いずれにせよ次にやるべきことは D3 をつかって E2E でなんらかのウェブアプリなりサイトなりを作ることだろうね。あまりアイデアがないなあ。保留しつつそのうち考えます。

脇道活動 – Scripting

Python じゃなく、静的型でかつ大仰じゃない言語ないかな karino2 が F# 使ってるような感じで・・・。

Kotlin Scripting

“kotlin” というコマンドがついてきて、それを使える。ただ依存とかはそんなに便利でもない。仕方ない気もするが・・・。がんばれば Graal とかでバイナリを作れるかもしれない、のはいい気もする。

ただスクリプティング、全然やる気を感じないねえ。おとなしく Gradle 使えということなのだろうな。

Deno

いい機会だから TypeScript でもやりたいな、でも Node で TSC とか日々のやっつけ仕事にはめんどくせーな・・・と眺める。

が、ライブラリが全然ないね。たとえば GCP とか使えないじゃん。Node 資産のない JS, Enthusiast にはともかく自分のような外野がちょろっと使える段階ではなかった。Coding quiz とか解くにはいいかもしれないけど。

Node + TSC

package.json や npm がいるので厳密にはスクリプトではないが、割と ergonomics が洗練されてるので敷居は低い気がする。npm install とかすれば一応コマンドをインストールできるし、おとなしくこれに慣れるのが良いのでは。

ライブラリはいちおうなんでもあるし、VS Code が圧倒的にちゃんと動く言語であるという事実は無視できない。

Scala Script

Kotlin Scripting よりは高機能っぽいが・・・・。そもそも Scala を真面目に覚える気がないのだった。

Python + (mypy, pyre, pytype)

慣れという意味では Python が一番良いのはわかってんだけど・・・。この愛せなさよ。Python とか Jupyter/Colab の外では一行も書きたくない。というのは大げさだけど、なんか全然楽しくないよね statically-typed python. とにかくダサい。型に助けられるより型を助けてあげる感じ。

そして type checker 間での互換性がよくわからない。プロジェクト構成の定番もよくわからん。現代の Python のグチャグチャさは 10-20 年前の Perl のグチャグチャさに通じるものがある。Perl よく知らないので不当にディスってるかもだけど。


こうして総合的にみると Node+TSC だな。D3 やったら触ってみよう、と心の積読へ。

Node 以前は Python が versatility という点で最強であった。つまりダサさに目をつぶればライブラリとかはだいたいあった。今でも Python でしかできないことはそこそこある(ML とか)が、一方で JS にしかできないこと(ウェブまわり)も多く、総合的な versatility という点で Node は Python と互角かそれ以上という感じがするねえ。

Kotlin はアプリ書くにはいいしマイクロサービシズ方面でも便利なのかもしれないけど、Node や Python のような何でもある感は (Java を含めてすら)ない、気がする。メジャー度がちょっとだけ低いよな。

入門活動 – D3.JS

というわけで今朝からは D3.JS でもやってみる。いちおう何ができるか理解して、必要な時に触れるようにしておけるくらい。深入りする予定も具体的に何か作る予定も今のところはなし。

そして今朝は寝坊したので 30 分くらいしかない。starting point さがして終わりだな。

今日はここまで。はー D3 はいいけど Observable がかったりー。しかし添付ファイルのデータに依存しているので他の環境に持っていくのはかったるいのだった。とりあえずこのチュートリアルは Obsevable でやるべし。

そして自分のフロントエンド力がゼロすぎるせいで D3 の NPM パケージを使って実際に何らかの HTML を作る部分ですごい困ってしまいそうだなあ・・・。まあそれは後日ということで。

誤読活動 – A layered grammar of graphics

A layered grammar of graphics

というわけで ggplot2 論文。これは…素晴らしいね。本家 GOG で曖昧だった部分をすべて明らかにしたのみならず(たとえば transformation のよくわからなかった部分は丁寧に解説されている)、整理しなおし拡張している。そして可視化の best practice のようなものにも言及している (ex. “In practice, many plots have (at least) three layers: the data, context for the data, and a statistical summary of the data.”)。コードもある。

一方、当然なら本家 GOG を参照しているのであれを読んでいないと割と意味不明ではある。そういう意味で GOG は Hadley のこの論文を読むための税金だったのだと思えば許せる。

そして読んでいて気付いたが、ggplot2 にあるのに Altair にない機能が普通にあるね。Contour とかないじゃん。これ超欲しいんですけど。

これに限らず、Hadley の可視化に関する知見を学ぶためにこの人の書いた本を読むのは意味がある気がする。

心の積読にしておこう。

さて、これで星空日記から始まった可視化入門の旅は終わった。次にやることを考えないとな。まあ dashboard の本は読むとして、あれだけだと若干退屈なのでもうちょっと他にないか考えようではないか。イメージとしては dashboard の本は私用 laptop にアクセスできない会社の昼休みとかで読み、家ではもうちょっと手を動かす感じにできないかなあ。まあ無理に手を動かさなくてもいいんだけど、画面でないとできない活動、同じ読むでもコード読みとか、そういうかんじの何かをやりたい。Notion にでも書き出して考えるべし。

収録活動、列挙活動 – It Will Never Work In Theory

子の日本語補習校入学式、および子が世話になったデイケアの関係者がなくなった葬式などが控えているが、スーツの下に着るシャツがない!がサイズがわからない!とあたふたしながら適当に Amazon で物品を購入。

プログラム雑談収録、のち雑談してたら夜が更けた・・・。


読む論文探しをしたい。そしてちょっと緩めのを何本か息抜きに読みたい・・・と思っていたら AOSAGreg Wilson がすばらしいサイトをやっていた。その名も: It Will Never Work in Theory! Empirical Software Engineering の論文をレビューしている。このレビューを読んで面白そうなのを選ぼうではないか。いやー全部面白そうだね。Greg Wilson 相変わらずいい仕事してるじゃん。

といっても全部が実際に面白いはずはないので、読んで面白いやつを紹介してまいりましょう。

執筆活動 – m.g.i

Message Passing 書き終わり。そのほかお手紙に返事など。


Windows には最近は scoop という便利インストーラがあると教わり入れてみる。(参考: MacOS ユーザが WSL では無い Windows のコンソール環境を整える – A Day in the Life)

お、いいじゃんと勢い余って duckdb を Windows native でビルドしようとしてみるが、なんかいろいろ足りず挫折。また今度・・・。そして Windows ネイティブ開発しようとするとダウンロードサイズがでかい。つぎ laptop 買うときはでかめの SSD が欲しいかもしれないなあ。

参考リンク

誤読活動 – The Grammar of Graphics

The Grammar of Graphics | SpringerLink

というわけで一通り読んだが・・・わけがわからん。

ハイレベルな主張はそれなりに理解できる。つまり、データの可視化というのは以下のようなステップからできていて、それぞれのステージの仕様を指定の上データをつっこめばグラフができるんだよ、というはなし。 *

  • まず入力は Data. これがなんなのかははっきりしないが、最初のステップ “Variable” ではこのデータを “Varset” というやや奇妙なモデルに変換する。これは雑に Pandas の indexed Series だと思えば良い。つまり、基本的には配列だが、ここの要素に index (ID) がついている。この index のおかげで他の Varset と Join できる。
  • 次のステップは “Algebra” で、ここで実際に join したりする。つまりこの Algebra は関係代数みたいなものである。なんか微妙に関係代数とは違うっぽいが、なぜなにが違うのかもその動機もはっきりしない。実際 Polaris では「我々はオリジナルを無視して関係代数に寄せます」といっている。
  • 次のステップは “Scale”. たとえば線形だったのを log scale に map したりする。
  • 次のステップは “Statistics”. ここで何らかの aggregation を行う。しかしどうやって group by 相当のことができるのかはナゾ。
  • 次は ”Geometry”. Point, Line, Area, Bar, Histobar, Tile, Contour とかを選びます。ここまでパインプラインの上を流れてきたのは Varset だったけど、この出力は Graph です。何が違うのかはナゾ。
  • 次のステップは “Coordinates” です。座標系を選びます。表示したいデータの次数が二次以上なら projection とかもします。
  • 最後が Aesthetics です。これはデータをどの見た目にマップするかを選びます。 x, y, color … このステップを通るとデータは ”Graph” から “Graphics” になります。これで無事描画できます。よかったですね。

よくねえええー。

この論文から Altair 的なものの面影を見ることは、頑張ればできなくもないが、これを読んでなにか可視化システムを実装できる気が全くしない。あまりに細部や具体性がかけている。Polaris paper を読んだ瞬間に「これ我々が使ってるダッシュボードの祖先じゃん!」と全てを理解してショックを受けたのとはだいぶ違う。

この論文が(回顧論文であることを差し引いても)2012 に出ていることを考えると、ひどくない?なんか引用されてる話題も 2002-2004 あたりで止まってるしさあ。を読むともう少し理解が深まる可能性はゼロではないが。個人的には「この著者はイマイチ」ということで打ち止めにしておきたい。重要な(かもしれない)アイデアを、ひどく不可解な形で公開してしまった Research Debt だと言えよう。$30 は無駄だった気もするが, GOG (as its original form) は overrated と結論できたのは良かった。Let’s move on.

こんな意味不明なものから GUI 側では Polaris/Tableau を生み出した Pat Hanrahan, API 側では ggplot2 を生み出した Hadley Wickham はマジ神。


そういうムカついた気分はさておき振り返ると、なぜ BQ でひっこぬいたデータを Altair で可視化する作業にもどかしさがるのか、またなぜ内製の可視化ツールはイマイチに感じるのか、理解が進んだ。

まず BQ+Altair のイマイチさは、Altair の ”Statistics” 機能の足りなさが一つの理由だと言える。GoG の世界において statistics すなわち aggregation はパイプラインの一要素である。SQL でデータを扱う我々は SQL で aggregate したいが、Altair の可視化は SQL から分断されたオンメモリの世界で起こる。オンメモリどころかデータを JSON で直列化する必要から 5000 件みたいなデータサイズの制限がある。aggregate するのは大量のデータから特徴を読み取りたいからなのに、その「大量」が 5000 件で cap されちゃうと厳しい。あと Altair というか Vega 側が色々な aggregation を実装しなおさないといけないのもムダ感がある。

よりハイレベルには, visualization と statistics は切り離せないというのが GoG の洞察なのに、Altair/Vega-Lite はそれを切り離してしまっている。これは R の世界で全てが閉じている ggplot2 にはなさそうな問題に見える。しらんけど。

個人的にはオンメモリで頑張るより Polaris みたいに可視化仕様から SQL を生成して必要なデータ整形を全部済ませるアプローチが Python の世界に来てほしい。Vega-Lite のレイヤを JS じゃなくて Python 側に持ってくるのが一つの方向性だろうけど、そういうことは起こらないだろうなあ・・・。

つぎに社内 dashboarding tool のイマイチさだが、これは可視化仕様の指定に GoG 的な composability が足りてないからだろうね。内製ツールの話をここに書いても仕方ないので細部は省略するけど、もうちょっと頑張ってほしかった。

以上。

文章活動、家庭活動

さて家賃を払わねばならぬ・・・そういえば引っ越して一年たったけど家賃更新なかったな。COVID の影響で禁止されているのだろうか。軽く探した感じ何も見つからなかったが。そういえば東京で一人暮らししていた 10 年間くらい、一度も家賃を引き上げられたことなかったな。今思えばすごい話だね。

銀行ついでに総資産を冷やかすと・・・減ってるね。そういえば株価下がってるというニュースを見た気がする。自国で百万人死んでも株価は上がり続けるのに、遠いユーラシアで戦争になると下がるの、資本主義のろくでもなさを突き付けられる感。

家計簿振り分け。ためなければ一瞬であります。

お手紙返事活動。

さて Message Passing のターンが来たので書くぞ。

昼間コーヒー飲んだせいか気が付いたら 23 時。今日はここまで。

雑用活動

お手紙活動に返事がきて、M1 Mac は Docker 動くしいいですよ、とのこと。ふーんとおもって適当にぐぐると、Docker Mac は仮想化のデフォルトが QEMU だという Reddit の発言を見かける。SO も。マイクロサービシズ!みたいな仕事は開発環境含め全部 Docker に入れてしまえば Mac だろうがなんだろうが好きなのを使えばよいということなのかねえ。

しかしこの話を書いていて WSL2 は Android Studio を使えない(限りなく無理) なことに気がついてしまった。うっかり Bazel とかを使おうとした日には積んでしまうな。Mac でも同程度にダメな予感もするが・・・。

IntelliJ なら最近は JetBrain Gateway というのがあって VSCode Remote 的に JetBrain 製品を使えるようになるっぽいが、Android Studio は当然のように未対応。これが動くとハッピーなのだけどねえ。そのうち対応してくんないかな。


来週の grocery 注文作業。子が生まれる前は一週間のメニューを考えてそれに合わせて買うとかやってたなあ。今は適当に買ってあるものを作るようになったが、適当に作るスキルの高まりにより割と困っていない。Imperfect Foods はイマイチなところもあるが、全体としては納得のできるサービスといえる。

家計簿。しかし前回から transaction が一件しかない。インポートが遅れてるな。

珍しく弟から電話。雑談。長男が中学受験だよ、など。自分の日本の友達でも子が中学受験とか言ってたし、昔と比べ一般化してるのだろうか。自分の卒業した都立高校も少しまえから中高一貫校になったという。そこには知らない日本がある。

少し前、自分の子に見せようと昔の日本の様子の動画(映画の切り貼り)を見たら不思議な気分になった。人々の恰好は古いが、街並みは案外変わっていない、というと誇張だけれど、多くが共通している気がする。しかし自分の脳内日本映像の解像度もだいぶ荒く、ただしく比較できない。

明日は朝から水族館にいくのでさっさと起きて弁当を作らねばならぬ。寝るべし。

運営活動 – m.g.i

昨晩は寝る前にダラダラと YT クッキング動画を見てしまい、結果として朝もシャキッと起きられずダラダラしてしまい、顔も洗ってないのにもう 4:35. 6:15 から走らないといかんので今日は読書はなし。Message Passing の原稿整理でもやります。なお最近観ているのは Pro Home Cooks, Joshua Weissman, Ethan Chlebowski, あとたまに J. Kenji López-Alt です。

終了。月末なことだしお手紙活動でもするか・・・。

終了。もうほとんど時間ないけど、昨日会社で印刷してきた論文のつづき読もうかな。

The Grammar of Graphics | SpringerLink

を買ったのだよ。$30 だから $1/page くらいしているが、この厚い本は読みたくないなということで。

  • 代数系を作るといっているが、ほんまかいな感。いろいろサンプルも怪しい。
  • たとえば 1980 年の人口をあらわす pop1980 という集合と 2000 年の pop2000 という集合を “blend” します、これは UNION 相当です。その結果を可視化するとこうなります、というチャートが pop2020 と pop1980 で別の列の pane に facet されているが、なんで UNION したあとなのに年代で group by できるんだよ!元のデータは year みたいな column なくて pop1980 も pop2000 も別の column じゃん!みたいな。
  • そのあとも scale のところでデータを指数表現に transform しますとかいっているが、可視化の legend がどうやって指数表現に transform された事実を知りえるのかまったくわからない。雑すぎる。まったく実装できる気がしない。可視化の dataflow pipeline をトラバースして検出するの?それとも dataflow の上を流れるデータに metadata つけとくの?

といったところで時間切れ。進まん。