/home/by-natures/dev*

データ界隈で働くエンジニアとしての技術的なメモと、たまに普通の日記。

2023/08/18 読んだ記事まとめ(Snowflake コスト削減/最適化/Iceberg連携)

お盆休みをしっかりいただけたので、徐々に通常運転に戻すべく今日は Snowflake 絡みの記事を3つ取り上げてみます。

Best practices to optimize Snowflake spend

medium.com

Snowflake のコストが高いと最近耳にしますが、便利で高性能なサービスがゆえに利用目的などを精査していない結果、仮想ウェアハウスの利用金額が高くなってしまったり(Tableau などの BI サービスから接続するときに顕著?)、Snowflake が用意する様々な機能を知らずに損をしている、ということがある気がしています。

上記記事は Snowflake のプロダクトマネージャによる投稿で、Snowflake のコストを最適化するためのノウハウが紹介されています。ノウハウといっても基本的なもので、以下の4ステップに分けて具体的な方法が紹介されていました:

  1. コストの可視化を行い、各部門やチームへの費用按分を決定する
  2. 予算に対してのアラートを設定する
  3. RoI に基づいたワークロードの分割を行う
  4. リソースを大きく消費する行動を制限する

コストの可視化については Snowflake はすでにダッシュボードを用意していますし、それで足らなければコストに関する様々なビューが提供されています。最近では Streamlit と連携して、Snowflake のデータを使ったダッシュボードが Python で簡単に構築できるようになりました。

3, 4番目は Snowflake の機能紹介の色が強い気がしましたが(自動クラスタリング, Query Acceleration Service, 検索最適化サービス, マテリアライズドビュー)、こういった機能を闇雲に使うのではなく、RoI を加味して使い分けたり、必要であればアーキテクチャ再構築も実施するべきとも書かれていました。

パフォーマンス改善の役に立つ 知っていてほしい Snowflake の仕様 2 選

speakerdeck.com

こちらは以前同僚に教えていただいた Snowflake のパフォーマンス改善に関するスライドです。カラム数とパーティションを読み込むサーバの特性について検証を交えながら紹介されています。

特にカラムに関して、100列と10000列によるパフォーマンス差異が検証されていました。実際には各環境/データで検証するべきでしょうが、メタデータ読み込みのオーバーヘッドが顕著になるカラム数の目安を知ることができます。

When To Use Iceberg Tables in Snowflake

medium.com

2023年の Snowflake Summit で、Iceberg フォーマットの連携が強化されると発表がありました(現在はプライベートプレビュー)。公式発表によると、外部テーブルとして Parquet データを読むよりも外部の Iceberg テーブルを読む方が早く、さらに Snowflake 管理の Iceberg テーブルはネイティブフォーマットと同等のパフォーマンスが得られるとのことです。

Iceberg フォーマット自体の解説も上記記事で紹介されていますが、誤解を恐れずに一言で言えばACID特性を持たせて更新可能にした Parquet フォーマットです。

これで Snowflakeで扱えるフォーマットは以下の4種類になりました:

  • ネイティブ: 多くの場合、最適なフォーマット
  • Snowflake 管理の Iceberg フォーマット: Snowflake のデータを外部ツールから読み込む場合など
  • Snowflake 非管理の Iceberg フォーマット: AWS Glue など、外部のETLツールが生成したデータを Snowflake 上で利用する場合など
  • 外部テーブル: CSVファイルなど外部にホストされているデータを読む場合にのみ利用

この Iceberg 連携により、オープンフォーマットでデータを管理したい場合でも Snowflake を利用する選択肢が増えました。Iceberg フォーマットでデータを(例えば S3 に)一元管理しておくことで、データの重複を防ぐことはできそうです。その場合 アーキテクチャ的に Athena と重複しそうですが、Snowflake には Snowpark などの機械学習向けの機能などもありますし、ユースケースを整理した上で、用途によってクエリレイヤを使い分けるのも一案かもしれません。

2023/08/10 読んだ記事まとめ(流行りに乗らないデータ人材)

今日読んだ記事は Snowflake や Databricks の話も交えつつ、その「流行り」に盲目的に乗らない Anti-Hype data person(流行りに乗らないデータ人材、と訳せるでしょうか)が行う、本質的なデータ業務についての紹介記事です。

Snowflake や Databricks などの DPaaS (Data Platform as a Service) の利用料金が高額になってしまうという声はしばしば聞きますし、ある知人の現場ではデータ活用目的を明確にした上でデータモデリングもしっかり行い、Snowflake ではなく S3 + Athena のデータ分析環境を構築していると聞きました。流行りのツールに乗ることは本質的な課題解決を先送りにしているだけであり、面倒でもデータ利用者のユースケースの把握をして、その上で適切なデータフォーマットやデータモデリングを行った上でデータ提供しよう、という主張です。

もくじ:

"Cached Takes: 80% of Companies do not need Snowflake or Databricks"

kjhealey.medium.com

(ChatGPTによる和訳をベースに要約&加筆修正しました)

キャリアが進行すると私はビジネス志向が強まり、使用している技術に対して無関心になりました。そして多くの企業がデータプロジェクトに過度に投資し、成果を十分に出せていないことに気付きました。主な理由として、Databricks や Snowflake といった大手データプラットフォームサービスのマーケティングに影響され、高価なサービスを必要以上に使用している企業が多いことが挙げられます。私の意見では、これらのプラットフォームが提供するサービスは、8割の企業にとって過度なものであると考えています。これらのプラットフォームは問題を解決すると主張されていますが、実際にはすでに存在する組織の非効率性を増大させる場合が多いです。

では、いわゆる流行から距離を置くにはどうすればいいのでしょうか。それは人を信頼することです。問題を解決するのは人であり、企業は大金をプラットフォームに使う前にデータ問題の核心に触れるべきです。このようにコスト意識を持ち、流行から距離を置くことでデータ関連の問題解決に大きなインパクトを与えることができます。

以下に挙げる3つの事柄を意識することで、コスト意識の高い、流行りに乗らないデータ人材となることができます:

1. データの代理人として、データ利用者の言葉に耳を傾ける

これはおそらく最も難しいことです。なぜならビジネスは結局、実現可能性や難易度を考慮せずによりよい結果を求めており、エンジニアにすべての責任を押し付けつつ、自分たちは責任を全く負わないでいたいと考えています。もしあなたのプロジェクトにおいてビジネスメンバーがデータに対して何を望んでいるのかを知らないのであれば、それは大きな警告信号であり、データをビジネス価値にどう変換したいのかを理解していないという組織の未熟さを示しています。

流行から距離を置くために最も重要なことは、クライアントの要望を正しく理解し、それを適切かつ計測可能な技術に翻訳することです。クライアントとの対話を促すために私が尋ねる質問は次の通りです:

  • データをどのように使用する予定ですか?
  • データを毎日受動的に見るだけですか、それとも他のベンダーにデータを送る機能が必要ですか?
  • データの頻度はどれくらいですか?
  • データの量はどれくらいですか?

エンジニアはコードを書くことができますが、流行から距離を置く優れたエンジニアは、クライアントが「わかりません」という回答が出るまで質問をします。この言葉が出たら、あなたは自分の経験と専門知識を生かして、クライアントのニーズを満たす解決策を設計と設計を支援できます。

2. (つまらない作業だが)データモデルを作る

データモデリングの技術はデータの流行の中で失われています。多くの人はどれだけ早く実装ができ、データ提供できるかという「速さ」にのみ焦点を当てています。モダンデータスタックはこの「速さ」を生むのが得意ですが、間違ったことを速くやってしまうと災厄を招く可能性があります。

自身のアーキテクチャについて考え、データウェアハウスのためのデータアーキテクチャ(Kimball、Inmonなど)と可視化レイヤー(Starスキーマ、Snowflakeスキーマ、Galaxyスキーマ)を決定します。データレイクを使用している場合、パーティションにする列を定義し、フィルタリングする列を同一の場所に配置します。ファイルを圧縮するタイミング、Z-orderにするタイミングを定義します。データが双方向に動く必要がある場合は、両方向でデータが読み込み時にどのように更新するかを決定します。

ステークホルダーとも話し合います。彼らがどのようにデータを見たいか尋ね、彼らが一緒に使いたいフィールドを同じ場所に配置し、彼らが見たくないデータフィールドを他のテーブルに押し出します。データの非効率性を早期に見つけ出して、この非効率性に対処できるアーキテクチャを作ることができれば、Snowflake や Databricks が提供するパワーは必要ないことが多いです。人と協力して本当の作業をしましょう。コードは自然と書かれます。私が約束しましょう。

3. SaaS ソリューションではなく、オープンソースを取り入れよう

あなたが分析プラットフォームのために Snowflake のコア機能を再現しようとしているなら、DuckDB を検討してください。DuckDBは FS ライクなシステム(S3、Azure Blobなど)やデータベースに対して動作します。私は Postgres の duck-db エクステンションを使用していますが、Snowflake が提供するストレージとコンピュートの分離を置き換える可能性があると考えています。

オープンソースの技術を常に把握しておくことで、コスト改善も図れます。OSS が最初から Snowflake と Databricks の両方に最初から含まれているようなセキュリティ機能を全て含んでいることはありませんが、どのようにセキュアに実装するかを Dev Ops エンジニアやマネージャーと議論することは可能でしょう。あなたの仕事において、人という要素が最も重要です。交流し、探索し、流行を追うのではなくビジネス価値への技術を翻訳することで、Databricks や Snowflake とほぼ同じレベルのコア機能を維持しながら費用を節約することができます。

感想:正しいと思える一方、組織の力学的には難しい?

この記事で書いてあることは非常に正しいと感じましたが、流行を追いたい気持ちも一方で理解できます。

記事にも書いてありましたが、働き始めのころは新しいツールやサービスを羨ましく思うものです。私も新社会人のころは Java ではなく Python や Ruby などの動的型付けな軽量プログラミング言語を使った業務経験に憧れがあったり、RDBMS ではなく MongoDB みたいな新しいデータベースを使ってみたいと思ったり、AWS を使ったサービスを構築してみたい、などとよく思っていました。こういった流行技術を身につけることでそのタイミングでは市場価値も上がり、社内外での発表の機会に恵まれるなど、いろんな恩恵も受けられると思います。

一方で、私の研究科の大学教授は以前「プログラミング言語には動的/静的の間で流行りがある」とおっしゃっていて、確かに最近では軽量プログラミング言語にも静的型付けの要素が取り入れられるようになったりしています。株価と同じように過度に値が上がったり下がったりした場合、より戻しが来るのと似ているかもしれません。記事に話を戻すと、何でもかんでも DPaaS を使うのではなく、どんな目的でどのデータをデータ活用するのか?を理解/議論し、アーキテクチャやデータモデリングも行った上で実装に移りましょうということでした。その結果 DPaaS ではなく DuckDB のようなスタンドアロンなデータ分析環境で十分な場合も多くあるとのことです。

この「流行に流されず、本質的な業務に注力する」という意識は、ある程度の規模の組織だと現場レベルの努力だけで意識共有するのは難しく、組織的な取り組みが必要だとも感じました。世間で盛り上がっている技術やサービスがどんなものか気になる、使ってみたいと多くの方は思うでしょうし、そういった技術などを取り入れたプロダクトはモダンだと認識され、間接的にエンジニアの採用にも繋がると思います(採用したいエンジニアかはともかく、応募数は増えるはず)。この辺の塩梅は結構難しそうです。

この記事は私が以前所属していた組織が目指す姿にとても近いのですが綺麗に言語化されていました。改めて、本質的な業務ができるよう心がけたいです。一方で流行りのツールやサービス、OSS がどのようなものかは学び続ける必要があり、適切な技術を組み合わせた、活用目的に沿ったアーキテクチャを組めるようになりたいです。

2023/07/20 Data Qualify Fundamentals 輪読会に参加しました

data-tech-jp にて Tsuchikawa さん (@tvtg_24) が実施されていた Data Quality Fundamentals の輪読会に参加していて、先日最終回を迎えました。2回ほど出席できなかったので半分ほどしか聞けていないのですが、理解している範囲で備忘録として感想を残します。

どんな本か

Data Quality Fundamentals: A Practitioner's Guide to Building Trustworthy Data Pipelines

Data Observability Platform というサービスを展開する Monte Carlo 社による書籍で、データ品質に関する以下の内容が解説されています:

  • 信頼性の高いデータパイプラインを構築する
  • データ観測性を使用して、データチェックのスクリプトを記述し、壊れたパイプラインを特定する
  • データのSLA、SLI、SLOを設定し、維持する方法を学ぶ
  • 会社でのデータ品質イニシアチブを開発し、リードする
  • データサービスとシステムを、本番環境でのソフトウェアのように慎重に扱う方法を学ぶ
  • データエコシステム全体でデータリネージ図を自動化する
  • 重要なデータ資産のための異常検出器を構築する

Data Downtime

書籍の冒頭でデータダウンタイムという概念が導入されます。これはデータが失われていたり、不正確だったり、そのデータを利用する上で不便が生じている期間のことを指します。

これによってビジネスとして様々な損害が発生する他、データチームはデータダウンタイムの対応に4割以上の時間を割いているという調査結果もあるようです。一見驚きの数字ですが、データ周辺で業務を行っている方なら、データの不具合調査等に業務のかなりの時間を取られているのは実感としてお持ちではないでしょうか。

このデータダウンタイムの対策としてまずはデータ品質を指標(KPI)を用いて定義し、このKPIを改善するための取り組みをしよう、というのがこの書籍の大まかな流れです。

データ品質と KPI

データ品質を改善できるようにするため、まずは定義をしなければいけません。

これには大きく5つの軸があります:

  • Freshness: データは新しい状態か?いつ更新されたか?
  • Distribution: データの値は想定範囲内か?正しいフォーマットか?
  • Volume: 全てのデータが届いているか?
  • Schema: どんなスキーマか?変更がある場合、誰によってどうやって変更されたか?
  • Lineage: そのデータの上流/下流のデータやデータ利用は何か?

これらを SQL を用いたり、データカタログやデータディスカバリーツールを利用して測定する方法が紹介されています。

Data Warehouse v.s. Data Lake

データウェアハウスとデータレイクの比較は書籍を通じてたびたび言及されています。

データウェアハウスはスキーマが事前に定義されているため、特定用途においてはパフォーマンスを発揮する一方、用途に見合わないデータであると、ユーザ側で更なる加工が必要になります。一方でデータレイクでは利用目的に応じて様々なデータを組み合わせることができますが、スキーマが管理されていなかったり、データ形式に応じて様々な変換方法を活用する必要があるなど、データ品質やデータガバナンスの面では課題が多く存在します。データカタログやディスカバリーツールにおいても、データレイク上で運用する上での難しさが際立ちます。

いつデータ品質の問題に取り組むべきか?

データ品質の課題は後回しにされがちです。それは仕方がないことで、データ基盤立ち上げ当初は「どんなデータを取り込むか」「どうやってデータを取り込むか」など、いわば機能面に関心があります。書籍では、データ基盤を運用する中で以下の現象が起きたら、データ品質に取り組むタイミングだと紹介されています:

1) You've Recently Migrated to the Cloud

最近データ基盤を移設した場合。移設後のデータ基盤を使ってもらうためには、新しい基盤が旧基盤と同様のデータ品質を保っているとユーザに示す必要があるため。

2) Your Data Stack is Scaling with More Data Sources, More Tables, and More Complexity

データの複雑性が増してきた場合。例としてテーブル数50個以上、という値が紹介されていました。

3) Your Data Team Is Growing

データチームの人数が増えてくると、暗黙知によるトラブルが増加します。

「あのテーブルは古いから使ってはいけない」など、聞かなければ分からないノウハウはみなさんも経験があるのではないでしょうか。こういったトラブルを避けるため、新しいメンバーが増えた場合にはデータ品質の課題に取り組む良いタイミングです。

4) Your Team is Spending at Least 30% of Their Time Firefighting Data Quality Issues

データ品質の課題にデータチームが多くの時間を使っている場合。これは一見明らかなタイミングに思えますが、一旦データダウンタイムが解消するとデータ品質への優先度は下がり、新しいデータ取り込みの開発に取り組まなければならないなど、分かってはいるけれど手がつけられない…という状況のチームは少なくないのではないでしょうか。

5) Your Team Has More Data Consumers Than They Did One Year Ago

データ利用者が一年前より急拡大している場合。先ほどのデータチームの拡大にも似ていますが、あらかじめデータ利用者の数を把握できる状態にしていないと、このタイミングに気づくのは難しいかもしれません。

6) Your Company Is Moving to a Self-Service Analytics Model

セルフサービス分析モデルという、ユーザ自身でデータ分析ができる体制を目指す場合にもデータ品質は重要です。なぜなら、データ利用者は信頼できなければ意思決定にデータを活用しなくなるためです。

7) Data Is a Key Part of the Customer Value Proposition

データを直接顧客に提供している場合は、もちろんデータ品質は重要な課題です。日本だと最近ではマイナンバーカードの問題なども騒がれていますが、システムやデータを直接ユーザに届ける場合は、その品質が非常に重要になると肝に銘じる必要があります。

書籍では DataMesh とデータ品質の関係も解説されています。「データプロダクト」として特定ドメイン内のデータを他ドメインに提供する関係上、そのデータ品質が重要になります。直接の顧客ではないかもしれませんが、データを外(ここでは他ドメイン)に利用してもらうという点が共通しています。

まとめ

データ品質だけに特化した面白い書籍でした。クラウド上のデータ基盤やデータレイク、DataMeshなど様々な概念も交えて解説がされていて、これらの概念をデータ品質の観点から理解を深めることもできます。

Monte Carlo 社が Data Observability Platform というツールを提供しているため一部製品紹介の側面もありそうですが、Snowflake を題材にした具体的な SQL でデータ品質の取得方法も紹介されており、すぐに実践できる内容も含まれていました。

個人的には最後の、データ品質にいつ取り組むかという内容が(自分の発表担当という理由もありますが)とても興味深かったです。後回しにされがちなデータ品質の課題の重要度を上げるため、データチームやステークホルダを説得する材料になるのではないかと感じました。

2023/07/05 Apache Flowman(YAMLでETL処理が書けるOSSプロダクト)の紹介

Medium で見つけた記事をいくつか紹介しようと思ったのですが、Flowman の記事が長くなったので Docker でのデモも交えながらご紹介します。

Flowman — A Declarative ETL Framework powered by Apache Spark

kupferk.medium.com

Flowman という Apache ライセンス下の ETL フレームワークの紹介です。ピュアな Spark, Cloudera, AWS EMR, Azure Synapse などの分散処理システム上で動く「宣言的な」ETLフレームワークとのこと。記事自体も Flowmanの開発者 Kaya Kupferschmidt 氏によって書かれています。地味なポイントですが公式ページがオシャレなのも良いですね。ドキュメントを読みたくなる…。

Flowman - Declarative ETL Framework powered by Apache Spark

「宣言的」にETLが書けるというのをアピールポイントにしていて、具体的には YAML ファイルを定義することで ETL 処理の記述をすることができます。

# File: flow/mapping/measurements.yml
mappings:
  # This mapping refers to the "raw" relation and reads in data from the source in S3
  measurements_raw:
    kind: relation
    relation: measurements_raw
    partitions:
      year: $year

  # Extract multiple columns from the raw measurements data using SQL SUBSTR functions
  measurements_extracted:
    kind: select
    input: measurements_raw
    columns:
      usaf: "CAST(SUBSTR(raw_data,5,6) AS INT)"
      wban: "CAST(SUBSTR(raw_data,11,5) AS INT)"
      date: "TO_DATE(SUBSTR(raw_data,16,8), 'yyyyMMdd')"
      time: "SUBSTR(raw_data,24,4)"
      report_type: "SUBSTR(raw_data,42,5)"
      wind_direction: "CAST(SUBSTR(raw_data,61,3) AS INT)"
      wind_direction_qual: "SUBSTR(raw_data,64,1)"
      wind_observation: "SUBSTR(raw_data,65,1)"
      wind_speed: "CAST(CAST(SUBSTR(raw_data,66,4) AS FLOAT)/10 AS FLOAT)"
      wind_speed_qual: "SUBSTR(raw_data,70,1)"
      air_temperature: "CAST(CAST(SUBSTR(raw_data,88,5) AS FLOAT)/10 AS FLOAT)"
      air_temperature_qual: "SUBSTR(raw_data,93,1)"

さらには単体テストの機能もついており、擬似データを定義することで各データ処理が正しく行われるかを確認することができます。

# File: flow/test/test-measurements.yml
tests:
  test_measurements:
    environment:
      - year=2013

    overrideMappings:
      measurements_raw:
        # Mocking a data source will pick up the original data schema, and the only thing left to do is providing
        # mocked records
        kind: mock
        records:
          # Provide two records including both the raw data and the partition key
          - year: $year
            raw_data: "042599999963897201301010000I+32335-086979CRN05+004..."
          - year: $year
            raw_data: "013399999963897201301010005I+32335-086979CRN05+004..."

    assertions:
      measurements_extracted:
        kind: sql
        description: "Measurements are extracted correctly"
        tests:
          - query: "SELECT * FROM measurements_extracted"
            expected:
              - [ 999999,63897,2013-01-01,0000,CRN05,124,1,H,0.9,1,10.6,1 ]
              - [ 999999,63897,2013-01-01,0005,CRN05,124,1,H,1.5,1,10.6,1 ]
          - query: "SELECT COUNT(*) FROM measurements_extracted"
            expected: 2

Dev, Prod など環境ごとの差異をプロパティによって吸収することもできたり、書き込みレコード数をメトリクスとして Prometeus などのモニタリングツールに連携することもできます。

ローカルの Docker で触ってみる

起動

Flowman Quick Start Guide - Flowman documentation

Flowman にはインタラクティブにジョブを実行する flowshell と、バッチとして設定する flowexec の2つのコマンドがあります。Dockerで触ってみる上のサンプルでは flowshell を利用したチュートリアルが紹介されています。

% docker run --rm -ti dimajix/flowman:1.0.0-oss-spark3.3-hadoop3.3 bash
Unable to find image 'dimajix/flowman:1.0.0-oss-spark3.3-hadoop3.3' locally
1.0.0-oss-spark3.3-hadoop3.3: Pulling from dimajix/flowman
e756f3fdd6a3: Pull complete 
bf168a674899: Pull complete 
e604223835cc: Pull complete 
6d5c91c4cd86: Pull complete 
5e20d165240e: Pull complete 
1334d60df9a8: Pull complete 
16c2728dcd90: Pull complete 
0eb2d7ff6056: Pull complete 
6dde7ad652df: Pull complete 
7b48840dfae0: Pull complete 
45d6f68b5a92: Pull complete 
cbc35c331c56: Pull complete 
3a262e284a90: Pull complete 
Digest: sha256:2135f9cd8c0dc4ba32def28681eb1158bc674594cd01515907cdcc9eda5e0e51
Status: Downloaded newer image for dimajix/flowman:1.0.0-oss-spark3.3-hadoop3.3
flowman@df9ac595c022:~$ ls

シェルに入って、-f でプロジェクトディレクトリを指定して CLI に繋ぎますが…

flowman@2e564406fbbe:~$ flowshell -f examples/weather/
23/07/05 12:58:58 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[INFO] Reading Flowman system settings file /opt/flowman/conf/system.yml
[INFO] Reading plugin descriptor /opt/flowman/plugins/flowman-impala/plugin.yml
[INFO] Loading Plugin flowman-impala
[INFO] Reading namespace file /opt/flowman/conf/default-namespace.yml
Killed

Kill されてしまいました。verbose オプションをつけても有益なログは出てきませんでした。Colima のメモリ割り当てを増やして再実行(ローカルマシンで余っていた6GBを割り当て)。

% colima stop
% colima start --memory 6
% docker run --rm -ti dimajix/flowman:1.0.0-oss-spark3.3-hadoop3.3 bash
flowman@3c594160f6f0:~$ cd /opt/flowman
flowman@3c594160f6f0:/opt/flowman$ flowshell -f examples/weather/
Welcome to
______  _
|  ___|| |
| |_   | |  ___ __      __ _ __ ___    __ _  _ __
|  _|  | | / _ \\ \ /\ / /| '_ ` _ \  / _` || '_ \
| |    | || (_) |\ V  V / | | | | | || (_| || | | |
\_|    |_| \___/  \_/\_/  |_| |_| |_| \__,_||_| |_|    1.0.0

Using Spark 3.3.2 and Hadoop 3.3.2 and Scala 2.12.15 (Java 11.0.15)

Type in 'help' for getting help
flowman:weather> 

無事に起動できました。Relation の一覧から、 S3にあるデータ stations_raw のデータを確認してみます。

flowman:weather> relation list
aggregates
measurements
measurements_raw
stations
stations_raw

flowman:weather> relation show stations_raw
CSV file: s3a://dimajix-training/data/weather/isd-history/isd-history.csv
+------+-----+----------+-------+-----+----+--------+---------+---------+----------+----------+
|  usaf| wban|      name|country|state|icao|latitude|longitude|elevation|date_begin|  date_end|
+------+-----+----------+-------+-----+----+--------+---------+---------+----------+----------+
|007018|99999|WXPOD 7018|   null| null|null|     0.0|      0.0|   7018.0|2011-03-09|2013-07-30|
|007026|99999|WXPOD 7026|     AF| null|null|     0.0|      0.0|   7026.0|2012-07-13|2017-08-22|
|007070|99999|WXPOD 7070|     AF| null|null|     0.0|      0.0|   7070.0|2014-09-23|2015-09-26|
|008260|99999| WXPOD8270|   null| null|null|     0.0|      0.0|      0.0|2005-01-01|2010-09-20|
|008268|99999| WXPOD8278|     AF| null|null|   32.95|   65.567|   1156.7|2010-05-19|2012-03-23|
|008307|99999|WXPOD 8318|     AF| null|null|     0.0|      0.0|   8318.0|2010-04-21|2010-04-21|
|008411|99999|      XM20|   null| null|null|    null|     null|     null|2016-02-17|2016-02-17|
|008414|99999|      XM18|   null| null|null|    null|     null|     null|2016-02-16|2016-02-17|
|008415|99999|      XM21|   null| null|null|    null|     null|     null|2016-02-17|2016-02-17|
|008418|99999|      XM24|   null| null|null|    null|     null|     null|2016-02-17|2016-02-17|
+------+-----+----------+-------+-----+----+--------+---------+---------+----------+----------+
only showing top 10 rows
ジョブ実行&確認

flowshell 内でジョブを実行します:

flowman:weather> job build main year=2011

Collected metrics
  job_runtime(name=main,project=weather,phase=BUILD,category=job,kind=job) = 151776.0
  target_records(name=measurements,project=weather,phase=BUILD,category=target,kind=relation) = 4335406.0
  target_records(name=stations,project=weather,phase=BUILD,category=target,kind=relation) = 29744.0
  target_records(name=aggregates,project=weather,phase=BUILD,category=target,kind=relation) = 28.0
  target_runtime(name=measurements,project=weather,phase=BUILD,category=target,kind=relation) = 144379.0
  target_runtime(name=aggregates,project=weather,phase=BUILD,category=target,kind=relation) = 3754.0
  target_runtime(name=stations,project=weather,phase=BUILD,category=target,kind=relation) = 3017.0
[INFO] Mark 'BUILD' job 'default/weather/main' as SUCCESS in history database
[INFO] -------------------------------------------------------------------------------------------------------------
[INFO] Execution summary for job 'weather/main' year=2011
[INFO] 
[INFO] weather/measurements ................................................................... SUCCESS [  2:24 min]
[INFO] weather/stations ....................................................................... SUCCESS [   3.014 s]
[INFO] weather/aggregates ..................................................................... SUCCESS [    3.75 s]
[INFO] -------------------------------------------------------------------------------------------------------------
[INFO] SUCCESS BUILD job 'weather/main' year=2011
[INFO] -------------------------------------------------------------------------------------------------------------
[INFO] Total time: 2:31 min
[INFO] Finished at: 2023-07-05T13:18:55.460834Z[Etc/UTC]
[INFO] -------------------------------------------------------------------------------------------------------------
[INFO] 
[INFO] =============================================================================================================
[INFO] Overall lifecycle summary for job 'weather/main' year=2011
[INFO] 
[INFO] Phase VALIDATE ......................................................................... SUCCESS [  13.936 s]
[INFO] Phase CREATE ........................................................................... SUCCESS [     0.4 s]
[INFO] Phase BUILD ............................................................................ SUCCESS [  2:31 min]
[INFO] =============================================================================================================
[INFO] SUCCESS lifecycle for job 'weather/main' year=2011
[INFO] =============================================================================================================
[INFO] Total time: 2:46 min
[INFO] Finished at: 2023-07-05T13:18:55.705432Z[Etc/UTC]
[INFO] =============================================================================================================

aggregates の定義は country ごとに集約して各気象情報の最小値/最大値/平均値を出すマッピングになっているので、この実行結果を見てみます。

集約も YAML ファイルで定義できています:

# https://github.com/dimajix/flowman/blob/main/examples/weather/mapping/aggregates.yml
mappings:
  # Create some aggregates containing min/max/avg metrics of wind speed and temperature
  aggregates:
    kind: aggregate
    input: facts
    dimensions:
      - country
    aggregations:
      min_wind_speed: "MIN(wind_speed)"
      max_wind_speed: "MAX(wind_speed)"
      avg_wind_speed: "AVG(wind_speed)"
      min_temperature: "MIN(air_temperature)"
      max_temperature: "MAX(air_temperature)"
      avg_temperature: "AVG(air_temperature)"

ここがちょっとよく分からなかったのですが、year=2011 というジョブコンテキストを指定しながらデータを確認します:

flowman:weather> job enter main year=2011

flowman:weather/main> mapping list                                                                                                                                   
aggregates                                                                                                                                                           
facts                                                                                                                                                                
measurements                                                                                                                                                         
measurements_extracted                                                                                                                                               
measurements_joined                                                                                                                                                  
measurements_raw                                                                                                                                                     
stations                                                                                                                                                             
stations_raw                                                                                                                                                         

flowman:weather/main> mapping show aggregates 
+-------+--------------+--------------+------------------+---------------+---------------+------------------+
|country|min_wind_speed|max_wind_speed|    avg_wind_speed|min_temperature|max_temperature|   avg_temperature|
+-------+--------------+--------------+------------------+---------------+---------------+------------------+
|     FI|           0.0|          21.0|  3.91133101326951|          -34.4|           30.7|3.1282190762524187|
|     NL|           0.0|          31.4|4.9690808808653575|           -8.2|           34.0|10.753498106716485|
|     CA|           0.0|          24.7| 4.170504328900583|          -45.0|           34.7| 2.931447564320896|
|     GK|           0.0|          19.0| 5.831224191330406|            1.0|           28.0|12.094347907821643|
|     PO|           0.0|          15.4|3.1292321427768974|           -1.0|           33.5| 15.40633601601996|
|     US|           0.0|          36.0|  2.95617297419709|          -44.0|           46.4|11.681804003913621|
|     GM|           0.0|          15.4|3.9108231087221923|          -10.0|           30.0| 9.442136410413799|
|     FR|           0.0|          19.5|2.8940356618754155|           -9.0|           37.8|14.367572536833235|
|     DA|           0.0|          22.6| 4.283963535296792|          -13.1|           27.0|  8.76277380753966|
|     UK|           0.0|          26.7| 4.486483891184242|           -5.2|           32.0| 11.08883097570104|
+-------+--------------+--------------+------------------+---------------+---------------+------------------+
only showing top 10 rows

country ごとに集約して各気象情報の最小値/最大値/平均値が計算できています。

/tmp/ にファイルを書き出したようなので、一度 flowshell から出て確認してみます。

flowman@3c594160f6f0:/opt/flowman$ ls -al /tmp/weather/aggregates/year\=2011/                                                                                        
total 20                                                                                                                                                             
drwxr-xr-x 2 flowman flowman 4096 Jul  5 13:18 .                                                                                                                     
drwxr-xr-x 3 flowman flowman 4096 Jul  5 13:18 ..                                                                                                                    
-rw-r--r-- 1 flowman flowman    8 Jul  5 13:18 ._SUCCESS.crc                                                                                                         
-rw-r--r-- 1 flowman flowman   32 Jul  5 13:18 .part-00000-d2cb9b17-99c7-45d7-8cd2-d74dfbfb375e-c000.snappy.parquet.crc                                              
-rw-r--r-- 1 flowman flowman    0 Jul  5 13:18 _SUCCESS                                                                                                              
-rw-r--r-- 1 flowman flowman 2822 Jul  5 13:18 part-00000-d2cb9b17-99c7-45d7-8cd2-d74dfbfb375e-c000.snappy.parquet

現在時刻で新しい Parquet ファイルが生成されていました。実際のジョブでは Sink の設定をすることで、S3 や HDFS など外部ストレージにデータを書き出す(書き戻す)ことができます。

感想

Docker 上で少し触っただけですが、 YAML ファイルだけで ETL 処理を管理できるのは魅力的だと感じました。

一方で mapping やジョブコンテキストの概念が少し独特で分かりづらくて GUI も開発されていないので、YAML で宣言的に ETL が書けると言っても、誰でも簡単に…という訳にはいかなそうです。この点は SQL さえかければ誰でもデータ構築ができると謳う dbt とは少しスタンスが異なります。Flowman は Spark 上で ETL 処理を行っているエンジニアの業務効率を上げて本来注力したいビジネスロジックの実装に時間を割けるようになるツールだと感じました。

2023/06/30 令和5年春期ネットワークスペシャリスト受験記

6月29日正午に令和5年春期のIPA高度試験の結果が発表されました。

私はネットワークスペシャリストを受けました。肌感としては午後Ⅰが厳しいかな…よくて60点ギリギリと思ったのですが、無事合格していました。去年11月に受験を決意してお正月明けから勉強開始、直前1ヶ月は午後Ⅰ・Ⅱの過去問対策を中心に土日はほぼ費やしたので無事に合格できて本当に嬉しいです。

自己採点は怪しい回答は全て0点とした厳しめの採点で午後Ⅰが57点、午後Ⅱが62点だったので、実際は部分点もあったようで無事合格点に達することができました。特に午後Ⅰは穴埋めで10点以上確実に落としているので記述問題が8割9割取れていることになっていて、点数調整が入ったか採点が相当甘かったようです。逆に午後Ⅱは問題自体が簡単だったので自己採点と乖離が小さく、採点が厳しい気がします(個人の感想です)。

それにしても午後Ⅰは相当難しかったです。大問3つから2つ選択なのですが、大問1:HTTP2, 大問2:センサーデバイス, 大問3:無線LAN(WiFi6) とどれも参考書からすると発展的なトピックだったので問題選択だけで10分以上掛かりました。。去年と今年の傾向からすると、午後Ⅰは発展的なトピックから選択、午後Ⅱは基本的なトピックから深ぼって知識を問う傾向にあるのかもしれません。

仕事をする上でネットワークの知識を補強したいと考えて受験しましたが、仕事で役立つ場面がさっそく何度かありました。過去問ではネットワーク機器のクラウド化が多く出題されていて、実サービス名こそ出ないものの今後の業務でも役立ちそうです。

今後ネットワークスペシャリストを受験される方へ

お役に立つか分かりませんが、もし私がまたネットワークスペシャリストを受けるならどうするかを書いてみます。

午前は過去問道場だけ

午前Ⅰ、午前Ⅱは選択問題で過去問からの出題も多いです。過去問道場を使って学習すると、電車内など移動中にもさくっと学ぶことができるので便利です。過去問からは2, 3割出題されるようなので、過去問からの出題を落とさなければ基本的に午前は突破できると思います。

集中できる時間があれば午後対策に費やし、隙間時間に午前対策をするのが個人的にはおすすめです。

ネットワークスペシャリスト過去問道場|ネットワークスペシャリスト.com

午後は早くに過去問に取り掛かる

午後問題は出題トピックの深い知識が求められるのですが、テキストを精読しようとすると分厚すぎて読んだそばから忘れていくので、ざっとテキストを読んだあとに過去問を解きながら不明点の理解度を上げる方法がよさそうです。私はテキストを精読してから試験1ヶ月半前にようやく過去問に取り掛かったんですが、自分の理解度の低さに絶望して大慌てしました。。

使ったテキストは ICT ワークショップの有名なテキストですが基本的なトピックしか掲載されていないので、近年の午後Ⅰ対策だと不十分でした。現に今年の大問1:HTTP2, 大問2:センサーデバイス, 大問3:無線LAN(WiFi6)はどれもこのテキストだと不十分です。

情報処理教科書 ネットワークスペシャリスト 2023年版

ちょっと値は張るのですが、日経ネットワークがかなり試験対策に役立つと言われています。新しいトピックもあり、トラブルシュートもあり、ネットワークスペシャリストを想定した問題も付いています。

日経NETWORK(日経ネットワーク)

落ちていた場合のことを考えて来年の受験対策に購読を始めたのですが、試験で出題されたトピックが多く登場しつつ新しい知識も取り入れられるのでおすすめです。値段の割に薄いですが精読しながら読むタイプの雑誌なので、しっかり読むと結構時間がかかります。試験は無事合格できましたが来年3月まで毎月届くので、頑張って読み続けます。

受け身の勉強として Youtube を活用

試験2週間前ぐらいから Youtube も使って学び始めたのですが、これがかなりよかったです。気張らずに学び続けられますし、特に WiFi や認証システムなどはテキストだと動作がよく分からないので、アニメーション付きで解説している動画がたくさんあるのでオススメです。「まさるの勉強部屋」さんにはとてもお世話になりました。

www.youtube.com

分からない用語や概念を ChatGPT に聞く

テキストを読んでいてよく分からない概念を ChatGPT 相手に質問して学習しました。今履歴を見返したら IPsec の IKE, VRRP の動作などを質問していました。下の例はリンクアグリゲーションとIPsecについて聞いたダイアログの一部です。まるで身近にいつでも聞ける先生がいるようでした。

もちろん ChatGPT がいつも正しいことを答えてくれるとは限りませんが、ネットワークスペシャリストで学ぶ内容は基礎的な内容が多いため、私が今回使った限りでは基本的には正しい回答をしてくれていました(最初は一応回答を確認していたのですが、問題なさそうだったのですぐに回答確認はやめました)。

 

秋にはデータベーススペシャリストを受ける予定なので、もし一緒に受験される方がいたら頑張りましょう!

2023/06/20 読んだ記事まとめ(データエンジニアリングとソフトウェアエンジニアリングの違い)

最近仕事をしていて、データエンジニアと肩書きはいただいてますがソフトウェア開発をすることも多く、データエンジニア・ソフトウェアエンジニアの2つに違いはどの程度あるのか?ただ役割を細分化しただけなのかが気になっていました。

そんな時に目についた記事がこちら:

medium.com

物理学の博士をもつ、ベルギーの様々な企業でデータエンジニアやデータサイエンティストとして働く Niels Cautaerts 氏による投稿です。2つの違いが簡潔に言語化されており、さらにアジャイル開発やテストの文脈での違いに焦点を当てて分かりやすく解説されています。

ちょっと長い記事なのですがまとめてみました。太字だけ追えばざっくり理解できるようにしたので、ぜひご覧ください。和訳要約には ChatGPT を利用しましたが、専門用語も多いので割と自分で加筆修正しています。分かりづらい箇所があればご指摘ください。

<<もくじ>>

"データエンジニアリングはソフトウェアエンジニアリングではない"

近年、データエンジニアリングはDevOpsに収束しているように見えます。両方とも、クラウドインフラ、コンテナ化、CI/CD、そしてGitOpsを採用し、信頼性の高いプロダクトをクライアントに提供しています。一部のツールに共通するこの傾向から、データエンジニアリングとソフトウェアエンジニアリングの間には大きな違いがないという意見を多くの人々が持つようになりました。その結果、データエンジニアリングがまだ「粗削り」であることは、単にデータエンジニアがソフトウェア開発の実践の採用に遅れているからだと考えられています。

しかし、この評価は誤解を招くものです。データエンジニアリングとソフトウェアエンジニアリングは多くの共通のツールと実践を共有していますが、重要な領域で大きく異なります。これらの違いを無視し、データエンジニアリングチームをソフトウェア開発チームのように管理するのは間違いです。本投稿では、データエンジニアリングの独自の課題を解説します。

データパイプラインはアプリケーションではない

ソフトウェアエンジニアリングは主にアプリケーションの開発に関わります(この投稿の文脈では、アプリケーションは非常に広い意味で定義され、ウェブサイト、デスクトップアプリケーション、API、モノリシックなメインフレームアプリケーション、ゲーム、マイクロサービス、ライブラリなどが含まれます)。これら全てが共有する特性は以下の通りです:

  • ユーザーに新たなインタラクションを提供し、価値を提供します。ゲームを遊べたり、ウェブサイトを閲覧できたり、APIは他のソフトウェアで利用できます。
  • 独立した機能が多数存在します。ウェブサイトはページ数を増やすことができ、ゲームはレベルやプレイ可能なキャラクターの数を増やすことができ、APIはエンドポイントを追加することができます。したがって、アプリケーションは決して完全に完成することはありません。
  • アプリケーションが作成する状態量は比較的小さく、大部分のソフトウェアが状態を持たないステートレスであることが目指されます。
  • 他のソフトウェアやサービスとは疎結合です。優れたソフトウェアは任意の環境で独立して機能するべきであり、これがマイクロサービスやコンテナの人気の理由です。

一方、データエンジニアはデータパイプラインの構築に関心があります。データパイプラインはデータを生成元から取得し、変換して消費者が利用する場所に置きます。通常の目標は、新しいデータを継続的に更新するためにパイプラインをスケジュールに従って自動化することです。しかし、アプリケーションとは対照的に、データパイプラインは以下の特徴を持ちます:

  • 直接的な価値を提供しません。パイプラインのユーザーは存在せず、パイプラインが生成するデータセットだけが下流の消費者にとって価値があります。
  • 顧客にとって関連性のある機能は一つだけで、要求されたデータセットを生成することです。そのため、明確な完成の時点がありますが、上流システムやユーザー要件の変更により、パイプラインは継続的なメンテナンスを必要とします。
  • 大量の状態を管理します。パイプラインは、自身が制御しない他のソフトウェアから既存の状態を処理し、自身が制御する状態に変換するために設計されます。多くのパイプラインはデータセットを増分的に構築するため、パイプラインは非常に長期間実行されるプロセスと見なすことができます。
  • 避けられない密結合があります。データパイプラインの目的はまさにデータソースとの結合です。パイプラインはソースが安定し信頼性がある場合でのみ安定した信頼性を持つことができません。

これらの根本的な違いは、ビジネス、ITマネジメント、そしてソフトウェアエンジニアさえもしばしば理解していない、データエンジニアリングの独自の課題をもたらします。それらを見ていきましょう。

パイプラインは構築が完了していなければ顧客にとって無価値である

多くの組織はソフトウェア開発チームを何らかのアジャイルの形で管理しています。このフレームワークの中心的な哲学は、短いイテレーションでソフトウェアを構築しリリースすることで、ソフトウェアが顧客に価値を提供する速度を最大化することです。これにより、可能な限り早く最小限の実行可能な製品(MVP)を提供し、迅速なフィードバックループを確保し、チームが常に最優先の機能に取り組んでいることを保証します。

しかし、この考え方はデータエンジニアリングにそのまま当てはまりません。

データパイプラインは、顧客価値を増加させる小さなイテレーションで開発することはできません。データパイプラインにはMVP相当のものはありません。それは顧客が求めるデータセットを生成するか、しないかのどちらかです。

したがって、データパイプラインの開発はアジャイルフレームワークにきれいに収まりません。複雑なデータパイプラインは単一のユーザーストーリーに対応しますが、通常は完了するために複数のスプリントを必要とします。非技術的な管理者はこの点をあまり考慮せず、データエンジニアをスクラムチームに無理に組み込もうとします。結果として、ユーザーストーリーはタスク、例えば「APIコネクタの構築」や「取り込みロジックの構築」に置き換えられ、スクラムボードはマイクロマネジメントの道具になってしまいます。

管理者が自分が管理するものの本質を理解していないと、彼らは不適切で実行不可能な決定を下す傾向があります。パイプライン開発の進行が遅いことに苛立ったマネージャーがデータエンジニアに対して、顧客が「一部のデータを使って作業を始められるように」と、データセットを列ごとに徐々に構築するように要求したことがありました。複雑なパイプラインやデータサイエンスの経験を持つデータエンジニアなら、この要求が荒唐無稽であると思うでしょう:

理由1) 部分的なデータセットは比例的な利用価値を持たない

データセットが10列中9列を含んでいる場合、その利用価値は90%なのでしょうか?それは省略された列がどれであるかによります。 - データサイエンティストがデータに基づいて予測モデルを構築しようとしているが、欠けている列が予測したいラベルまたは値である場合、そのデータセットの利用価値は0%です。 - 列がラベルと無関係なランダムなメタデータである場合、利用価値は100%かもしれません。

最も一般的なのは、列がラベルと相関する可能性があるかどうかをフィールドごとに確認することです。これはまさにデータサイエンティストが実験を通じて見つけ出したいことです。そのため、データサイエンティストは可能な限り完全なデータセットを手に入れて、実験を始め、探索し、モデルを徐々に最適化したいと思っています。部分的なデータセットを提供しても追加のフィールドが利用可能になった時点で、実験と最適化を再度行う必要があります。

理由2) パイプラインの開発時間はデータセットのサイズとは関連しない

たとえ顧客がデータセットの半分で満足するとしても、それを作り出すのに全体のデータセットを作るのに必要な時間の半分を必要とするわけではありません。データパイプラインは、それぞれが列を生成する独立したジョブで構成されているわけではありません。複数の列が同じソースから派生している場合、パイプライン構築はほぼ同じ作業量です。

理由3) データセットを構築するための時間と経済的コストはそのサイズに比例する

データセットが(行と列の両方の意味で)大きいほど、それを構築し更新するのにかかる時間も長くなります。巨大なデータベースの単一レコードを編集することは些細で早い作業ですが、通常、分析用データセットを変更することは、列の追加、列全体の変更など、何千、何百万という行の更新を含みます。データの変更を処理する方法はテーブルごと上書きしたり、該当する列や行のみ更新処理するなどいくつか手段はありますが、どちらも簡単ではありません。

結論:部分的に完成したパイプラインを本番環境にデプロイすることは無駄である

部分的に完成したパイプラインを本番環境にデプロイすることは、顧客にとって有益でなく、計算資源を無駄にし、パイプラインを構築するエンジニアの業務を複雑にします。DevOpsとアジャイルの原則をそのままパイプライン構築作業に適用して、増分的な変更と頻繁なデプロイを推奨しても、それはデータの慣性を単純に無視しているだけです。

パイプライン開発におけるフィードバックループは極めて遅い

新しい機能を迅速に作成したり、ソフトウェアのバグを修正したりするためには、開発者が書いたコードが正しく、ソフトウェアを正しい方向に進めるかどうかを早急に知らせるフィードバックが必要です。

ソフトウェア開発では通常、ユニットテストのスイートを使用して達成されます。これは開発者がローカルで実行し、ソフトウェアの各コンポーネントが(依然として)意図した通りに機能するかどうかを確認するためのものです。ユニットテストは高速であり、外部システムとは一切関連せず、状態に依存することもありません。関数、メソッド、クラスを独立してテストができます。このようにして、ソフトウェア開発者は開発中に迅速なフィードバックを得て、プルリクエストを出すときには、コードが意図した通りに機能することをかなり確信することができます。他のシステムとのインタラクションをテストする必要がある場合、ユニットテストよりは遅いですが CIパイプラインはインテグレーションテストを実行することも可能です。

データパイプラインはほとんどユニットテストされません。データパイプラインは通常、単純にデプロイすることでテストされます。通常は最初に開発環境に向けてです。これにはビルドとデプロイのステップが必要で、その後エンジニアは一定時間パイプラインを監視して、意図した通りに機能するかどうかを確認しなければなりません。もしパイプラインが冪等でないなら、再デプロイする前に、前回のデプロイが残した状態をリセットするための手動の介入が最初に必要かもしれません。このフィードバックサイクルは、ユニットテストを実行することと比較して非常に遅いです。

では、なぜ単にユニットテストを書かないのでしょうか?

理由1) ユニットテストできない側面でデータパイプラインは失敗する

データパイプラインにおいてユニットテストできる自己完結型のロジックは通常、限られています。ほとんどのコードはシステム間のグルーとダクトテープのようなもので、ほぼ全ての失敗は、システム間の不適切なインターフェースや予期しないデータがパイプラインに入った結果発生します。

システム間のインターフェースはユニットテストすることができません。なぜなら、これらのテストは孤立して実行することができないからです。外部システムをモック化することは可能ですが、これはデータエンジニアが理解する範囲でしか外部システムの動作を保証できません。

データを生成するシステムは多くの場合、一貫性のある品質の高いデータを提供してくれません。データの予期せぬ内容や構造はパイプラインを壊すか、少なくとも不正確な結果を生み出します。低品質なデータソースに対抗するための一般的な戦略は、読み取り時にスキーマを検証することです。しかし、これはデータの誤った内容や微妙な「データバグ」を防ぐことはできません。例えば、時系列は夏時間を正しく扱っていますか?期待されるパターンに適合しない列内の文字列はありますか?測定値を表す列は実際に意味のある値ですか?これらの質問はいずれもユニットテストできるパイプラインロジックに関連していません。

理由2) ユニットテストはパイプラインロジックよりも複雑

ユニットテストが書けたとしても、テストコードはパイプラインロジックよりも複雑です。なぜなら、それは開発者が代表的なテストデータ、さらには期待される出力データを作成しなければならないからです。

さらに、テスト目的である「この関数は意図した通りに機能しますか?」という問いから「このテストデータは私の本物のデータを適切に代表していますか?」という問いに関心が変わってしまいます。一般的なユニットテストは理想的には入力パラメータの良い部分集合をカバーするべきですが、データセットを変換する関数では例えばデータフレームで入力が表現され、パラメータ空間は非常に大きなものとなってしまいます。

結論:パイプラインの開発は遅い

データパイプラインに対する信頼性のあるフィードバックを得る最善の方法は、それをデプロイして実行することです。これはローカルでユニットテストを実行するより遅く、フィードバックを得るのに時間がかかります。その結果、パイプラインの開発、特にデバッグの段階では、面倒くさく遅いということになります。

全体のパイプラインを実行するよりも早い統合テストを考えることもできますが、ローカル環境では関連するソースシステムへの直接アクセスを持っておらず、このテストはパイプラインと同じ環境でしか実行することができず、デプロイを必要とします。これは高速なフィードバックを得るためのテストを書くという主旨を大幅に逸脱します。

「データ契約(Data Contracts)」は今、無謀なデータ生成者への対処法として大流行しています。パイプラインに入るデータに対して信頼を持つことができれば、パイプラインの開発から多くの不確定性を取り除き、壊れにくくできます。しかしデータ生成者にはこれを遵守するインセンティブはなく、この契約を適用することは難しいようです。

さらに、組織は公開APIからデータを引き出して利用したいと思うでしょう。外部パーティとデータ契約を交渉する場合、幸運を祈るしかありません。

パイプライン開発は並列化できない

データパイプラインは、フィードバックサイクルが長くて開発が遅い、単一のユーザーストーリーであることを見てきました。

パイプラインは複数のタスクで構成されているため、一部のマネージャーはプロセスをスピードアップするために、それらを複数の開発者で分担させようとします。残念ながらこれはうまくいきません。パイプライン内でデータを処理するタスクは順序があります。第二段階を構築するためには、第一段階の出力が安定していなければなりません。第二段階を構築することで得られた洞察は、第一段階の改善にフィードバックされます。したがって、パイプラインは特定の開発者が反復して機能改善していかなければなりません。

一部のマネージャーは、これは単にパイプラインが最初から十分に計画されていないのだと反論します。最初にデータがあり、最後に出力されるべきデータは明確です。それなら中間で何を構築する必要があるかは事前に計画できるのではないでしょうか?

データソースの特性が事前に分かっていない限り、パイプライン全体を最初から計画することはできません。契約や文書化がない場合、データエンジニアはデータの特性を発見するために試行錯誤するしかありません。この発見プロセスはパイプラインのアーキテクチャを形成します。ある意味でこれはアジャイルです。ただし、ビジネスステークホルダーが考えるアジャイルの方法とは異なります。

結論と推奨事項

データパイプラインはソフトウェアですが、ソフトウェア製品ではありません。これは、例えるなら顧客が要求した車を製造するための工場です。目的のための手段であり、取り扱いが難しいデータソースから簡単に利用可能なデータセットを作成するための自動化レシピであり、相互に通信するように設計されていないシステム間のダクトテープであり、醜く壊れやすくて高価な解決策です。

データは本質的にソフトウェアとは異なります。これらの違いを認識せず、ソフトウェアチームで機能したからといってデータチームでアジャイルプロセスを強制することは、逆効果になるだけです。

データチームを成功させ、生産性を高めるために何ができるでしょうか?

  • データパイプラインプロジェクトでは、軽量なウォーターフォール(通常、ソフトウェアエンジニアリングでは悪と考えられている)が避けられないと認識してください。開発を始める前に、望ましいデータセットについての要件を明確にし、データ生成者との知識を共有してソースシステムに接続するための多くの会話を顧客と行う必要があります。顧客とデータ生成者の両方が解決策に同意するまで、あまりパイプライン開発に時間を費やさないでください。パイプラインが一度リリースされると、変更費用が高くなってしまいます。
  • データエンジニアにデータソースで実験する時間を与えてください。それまではデータセットが利用可能になる時期についての見積もりは間違っていることを認識してください。
  • パイプラインを複数の開発者に分割しないでください。代わりに、2人以上の開発者が同時にパイプラインに取り組むことを許可してください。ペアプログラミング・エクストリームプログラミング・グループプログラミングとトランクベースの開発を行い、gitブランチの地獄・プルリクエスト・コードレビューを避けることで最大の生産性を確保します。常に二人がかりでコードを検査することで、早期に問題を発見できます。これは特に、フィードバックループが遅いパイプライン開発では特に価値があります。

2023/06/15 Webアプリ開発中: Create React App への環境変数の渡し方

1ヶ月近くブログ更新できていなかったのですが、ここしばらくは一からWebアプリを作ってみようと思い立って業務が終わってからずっと開発をしていました。ようやく最低限の機能開発と、ローカルとAWSでの差異を加味した環境構築が終わったのでブログにまとめてみます。

作りたいのは簡単な体調管理システムで、睡眠記録、その日行ったこと、気分や体調などを記録することで傾向を確認したり、ある程度データが溜まったら機械学習をさせて特徴量の分析をさせたいと思っています。武井壮さんが Youtube で解説されているようなものを想定しています:

武井壮の体調管理法その1 - YouTube

日時から天気や気圧情報なんかも外部APIを使ってデータを追加することもできそうです。

データの性質的に個人情報に該当しますし、健康情報という意味では要配慮個人情報にも該当する可能性があるかな…と思い、一般公開は予定していません。

ごく簡単ですが開発の最初にER図でどういうデータを記録しようかを検討しました:

体調管理システムのER図

以下の内容を学ぶことを裏目標としています:

  • 一からのWebアプリ開発(Node.js + React)
  • Docker でコンテナ化し、AWS ECS 上でサービス稼働させる
  • AWS は Terraform で構成管理
  • ローカル、AWSでどちらも動くように環境差異を吸収する

どれも業務で使ったことはあるのですが、すでに環境が出来上がってから参画することが多かったので、一から全部自分で構築するのは実は初めてです。特にフロントエンドの開発は何年もやっていないので本当に久しぶりです。

ChatGPT や GithubCopilot も活用しています。まだ業務で利用する許可が得られていない企業も多いと思いますが、特に環境構築初期に曖昧な疑問を ChatGPT にぶつけられるのが非常に助かりました。生成されたコードは割と間違っていることも多いのですが、どういったライブラリがあるかを知って公式ドキュメントを辿るきっかけにしたり、実装方針のあたりをつけるには十分でした。

ハマったところを書き出してみます。今日は環境変数について。

コンテナに環境変数を渡す

環境差異を吸収するために、ローカルでは localhost, AWS 上では ELB のドメイン名上で動くようにしたかったのですが、Create React App はビルド時に環境変数を埋め込んでしまうため、動作環境の環境変数を読み込んでくれません。

create-react-app.dev

The environment variables are embedded during the build time. Since Create React App produces a static HTML/CSS/JS bundle, it can’t possibly read them at runtime. To read them at runtime, you would need to load HTML into memory on the server and replace placeholders in runtime, as described here. Alternatively you can rebuild the app on the server anytime you change them.

この記載の通り、ECS のタスク定義の environment は読みこみませんでした。最適解かは分かりませんが、docker-compose.yml に args として環境変数を渡し、Dockerfile でそれをENV に与えることでうまくイメージ構築ができました:

version: '3'
services:
  my-server:
    ...
  my-client:
    image: ${REPO_NAME}/my_client:${TAG}
    build:
      context: ./client
      dockerfile: Dockerfile
      args:
        - "REACT_APP_MY_SERVER_URL=${REACT_APP_MY_SERVER_URL}"
    depends_on: 
      - my-server
    ports:
      - 80:80
FROM node:18.16-slim as build-deps

ARG REACT_APP_MY_SERVER_URL
ENV REACT_APP_MY_SERVER_URL=${REACT_APP_MY_SERVER_URL}

ビルドで読み込む環境変数を明示的に分けることでそれぞれの環境に適したイメージが構築でき、 process.env.REACT_APP_LILYCHUM_SERVER_URL で環境変数を参照できます:

$ docker-compose --env-file ./.env_local build
$ docker-compose --env-file ./.env_aws build