データ統合パイプラインのビルドベストプラクティス開発のベストプラクティス

注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。

開発のベストプラクティス

このガイドは、変換を開発しているパイプライン開発者向けのガイドラインを提供することを目的としています。 一般的な 推奨事項は、プロジェクトマネージャーやプラットフォーム管理者にも興味深いもので、一般的なクリーンなパイプラインの高次原則に焦点を当てています。

一般的なベストプラクティス

パイプライン開発はソフトウェア開発です

一般的なソフトウェア開発からの多くのベストプラクティスは、ユーザーのデータパイプラインを構成する変換を定義するのと同様に適用されます。以下に、このアプローチを示すいくつかの一般的な実践を挙げます:

  • 認知負荷を減らす: 可能な限り、考える必要を減らすこと。複雑な関数やAPI呼び出しを使用する必要があり、それが明らかでない場合は、明確なドキュメンテーションが必要です。
  • 未来の自分に優しくする: パイプラインはユーザーのデータエンタープライズの基盤です。長期的かつ高いビジョンを持って計画と実装を行います。
  • Don't Repeat Yourself (DRY): コードや概念の複製はメンテナンスを増やし、微妙なエラーを引き起こします。代わりに、頻繁にリファクタリングを行い、自身のライブラリーやパッケージを構築・公開してクロスリポジトリ使用を行い、複製を最小限に保つこと。
  • テクノロジーデットを避ける: 長期的な思考に付随するコラリーであり、テクノロジーデットはプロジェクト固有の要求の期限に直面して簡単に蓄積されますが、必ず噛みつきます。
  • 規約が重要: 先例を設けてそれに従うこと。これにより認知負荷が減り、コードリポジトリ間の可読性が向上します。たとえば、行とデータセットの名前は通常 snake_case で書かれています - この規約に従うことで、他の人がユーザーの素晴らしいデータセットを参照したい場合、それが awesome_dataset であることを知ることができます。AwesomeDatasetAwesome_Dataset ではありません。
  • 少なければ少ないほど良い: 多くの小さな単位を持つシステムは、少数の大きな単位を持つシステムよりもメンテナンスや推論が容易です。したがって、以下に偏るべきです:
    • タイトにスコープされた Foundry プロジェクト
    • 一緒に連鎖した小さな変換
    • 短い関数 - そして変換におけるヘルパー関数

アンチパターン

  • 上書き vs. 削除: 他のファイルシステムとは異なり、Foundryのリソースとデータセットはファイル自体ではなく他の作成物に接続されています。したがって、パイプラインやデータを反復処理する場合、その種類のデータを根本的になくしたい場合のみ削除を行います。たとえば、不正なデータをデータセットに書き込んだトランザクションがある場合、データセットを削除しないでください。代わりに、新しい SNAPSHOT トランザクションを作成して前のものを上書きします。データセットを削除しようとすると、新しいデータセットを同じ場所に作成するよりも多くの問題が発生する可能性があります。
  • 循環依存関係を導入しない: 変換のデータセット出力を他の変換の入力として使用する予定の場合、コードに循環依存関係を導入していないことを確認する必要があります。Foundryのビルドオーケストレーションレイヤーは、現在使用しているブランチでループを設定することを防ぐようにし、コードリポジトリのブランチはループが検出された場合にチェックに失敗します。

Foundryは開発中のブランチで循環依存関係をチェックしますが、コードを書く際にすべてのブランチでチェックを実行するわけではありません。たとえば、マスターブランチ上にのみ存在するオントロジーの書き戻しデータセットがある場合などです。他のブランチで循環依存関係が検出された場合、Foundryはフィーチャーブランチを循環依存関係を含むブランチとマージしようとするときにチェックに失敗します。

プロジェクトフォルダー構造

複数のプロジェクトを通じてデータの全流れを組織化する包括的なモデルの説明については、Recommended Project structure を参照してください。

ベストプラクティス

  • プロジェクトレベルでの権限管理: プロジェクト内でさらに権限を分割する必要があると予想される場合、プロジェクトをさらに小さな部分に分解するべきかどうかを考慮してください。権限の適用と管理の概念と実践について詳しく学ぶ。
  • プロジェクトをタイトにスコープする: 「機能の侵食」を避け、ユーザーのプロジェクト内に関連性の低いリソースやユースケース固有のロジックを追加するのを避けます。
  • 意味のあるフォルダー名を使用する: ユーザーのチーム外での消費や日々の繰り返しと開発のためにプロジェクトを設計することを覚えておいてください。最低限、/Documentation フォルダーと /Output フォルダーを持つべきです。異なるユースケースやワークフロープロジェクトでは、より具体的な名前が必要になる場合がありますが、ユーザーのプロジェクト構造と命名スキームが訪問者のための道しるべであることを常に考慮してください。

アンチパターン

  • ホームフォルダーでの作業: ユーザーの個人フォルダーは開発の場所ではありません - アクセス制御は厳格で、共有が難しいです。ホームフォルダーでの構築ではなく、実験や一時的な作業のためにユーザーのプロジェクトに /scratch フォルダーを作成することを検討してください。

命名

ベストプラクティス

  • 規約に従う: ユーザーの組織やFoundryインスタンスの規約に従って、行、データセット、リポジトリ、ファイル、その他のリソースの名前を付けます。
  • 説明的な名前を選ぶ: リソースの名前を付ける目的や内容を読者に向けて指向する2~3語の名前を付ける時間をかけます。名前の独特な部分を最初に置くことを検討します。これは、ドロップダウンで全文が切り取られる長い名前に役立ち、コードベースやファイルのリストをスキャンするときにすぐに参照を区別するのに役立ちます。略語の使用を避けます。

アンチパターン

  • 暗号化された名前: 単に数値を増やす名前(つまり、 dataset1dataset2 など)や、単一の文字の名前を選ぶと、コードの読みやすさが低下し、新しい開発者がユーザーの作業に取り組むことがはるかに困難になります。
  • パスのみで区別される名前: これは許容される場合もあります。例えば、/raw/my_important_dataset とその後の /clean/my_important_dataset がある場合ですが、多くの場合、この命名パターンはデータセット名自体が目立つビューで混乱を生む可能性があります。覚えておいてください、系譜は追跡され、容易に見えるので、ユーザーはこのような「状態」をデータセットの名前に埋め込む必要はありません。

データ型

ベストプラクティス

明示的に列の型をキャストする: Datasource Project で作業している場合、データ接続からのスキーマ推論が正しい値を選択していたとしても、 rawclean 変換で列の型を明示的にキャストします。これにより、列の型が変更された場合や無効な値が同期中に誤った推論を作成した場合に、ソースシステムからの破壊的な変更を検出するのに役立ちます。

パターン

「Time Only」データ型のタイムスタンプを使用する: Sparkには、「10:59:00」のような値を持つフィールドのための時間のみのデータ型がありません。Sparkのタイムスタンプ型に付属する時間関数を利用するためには、値を秒にキャストしてから -2208988800 を加え、それをタイムスタンプにキャストして年0にすることで対応します。もしくは、それを文字列として残し、ユーザーが必要に応じて解析することも可能です。

アンチパターン

  • すべての数値フィールドを数値データ型にキャストする: 例えば、航空機IDの列、545972314のようなものを考えてみてください。それらは整数に見え、ソースシステムでは実際に整数である可能性もあるので、これらを整数列にキャストすることは誘惑的です。しかし、これには大きな欠点があります:

    • 先頭のゼロが意味を持つ場合、つまり123と0123が両方とも合法的なIDで、区別すべきである場合、これはエラーを引き起こします。
    • IDがUIで望ましくない形式でフォーマットされる可能性があります(例えば、545.01,234、右寄せなど)。
    • IDが長すぎると、MAX_INTの問題に直面する可能性があります。
    • これらの値に対して数値関数は適用されません(例えば、2つの航空機IDを足すことはありません)。規則は、それらを算術に適用するのが適切であれば、数値フィールドを数値データ型にキャストすることのみで、それ以外の場合はそれらを文字列としてキャストするべきであるとするべきです。
  • 異なるタイムゾーンでタイムスタンプを格納する: — Sparkのタイムスタンプはタイムゾーンに依存しません。それらは内部的にUTC(別名 Zulu、GMT)で格納されています。タイムゾーンの表示はフロントエンドで行うことが期待されています。

    • 表示用に特定のタイムゾーンが必要な場合は、PySparkの from_unixtime() 関数を使用して、適切なタイムゾーンの文字列を格納します。
    • あるデータソースのタイムスタンプがUTC以外のタイムゾーンにある場合は、PySparkの to_utc_timestamp() 関数を使用してそれらをUTCに正規化します。

コードコメント

アンチパターン

  • コミットでコードをコメントアウトする: 変更を行う際、古いコードをコメントアウトして参照用に残すか、後で元に戻す必要がある場合などになることがあります。反復処理中にコメントを使用することはできますが、コメントアウトしたステートメントを持つコードをコミットしないでください。そうすると、クラフトが増えて可読性が低下します。古いコードは、以前のコミットで簡単に見つけることができます。

  • 作成者の詳細と日付のコメント: これらはコミット/ gitリポジトリで自動的に追跡されます。手動でコメントをすると、更新されない可能性があり、クラフトを作り出します。

  • 過度に冗長なコメント: コメントは意思決定の背後にある理由を共有すべきであり、ロジック自体を説明すべきではありません。「自己文書化」コードを書くことを目指し、一連のステートメントが理解しにくい場合は、それがリファクタリングと単純化の明確なサインであると努力します。

リポジトリの運用方法

ベストプラクティス

  • masterブランチを保護する: チームで開発している場合や、長期間継続する個人プロジェクトであっても、masterブランチを保護し、GitFlowやお好みの開発ワークフローを実践してください。コードがmasterに移動する際に、レビューとテストが行われていることを確認することが重要です。

  • コミットメッセージを書く: コミットメッセージは、リポジトリのすべてのアクティビティのログです。変更の有用な説明を記述するために時間をかけてください:

    • コードレビュアーは、ユーザーのコミットを見て、ワークフローや行われた変更の内容を把握することができます。
    • 変更を元に戻したい場合、一目で探していたコミットを見つけることができると、非常に簡単です。
    • ビルドボタンをクリックすると、タイムスタンプ付きのコミットメッセージが自動生成されますが、これを使用しないでください。まずコミットをクリックし、コミットメッセージを書いてからビルドをクリックしてください。
  • ブランチを整理する: 長期間維持されるリポジトリでは、ブランチが蓄積されることがあります。ブランチの開発が放棄された場合、特にブランチが別のブランチにマージされた場合は、ブランチを削除することで整理し、どのブランチがアクティブに開発されているかが分かりやすくなります。

  • リポジトリをアップグレードする: プロンプトに従って、リポジトリ内の言語バンドルをアップグレードする手順に従ってください。このプロセスでは、アップグレードを含むプルリクエストがアクティブブランチに開かれます。バージョンアップがコードに影響を与えないことを確認するために、アップグレードブランチでパイプラインのビルドを実行しても構いません。ただし、これらのアップグレードに常に最新の状態を保つことで、他の場所で見られるエッジケースに遭遇しないようにすることができます。これらのケースは、アップグレードされたバージョンでパッチされます。

  • コードレビューを実践する: トランスフォーメーションを開発するためにチームメイトと協力して、プルリクエストプロセス中にコードレビューの実践を導入してください。コードレビューのベストプラクティスについての私たちの考え方を共有していますが、データ変換コードのレビューにも多くの概念が等しく適用されます。

パターン

リポジトリ間でコードを共有する: Foundryのリポジトリは、さまざまな理由でプロジェクトレベルで動作しますが、パイプライン間で再利用できるロジックがしばしば存在します。これにはいくつかの利点があります:

  • DRYに従った一般的なコードの再利用。

  • データ基盤の異なる領域でフォーク/矛盾したロジックを回避する。

  • 基盤パイプラインを使用するのが理想的なパイプラインがあるかもしれませんが、SLAやパフォーマンス要件がはるかに厳しい場合があります。このような場合、解決策は、ロジックを共有することですが、トランスフォーム/データセットは共有しないことで、重要なパイプラインが事前にフィルター処理されたデータセットに依存したり、トランスフォームが少なくなったり、ビルドスケジュールが異なったりすることができます。

  • コードリポジトリは、これを実現するための優れた方法です。例えば、Pythonトランスフォームを使用する場合、ライブラリは自分自身をCondaチャンネルに公開し、他のリポジトリがそれらを利用できるようにすることができます。Pythonライブラリの共有に関するドキュメントを参照してください。

  • 共有リポジトリのさらなる利点はセマンティックバージョン管理です。共有リポジトリは、コミットにバージョンをタグ付けすることができます(例:1.0.0, 1.0.1, 2.0.0)、消費者ライブラリは、新しいバージョンをどのように取得するかを選択することができます。例えば、リポジトリは、最新バージョン(上記の2.0.0)を取得するか、特定のバージョン(例えば、1.0.1)のみを取得し、新しいバージョンの取得を手動で決定するまで保留することができます。後者のケースは、パイプラインが重要で、パイプラインのオーナーが共有リポジトリの変更を承認/選択する機会を得たい場合に特に有益です。

  • リリース: 同じように、チームがパイプラインの明確なリリーススケジュールを持ちたい場合、ステージングインスタンスや長期間維持される開発ブランチを避けるための1つのオプションは、ロジックを共有リポジトリ内の関数に分割し、セマンティックバージョンを使って消費リポジトリをメジャーリリース(1.0.0、2.0.0、または.0.0)に保持することです。これにより、開発者はロジックを継続的に反復処理し、中間リリースをタグ付けすることができます。ただし、master上でライブにならないようにします。さらに、消費リポジトリのブランチでは、開発者は常に中間バージョンを取得することができます。ただし、リリース日前にマスターにマージしない限り、中間バージョンを取得することができます。

ユニットテスト

ユニットテストは、コード品質の向上と維持に役立つ人気のある方法です。ユニットテストでは、ソフトウェアの小さくて個別なコンポーネント(「ユニット」)が、個別に、独立して、自動化された方法でテストされます。

  • Pythonユニットテスト: PythonトランスフォームリポジトリのCIチェックの一部としてpytestユニットテストを有効にするには、Pythonユニットテストの手順に従ってください。

  • Javaユニットテスト: Javaトランスフォーム用のユニットテストを設定する手順は、Javaユニットテストのドキュメントに記載されています。

ベストプラクティス

ユニットテストは、次の条件を満たす必要があります:

  • 一度に1つのロジックのみをテストする;
  • 軽量である(言語固有の指示に従ってメモリ使用量を減らす方法についてのヒントを参照してください);
  • 関数が機能するために追加の入力に依存しない; そして
  • 互いに依存しない、または呼び出さない。

ヘルスチェック

ベストプラクティス

データの健康状態をチェックする: パイプラインの一部が完了した後、スケジュールを設定し、気にかけなくなることがよくあります。しかし、ロジックが正しい場合でも、入力データがビルドに影響を与える方法で変更される可能性があります。これにより、パフォーマンスが低下したり、データ規模が増加したり、ビルドが完全に失敗したりする可能性があります。データセットのサイズやビルド時間に基本的なチェックを設定し、アラートを設定しなくても、これらの主要な指標の時間経過による状況を確認できます。たとえば、データセットのサイズがどれだけ増加しているかや、データセットの平均ビルド時間を確認することができます。利用可能なヘルスチェックとそれらの設定方法について詳しく読んでください。

パターン

ヘルスチェックを拡張する: ほとんどの場合、デフォルトのヘルスチェック設定で十分なはずです。ただし、さらなる柔軟性が必要な場合は、パイプラインに1つ以上の派生したヘルスチェックデータセットを追加してください。このデータセットのトランスフォームは、入力データセット(検証するデータセット)の妥当性を判断するために任意のロジックを実行し、その後、シンプルなヘルスチェック(Allowed Valueなど)がデータセットが有効かどうかを報告できるように、データを出力形式に合わせます。

このデータセットのスケジュールを、入力データセットが更新されるたびにビルドされるように設定すると、包括的なヘルスチェックの追加セットが得られます。

スケジューリング

ベストプラクティス

スケジューリングのベストプラクティスを確認してください。