本記事の位置付け
下記読書会のための要約です。
課題図書
※3/27に日本語版が出るようです。今後は日本語版の参照もOKにしながら、読書会は英語で続けていく予定です。より幅広い方にご参加いただけるようになるといいなぁ。
今回の範囲
Chapter8
- Batch Transformation
より (Distributed joins 〜 ETL, ELT and data pipelines は前回やったので、次の箇所から)- SQL and code-based transformation tools
- SQL is declarative...but it can still build complex data workflows
- Example: When to avoid SQL for batch transformations in Spark
- Example: Optimizing Spark and other processing frameworks
おまけ
データ分析の学校の課題をやりながら、処理待ち時間に今回の箇所を音読&翻訳した Space。
おまけ②
今回たびたび出てくるSQL、英語での発音は「しーくえる」。
(えすきゅーえる って言ってる英語話者を見たことがない)
記述要領
本文中の [ ] 内の文言、および以下の形式での注記は訳者によるコメントです。
知れてよかった気づきポイントを記載
本編を理解するのにちょっと前提知識が必要と思えた場合に、その部分を補足する内容を記載
要約
全体のポイントを10文字で要約するなら「SQLばかにすんなよ」、もう少し granular に要約すると
- SQL : 簡単に使えて、最適化を自動でやってくれる
- Spark(や、その他のコードベースの言語) : SQLよりいろんなことができてパワフルだけど、最適化を自分で考えてやらなきゃいけないのでそのぶん大変
- 両方のいいところを組み合わせて使ってくれよな
…といったところでしょうか。
SQLとコードベースのデータ加工ツール
現時点では、SQLベースのデータ加工ツールとSQLベースではないデータ加工ツールの差分は人工的なもの[原文では synthetic] だ。
Hadoop に対する Hive が登場したことにより、SQLはビッグデータの世界で第一級の住民になりました。
Hadoop - 分散並列処理によりビッグデータを管理・処理するためのフレームワーク。
Hadoopフレームワークの根幹をなすのがHDFS(Hadoop Distributed File System)というファイルシステム。これは構造・非構造含めあらゆる構造のデータを保管できるファイルシステムであり、本来はコマンドやAPIを使ってアクセスする。
Hive - HDFS上の特定のディレクトリとその配下のファイルをデータベースのテーブルのようにみなし、SQLでアクセスできるようにしたクエリツール。
要するに、ビッグデータはそもそも(RDBのように)SQLでアクセスすることを想定したデータのつくりにはなっていないが、分析に使うデータである以上「SQLでアクセスしたい」という要請は強い。そのため、Hiveをはじめとする様々な技術が「そもそもSQLを想定して作っていないデータに対してSQLをたたけるようにする仕組み」を提供してくれている。
たとえば Spark は Spark SQL [というSpark上で SQL を扱えるようにする仕組み] を初期から搭載していたし、Kafka, Flink, Beam などの ストリーミング用のフレームワークも、他の様々な機能に加えてSQLをサポートしている。
Kafka, Flink, Beam はいずれも、オープンソースでビッグデータを高速に処理するフレームワークの名前。
Kafka - https://kafka.apache.org/
Flink - https://flink.apache.org/
Beam - https://beam.apache.org/
なのでより適切なのは、「SQLしかサポートしていないツール」と、「様々な汎用的な機能の中でSQLをサポートしているツール」を比較することだろう。
...と言いながら、この先の本文で「SQLしかサポートしていないツール」と「様々な汎用的な機能の中でSQLをサポートしているツール」の比較という論点は出てこない。
むしろ、汎用ツールの中で SQL を使うのか、SQL 以外の方法を使うのかという論点になっている。
SQLは宣言的だ、しかし複雑なデータのワークフローを作ることもできる!
SQLが「手続き的でない」という理由で却下されるケースがよくある。
これは技術的には正しい [原文は technically correct] 。
technically は、「厳密には」と訳されることも多いが、この文脈では「技術的観点では」とした方が合いそう??
SQLでは、書き手が集合論的な文法でどのような結果を得たいのかを宣言すると、そのために必要な手続きをコンパイラやオプティマイザーが決めてくれる。
この点は考えてみれば当たり前なんですが、SQLの裏でそんなことが起こっていると意識したことがなかったです。
でも、これがあるからSQLを書いた瞬間に実行計画を見たりすることができるんですね。(たぶん)
SQLが手続き的な言語でないという理由で、複雑なデータパイプラインを作れないと主張する人もいるが、これは正しくない。
SQLは、一般的なテーブルの表現や、SQL スクリプトやオーケストレーションのツールを使うことで、複雑なDAGを効率的に作ることができる。
DAG - 非巡回有向グラフ とデータ処理の関係がわかりやすくまとまっていそうだったのは以下のサイト。
https://gihyo.jp/book/pickup/2017/0080?page=2
もちろん SQL には限界はあるが、SQL を使えばもっと簡単かつ効率的にできるようなことを、エンジニアが (素の)Python や Spark を使って(わざわざ難しく)実装しているケースはよくある。
これらのトレードオフについて理解を深めるために、Spark と SQL [の使い分け] について具体例を見ていこうぜ!
具体例:Spark のバッチ処理で、SQLを避けるべきなのはいつか
Spark SQL やその他の SQL をサポートするツールを使わずに、素の Spark や PySpark [のSQLを使わない構文] を使うべきかどうかを決めたいときは、以下の3つの問いを自問してみるとよい。
- そのデータ加工を SQL でやるのはどのくらい難しいか?
- そのデータ加工を SQL でやった場合のコードは、どのくらい読みやすくメンテナンスがしやすいか?
- そのデータ加工のコードの一部を、組織内で共有するカスタムライブラリとして共有する必要があるか?
ポイント1について
Spark のコードでできる大抵のデータ加工は、SQL を使えば相当シンプルにできる。
一方、そのデータ加工がSQLでは実現できなかったり、SQL でやろうとするとあまりに不器用 [原語:awkward] になる場合は、素の Spark を使う方がいいでしょう。
素の Spark を使うべき加工の具体例として、本文では word stemming = 単語の語幹を取り出す処理が紹介されている。
例えば changing, changed, change はすべて共通の change という語幹を持っているが、このように、単語の変化形から語幹を取り出す処理を word stemming という。
個人的には、ここまで複雑な処理ではなくても、例えばカテゴリカルな変数の Dummy 化なども、SQLでやるのはけっこうしんどい部類に入ると思いました。
ポイント2について
ポイント1 とほぼ重なる。 word stemming を SQL でやろうと思ったら、そのコードって読みやすくもメンテしやすくもないよね。
ポイント3について
SQL の主要な制約のひとつは、SQL には ライブラリとか、コードの再利用可能性といった概念がないことだ。
例外として、いくつかの SQL エンジンは UDF [User Defined Function = ユーザー定義関数] をデータベース内のオブジェクトとして保存することをサポートしている。
しかし、UDF はデプロイを管理する外部の CI/CD ツールの助けなしに Git リポジトリにコミットすることができない。
さらに、SQLにはより複雑なクエリに対する再利用可能性という概念がない。
もちろん、再利用可能なライブラリは Spark や PySpark では簡単に作れる。
SQL を再利用可能にするふたつの方法
SQLをもしもリサイクルするとしたら、2つの道があることを追記しておこう。
1. 結果の再利用 - DBのコミット、ビュー
SQL クエリをテーブルにコミットしたり、ビューを作ることによって、SQLの結果を再利用することが可能である。
ビューにも色々あるが Materialized View のことを言っているのかな
このプロセスは Airflow のようなオーケストレーションツールで最もよく扱うことができ、下流のクエリは [そのクエリが使うマテビューを作る] 先行クエリの処理が完了次第動き出すことができる。
2. DBT (Data Build Tool) を使った SQL 文の再利用
DBTでは、カスタマイズが容易な SQL のテンプレートにより、SQL文の再利用を促進している
テンプレがあるのと再利用ができるのはちょっと意味合いが違う気がするが…具体的なツールと使い勝手がわかるともう少しイメージが湧くのですが。。。
具体例:Spark および他のデータ処理のフレームワークの最適化
Spark の信奉者 [原文では acolytes] は、SQLはデータの処理についてのコントロールが [書き手側に] 渡されないことに不満を言う。 SQLエンジンは書かれた文をもとに、そいつを最適化して、処理のステップに翻訳することをやってくれるからな。(実践の中では、最適化はコンパイルの前に行われることもあるし、後に行われることもあるし、両方の場合もあります。)
その不満は理にかなっているが、副作用もある [原文では a corollary exists]。
Spark やその他のコードをたくさん書くようなフレームワークは、SQLベースのエンジンでは自動でやってくれるような最適化を、コードの書き手が責任を持って取り扱わなければならない。
Spark の API はパワフルだが複雑なので、順序を並べ替え、組み合わせ、分解 などの候補となる箇所を見つけるのは簡単ではない。
太字部分 が、最適化の具体例なんだろうな。
Spark を利用する場合、エンジニアリングチームは Spark の最適化の問題にプロアクティブに関わらないといけない。計算リソースを多くとったり [原文は expensive]、長時間稼働するJOBの場合は特に。
つまり、チームの中で最適化に関する専門知識を育て、個々のエンジニアにどうやって最適化するのかを教え込まなければならない。
ここに来て急に教育の話が出てきた。
「結局、人」というのはどの分野でもよくある話か。
素の Spark を使う際に、特に留意するべきポイント
- フィルターは早い段階でかけろ、こまめにかけろ
- Spark API のコアをなるべく使え、そして Sparkネイティブな処理のしかたを理解しろ。ネイティブの Spark API でやりたいことができない場合は、メンテの行き届いたパブリックライブラリを使え。良い Spark のコードは、相当程度宣言的になるはずだ [原文は substantially declarative] 。
- UDF に気をつけろ
- SQL と組み合わせて使うことを考慮に入れろ
2番だけ妙に長くないか?
対して3番あまりに雑じゃないか?
留意事項 1. について
これは [フィルタリング以外の] Spark の最適化についても当てはまる。例えば SQL なら自動でやってくれるような順序の入れ替えを、素の Spark はやってくれない場合もあるからな。
[ここでフィルターの話に戻って] Spark はビッグデータを処理するフレームワークではあるが、データが少なければ少ないほど、リソースを食わなくなるし、パフォーマンスも良くなるからな。
留意事項 2. について
自分が極端に複雑なカスタムコードを書いているなと思ったら、一度立ち止まって、やろうとしていることを実現できるもっとネイティブな方法がないかを考えてみよう。
チュートリアルをちゃんとやって、公的なコード例を読んで、Spark らしい物事のやりかたを学ぼう。
やろうとしていることを実現できる Spark API はないのか?
メンテの行き届いた、最適化されたパブリックライブラリを使えないか? [といったことを常に検討しよう]
留意事項3. について
これは特に PySpark について言えることだ。
ざっくり言えば、PySpark は、Scala の Spark をくるむラッパーだ。
Spark における Scala vs Python のジレンマ
-
Scala とは
- Java に関数型言語のコンセプトを加えたような、Javaよりは少ないコードで記述できる言語
- Java との互換性が高く、JVM上で動く
- Spark のネイティブ言語は Scala
-
Java/Scala と Python の違い
観点 | Java/Scala | Python |
---|---|---|
実行方式 |
コンパイラ型 まずコンパイルして機械語に翻訳してから実行する |
インタプリタ型 機械語に翻訳しながら実行する |
処理スピード | 速い | 遅い |
利用目的 | 主にバックエンドのシステム開発 | バックエンドのシステム開発の他、データ分析、機械学習などに利用 |
→ Python は処理速度がコンパイラ型よりは劣るといっても、通常のシステムであれば、Python とコンパイラ型の速度の違いが体感ではわからない程度には、最近のコンピューターの性能は上がってきている。なので、普通のシステム開発をするのであれば、Java でも Python でも開発チームが書きやすい方で作ればええやんという判断が通るようになってきている。
→ しかし、ビッグデータのように大量の計算を行うとなると話は変わってくる。バックエンド(データエンジニアリング)的な意味での処理速度を考えると Java/Scala、特に Spark の場合は ネイティブ言語である Scala が勝つ。
→ 一方、Python は機械学習に強みを持っているため、ユーザーであるデータサイエンティストは Python を使いたがる場合もある。(だからこそ PySpark がある)
[たとえ PySpark を使った場合でも] キミのコードはAPIを通じて、JVM上で動くネイティブな Scala のコードに変換されて動作するんだぜ。
Python の UDF を使うということは、データを必ず一度、効率の劣る Python に渡して処理をさせることになります。
もし、自分が Python の UDF を使っていることに気がついたら、同じことをもっと Spark ネイティブな方法でできないか考えてみるべきだ。
「メンテの行き届いた、最適化されたパブリックライブラリを使えないか?」という留意事項に立ち戻れ。
どうしても UDF を使わなければいけないとしたら、パフォーマンス向上のために、そいつを Scala か Java で書き直すことを検討しろ。
後半部分(「もし、自分が〜」以降)には正直驚いた。実質的に、PySpark 使うなって言ってませんか? と。
これに対する Sakatokuさん のコメントで、「最終的にはデータサイエンティストが使うために、ビッグデータをPythonに渡さなければならない場合がある。PySparkはそういうときにだけ使えという意味では」と聞いて納得。
処理の途中で、ネイティブな Spark と PySpark を行ったり来たりするのは効率が悪いので、なるべくネイティブな Spark で処理をやりきった上で、最後の出口で必要なところだけ PySpark を使いましょうということ。
とはいえ私のように、Python なら多少書けるけど Java も Scala もほとんど書けませんみたいなスキルの人がデータエンジニアチームの大半だった場合は…
…ん? そんな奴がデータエンジニアになるなってことか?
留意事項4. について
Spark SQL を使うと、Spark Catalyst オプティマイザーの恩恵にあずかり、ネイティブのコードよりもいいパフォーマンスを叩き出すことも可能になる。
シンプルなオペレーションであれば、SQLの方が簡単に書けることが多い。
ネイティブの Spark と Spark SQL を組み合わせることで、両方のいいところどりができる。
[ネイティブ Spark の] パワフルで汎用的な機能と、使える場面では[SQLの]簡潔さの両面のことだ。
最後に
[このセクションでは主に Spark と Spark SQL の話をしてきたが、] このセクションでとりあげた留意事項は一般的なもので、たとえば Beam などを使うときにも当てはまる。
主要なポイントは、SQLはたぶんプログラムによる処理ほどパワフルではないけれども簡単さでは勝り、プログラムによるデータ処理は[プログラマーに]最適化という難題を軽くやってのける力[原文は finnesse] を要求するということだ。
感想
... Java ... まじめに勉強しようかな。。。 orz
先に Scala からやろうとか思うのは、ただ業界歴がそこそこ長くなってしまっただけの低スキルエンジニアのかっこつけなのか・・・うーんうーん。。。