Delta Live TablesとApache Kafkaを用いた低レーテンシーストリーミングデータパイプライン

[Declarative Streaming Data Pipelines with Delta Live Tables and Apache Kafka \- The Databricks Blog](https://www.databricks.com/blog/2022/08/09/low-latency-streaming-data-pipelines-with-delta-live-tables-and-apache-kafka.html)の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

[Delta Live Tables \(DLT\)](https://www.databricks.com/jp/product/delta-live-tables)は、バッチやストリーミングデータに対する高信頼なデータパイプラインを作成し、大規模インフラストラクチャを完全に管理するためにシンプルな宣言型のアプローチを使用する初めてのETLフレームワークです。多くのユースケースでは、アクション可能な洞察をニアリアルタイムのデータから導き出す必要があります。Delta Live Tablesは、[Apache Kafka](https://kafka.apache.org/)、[AWS Kinesis](https://aws.amazon.com/kinesis/)、[Confluent Cloud](https://www.confluent.io/confluent-cloud)、[Amazon MSK](https://www.youtube.com/watch?v=HtU9pb18g5Q)、[Azure Event Hubs](https://docs.microsoft.com/en-us/azure/event-hubs/)のようなイベントバスからデータを直接取り込むことで、低レーテンシーを必要とするそのようなユースケースをサポートするために**低レーテンシーのストリーミングデータパイプライン**を実現します。

本書では、ストリームを取り込むために必要なPythonコードを提供しながらも、Apache KafkaとDLTを用いてウォークスルーを行います。推奨のシステムアーキテクチャが説明され、途中では検討の価値があるDLTの関連設定を探索していきます。

# ストリーミングプラットフォーム

イベントバスやメッセージバスは、メッセージのプロデューサーをコンシューマーから分離します。よくあるストリーミングのユースケースには、すべてのユーザーインタラクションがApache Kafkaのイベントとして格納される、ウェブサイトのナビゲーションによるクリックスルーのコレクションがあります。Kafkaからのイベントストリームは、リアルタイムのストリーミングデータ分析に活用されます。複数のメッセージコンシューマーは、Kafkaから同じデータを読み込むことができ、聴衆の興味やコンバージョン率、離脱の理由を学ぶためにデータを活用することができます。また、ユーザーインタラクションによるリアルタイムのストリーミングイベントは、多くの場合、請求データベースに格納される実際の購入と関連づけられる必要があります。

## Apache Kafka

[Apache Kafka](https://kafka.apache.org/)は、オープンソースのイベントバスです。Kafkaは、特定の期間でメッセージがバッファされる、追記のみのイベントの分散ログであるトピックの概念を使用しています。Kafkaのメッセージは消費されても削除されませんが、これらは永久に保存されるわけでもありません。Kafkaのメッセージ保持期間はトピックごとに設定され、デフォルトは7日間です。有効期限が切れたメッセージは最終的には削除されます。

本書では、Apache Kafkaにフォーカスします。しかし、ここで議論されるコンセプトは、[多くのイベントバスやメッセージングシステム](#他のイベントバスやメッセージングシステム)にも適用することができます。

# ストリーミングデータパイプライン

データフローパイプラインにおいては、Delta Live Tablesとそれらの依存関係は標準的なSQLのCreate Table As Select (CTAS)文とDLTのキーワード`live`で宣言することができます。

PythonでDLTを開発する際には、Delta Liveテーブルを作成するために`@dlt.table`デコレーターを使用します。パイプラインのデータ品質を保証するために、DLTは不正なレコードを持つパイプラインの挙動を定義するシンプルなSQLの制約である[エクスペクテーション](https://docs.databricks.com/data-engineering/delta-live-tables/delta-live-tables-expectations.html)を使用します。

ストリーミングワークロードは、多くの場合予期できないデータボリュームを伴うので、Databricksでは、不要なインフラストラクチャをシャットダウンすることでコストを削減しつつも、全体的なエンドツーエンドのレーテンシーを最小化するために、データフローに対して[強化オートスケーリング](https://qiita.com/taka_yayoi/items/7dcd8730fbb097952caa)を適用します。

**Delta Live Tables**は、それぞれのパイプラインの実行において正確に一度のみ、適切な順序で完全に再計算されます。

逆に、**ストリーミングDelta Live Tables**はステートフルであり、最後のパイプライン実行以降に追加されたデータのみをインクリメンタルに処理します。ストリーミングライブテーブルを定義するクエリーが変更された際、新たなクエリーに基づいて新規のデータは処理されますが、既存データは再計算されません。ストリーミングライブテーブルは常にストリーミングソースを使用し、Kafka、Kinesis、Auto Loaderのような追加のみのストリームに対してのみ動作します。ストリーミングDLTはSpark構造化ストリーミングをベースとしています。

例えば、非常に大量のデータを取り扱うワークロードと低レーテンシー要件というように、複数のストリーミングパイプラインをチェーンすることができます。

## ストリーミングエンジンからの直接取り込み

Pythonで記述されたDelta Live Tableは、Spark構造化ストリーミングを用いることでKafkaのようなイベントバスから直接データを取り込むことができます。コンプライアンスの問題を回避し、コストを削減するためにKafkaのトピックの保持期間を短くすることで、Deltaが提供する安価かつ弾力性のあるガバナンスの効いたストレージのメリットを享受することができます。

パイプラインにおける第一歩として、ブロンズ(生データ)テーブルにデータを取り込み、重要なデータを削除してしまう様な複雑な変換処理を避けることをお勧めします。他のDeltaテーブルと同じ様に、ブロンズテーブルは履歴を保持し、GDPRや他のコンプライアンスタスクを実行することを可能とします。
![](https://www.databricks.com/wp-content/uploads/2022/08/db-276-blog-img-1.png)
*Apache Kafkaからのストリーミングデータの取り込み*

PythonでDLTパイプラインを記述する際には、DLTテーブルを作成するために`@dlt.table`のアノテーションを使用します。PythonでストリーミングDLTであることを示すための特別な属性はありません。ストリームにアクセスするには、シンプルに`spark.readStream()`を使用します。以下にKafkaのトピックを消費する`kafka_bronze`という名前のDLTテーブルを作成するサンプルコードを示します。

```py:Python
import dlt
from pyspark.sql.functions import *
from pyspark.sql.types import *

TOPIC = "tracker-events"
KAFKA_BROKER = spark.conf.get("KAFKA_SERVER")
# subscribe to TOPIC at KAFKA_BROKER
raw_kafka_events = (spark.readStream
    .format("kafka")
    .option("subscribe", TOPIC)
    .option("kafka.bootstrap.servers", KAFKA_BROKER)
    .option("startingOffsets", "earliest")
    .load()
    )

@dlt.table(table_properties={"pipelines.reset.allowed":"false"})
def kafka_bronze():
  return raw_kafka_events
```

### pipelines.reset.allowed

Deltaはデータを永久的に保持するのとは異なり、イベントバスは通常、特定の期間の後にメッセージを破棄することに注意してください。

このため、DLTパイプラインのフルリフレッシュを実行する際に、Kafkaのソースデータがすでに削除されているという状況が起こり得ます。この場合、メッセージングプラットフォームからすべての履歴データがバックフィルされない場合があり、DLTテーブルでデータの欠損が起こり得ます。データ削除を防ぐためには、以下のDLTテーブルのプロパティを使用します。

```
pipelines.reset.allowed=false
```

`pipelines.reset.allowed`を`false`に設定することで、テーブルのリフレッシュをガードしますが、テーブルに流れ込む新規データやテーブルへのインクリメンタルの書き込みは許可します。

### チェックポイント

あなたが経験豊富なSpark構造化ストリーミング開発者であれば、上述のコードでチェックポイント作成が無いことに気づいたでしょう。Spark構造化ストリーミングにおいては、どのデータが処理に成功のか失敗したのかに関する進捗情報を永続化するためにチェックポイントの作成が必要となり、失敗したクエリーを失敗した時点から再開するためにこのメタデータを使用します。

Spark構造化ストリーミングでは一度のみの処理を保障するために障害復旧のためにチェックポイントが必要となりますが、DLTは手動の設定や明示的なチェックポイントを必要とすることなしに、自動で状態をハンドリングします。

### DLTパイプラインにおけるSQLとPythonの混在

複数のノートブックからDLTパイプラインを構成することができますが、一つのDLTノートブックは、全体がSQLかPythonで記述される必要があります(単一のノートブックでセルごとに異なる言語を記述できるDatabricksノートブックとは異なります)。

このため、SQLが好みであるならば、PythonのノートブックでApache Kafkaからデータ取り込みのコーディングを行い、別のSQLノートブックでデータパイプラインの変換ロジックを記述することになります。

### スキーママッピング

メッセージングプラットフォームからデータを読み取る際、データストリームは不明瞭でありスキーマを提供する必要があります。

以下のPythonサンプルでは、フィットネストラッカーからのイベントのスキーマ定義と、どのように[Kafkaメッセージの値の部分がこのスキーマにマッピング](https://docs.databricks.com/spark/latest/structured-streaming/kafka.html)されるのかを示しています。

```py:Python
event_schema = StructType([ \
    StructField("time", TimestampType(),True)      , \
    StructField("version", StringType(),True), \
    StructField("model", StringType(),True)     , \
    StructField("heart_bpm", IntegerType(),True), \
    StructField("kcal", IntegerType(),True)       \
  ])


# temporary table, visible in pipeline but not in data browser, 
# cannot be queried interactively
@dlt.table(comment="real schema for Kakfa payload",
           temporary=True)


def kafka_silver():
  return (
    # kafka streams are (timestamp,value)
    # value contains the kafka payload
        
    dlt.read_stream("kafka_bronze")
    .select(col("timestamp"),from_json(col("value")
    .cast("string"), event_schema).alias("event"))
    .select("timestamp", "event.*")     
  )
```

## メリット

DLTでメッセージブローカーから直接ストリーミングデータを読み込むことで、アーキテクチャ上の複雑性を最小化し、データはメッセージングブローカーから直接ストリーミングされ、中間ステップを含んでいないので、低レーテンシーを実現することができます。

# 中間クラウドオブジェクトストレージを用いたストリーミング取り込み

特定のユースケースにおいては、Kafkaコネクターを用いて、中間層としてクラウドオブジェクトにストリーミングデータを保存し、Apache Kafkaからデータをオフロードしたいと考えるかもしれません。Databricksワークスペースでは、クラウドベンダー固有のオブジェクトストアは、クラウドに依存しないフォルダーとしてDatabricksファイルシステム(DBFS)にマッピングされます。データがオフロードされたら、[Databricksのオートローダー](https://qiita.com/taka_yayoi/items/df143647dcf5942b51c6)でファイルを取り込むことができます。
![](https://www.databricks.com/wp-content/uploads/2022/08/db-276-blog-img-2.png)

Auto Loaderは1行のSQLコードでデータを取り込むことができます。DLTテーブルへのJSONファイルの取り込みの構文を以下に示します(可読性のために2行で記述しています)。

```sql:SQL
-- INGEST with Auto Loader
create or replace streaming live table raw
as select * FROM cloud_files("dbfs:/data/twitter", "json")
```

Auto Loader自体がストリーミングデータソースであり、新たに到着したファイルは1回のみ処理され、当該テーブルにインクリメンタルにデータが取り込まれることを示すキーワード`streaming`が`raw`テーブルに指定されていることに注意してください。

ストリーミングデータをクラウドオブジェクトストアにオフロードすることで、あなたのシステムアーキテクチャに追加のステップを導入することになるので、エンドツーエンドのレーテンシーを引き上げ、追加のストレージコストを発生させます。クラウドオブジェクトストアにイベントデータを書き込むKafkaコネクターは管理する必要がるために、オペレーション上の複雑性を増加させることに注意してください。

このため、[上述した様に](#ストリーミングエンジンからの直接取り込み)Spark構造化ストリーミングを用いてDLTからイベントバスのデータに直接アクセスすることをベストプラクティスとしてお勧めします。

# 他のイベントバスやメッセージングシステム

本書はApache Kafkaにフォーカスしています。しかし、議論されているコンセプトは他のイベントバスやメッセージングシステムに適用することができます。DLTでは、[Databricksランタイムが直接サポートしているデータソース](https://docs.databricks.com/data-engineering/delta-live-tables/delta-live-tables-data-sources.html)をサポートしています。

## Amazon Kinesis

Kinesisでは、完全に管理されたサーバレスストリームにメッセージを書き込みます。Kafkaと同じ様に、Kinesisはメッセージの永続化は行いません。Kinesisのデフォルトのメッセージ保持期間は1日です。

Amazon Kinesisを使用する際、上述したストリーミング取り込みのPythonコードで`format("kafka")`を`format("kinesis")`で置き換え、`option()`を用いてAmazon Kinesis固有の設定を追加します。詳細に関しては、Spark構造化ストリーミングの[Kinesis Integration](https://spark.apache.org/docs/latest/streaming-kinesis-integration.html)のセクションを参照ください。

## Azure Event Hubs

Azure Event Hubsの設定に関しては、[Microsoftのドキュメント](https://docs.microsoft.com/ja-jp/azure/event-hubs/event-hubs-kafka-spark-tutorial)と[Delta Live Tables recipes: Consuming from Azure Event Hubs](https://alexott.blogspot.com/2022/06/delta-live-tables-recipes-consuming.html)をご覧ください。

# サマリー

DLTはETLの`T`以上のものです。DLTを用いることで、任意のクラウド上のDatabricksレイクハウスプラットフォーム上で、データ品質を担保しつつストリーミングとバッチソースから容易に取り込むことができます。

PythonのDLTノートブックを用いて、Kafkaブローカーに直接接続することで、Apache Kafkaからのデータを取り込むことができます。Kafkaのストリーミングレイヤーのソースデータが破棄されたとしても、パイプラインのフルリフレッシュによってデータの損失を防ぐことができます。

# 使い始める

Databricksを利用されているのであれば、[スタートガイド](https://qiita.com/taka_yayoi/items/0cde1f2732ff859a726f)に従ってください。このGAリリースに何が含まれているのかに関してはリリースノートをご覧ください。Databricksを利用されていないのであれば、[フリートライアルにサインアップ](https://databricks.com/jp/try-databricks)いただき、[こちら](https://www.databricks.com/jp/product/pricing)のDLT価格の詳細を確認ください。

Data + AI Summit 2022に関して議論している[Databricks Community](https://community.databricks.com/s/topic/0TO8Y000000VJEhWAO/summit22)に参加ください。

さらには、サミットの[Dive Deeper into Data Engineering](https://youtu.be/uhZabeKxXBw)をご覧ください。このセッションでは、Twitterのライブストリーム、Auto Loader、SQLにおけるDelta Live Tables、Hugging Faceの感情分析を用いたストリーミングデータのサンプルコードをウォークスルーしています。

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)