まえがき
Data Engineering Design Patterns の 4 章の感想を残していきます。4 章の題材は「べき等性」であり、データエンジニアリングにおける非常に重要な概念ですね。
2-3 章の感想は以下になります。
4 章の目次
- Overwriting
- Pattern: Fast Metadata Cleaner
- Pattern: Data Overwrite
- Update
- Pattern: Merger
- Pattern: Stateful Merger
- Database
- Pattern: Keyed Idempotency
- Pattern: Transactional Writer
- Immutable Dataset
- Pattern: Proxy
- Summary
感想
Snowflakeによる Fast Metadata Cleaner 相当の実現
べき等性を保つためには、既存のデータを削除してから新しいデータを再作成する(洗い替え)ことであり、この削除処理の方式として、以下の 2 パターンを書籍では紹介しています。
- Fast Metadata Cleaner パターン
- Data Overwrite パターン
Fast Metadata Cleaner パターンはパーティション単位の TRUNCATE を使うことで、DELETE による削除より高速だと主張されています。書籍の例ではないですが、Oracle などでは以下のように実現します。
-- パーティション単位の削除
alter table sales_summary truncate partition sales_summary_202511;
-- パーティションデータの再作成
insert into sales_summary select ... from sales_detail
where sales_timestamp >= '2025-11-01 00:00:00'
and sales_timestamp < '2025-12-01 00:00:00':
(実際には、パーティション TRUNCATE ⇒ INSERT ではなくパーティション入れ替えを採用することが多いですが割愛)
ただし、DBMS のパーティション機能に依存しており、パーティション機能がない DBMS においてはストレートにこれはできず、ビューなどを利用するのが 「過去の」 やり方でした。
create table sales_202504 ...;
create table sales_202505 ...;
create table sales_202506 ...;
....
create view sales
as
select * from sales_202504 union all
select * from sales_202505 union all
select * from sales_202506 union all
...;
「過去の」と書いたのは最近の DWH サービスでは状況が異なるからです。例えば、Snowflake においては内部ではマイクロパーティションと呼ばれる単位で保存されます(マイクロパーティションは「パーティション」と名前がついていますが、従来のパーティション機能と異なり明示的に定義するものではありません)。この機能が Fast Metadata Cleaner に近いことを実現してくれます。
例えば、以下のようなマイクロパーティションにデータが保存されているとします。マイクロパーティションでは、そこに含まれるカラム値の最大値と最小値を保持しています。(明示的なパーティションとは異なるため、最小値 - 最大値の範囲に重複があります)
このケースで 11 月分のデータを削除する以下の DELETE 文を実行するとします。
delete from sales
where sales_timestamp >= '2025-11-01 00:00:00'
and sales_timestamp < '2025-12-01 00:00:00':
この際には以下のような動作になります。
- パーティション001以前とパーティション012以降
- 最小値/最大値から 11 月分のデータは含まないことが分かるため、処理をスキップされる
- パーティション003 ~ パーティション010
- 最小値/最大値から 11 月分のデータしか含まないことが分かるため、1件1件チェックはせず、マイクロパーティションを丸っとテーブルから除外する(メタデータ上の操作のみ)
- パーティション002とパーティション011
- 11月分データとそうでないデータが混在しているため、1件ずつチェックし削除する
要は1件ずつ処理されるのはごく一部のパーティションのみであるため、Fast Metadata Cleaner に近い処理性能を実現することができます。
以下のポストの3枚目の画像にも同様の説明をしています。
以上は Snowflake の話でしたが、最近の Redshift もこのような動作をするのではないかと推測しています(ドキュメント上の裏はとれておらず、実際の処理性能からの推測)。
この動作があるおかげで、最近は明示的なパーティションやビューなどを利用せずに Fast Metadata Cleaner パターンと近い性能が出せるようになって、ありがたいですね。
パーティションの単位
Fast Metadata Cleaner パターンはパーティション機能に依存しており、パーティションの粒度には注意が必要という旨が書籍には記載されています。例えば、パーティション単位を1週間にした場合、1日分だけバックフィルしたくても1週間=7日分をバックフィルする必要があるということです。
ただ、パーティションの粒度ってバックフィルの単位以外にも、以下の要素などを考慮に入れて決める必要があります。
- ユーザーのクエリの単位
- 下流のデータパイプラインの処理の単位
- 保持期間切れデータを削除する単位
特に日本だと現場担当者レベルの BI ユーザーが昨日のデータを見るというユースケースが多いような気がしており、1日単位でパーティションを切りたくなることが多いです。(あと、夜間バッチで1日分のデータを処理することが多いなども理由になりえる)
ただ、あまりに細かいパーティションの単位は以下のような弊害も生み出しやすいです。
- DBMS のパーティション数上限に抵触しやすくなりやすい
- 大量のパーティションを対象とする参照 & 更新処理のオーバーヘッドが増加しやすい
- 大量パーティションの操作をするため、保守 & 運用などが複雑になりやすい
- 1パーティションが小さくなりすぎることで圧縮や一括処理などのメリットが享受しづらい(SQL Server の列指向では1パーティション 10 万件以上でないと、データが圧縮されず逆に遅くなる可能性があります)
最近はスキャン性能をはじめとした処理性能も上がってきているので、細かくパーティションを切る前に一息おいて冷静に考えたいですね。(最近何か見た)
まぁ、Snowflake や Redshift では関係のない悩みなのですが…
Stateful Merger が求められるパターン
Stateful Merger パターンはバックフィル処理を行う際に、データ全体の一貫性を優先して対象データが挿入される直前にデータ全体をリストアするアプローチです。
例えば、2025/11/20 ~ 2025/11/22 の3日間に渡り1日1回当日分データを処理するとします。
- 2025/11/20
- 2025/11/21
- 2025/11/22
3日間分の処理が終わった後に 2025/11/21 のデータに問題が見つかりバックフィルを行う場合、2025/11/21 分のデータを処理するのではなく、以下の順で実行します。
- データ全体を 2025/11/20 処理済みの状態にリストア(この時点で 11/21, 11/22 のデータは消える)
- 11/21 のデータをバックフィル
- 11/22 のデータをバックフィル
これにより、データ鮮度の問題は除きますがデータ全体としては一貫性を保つことができます。
ただ、これってどのようなケースで必要なのでしょうか?ぱっと思いつくのは以下です。
- 月の累積値など前の日のデータに当日データが依存するメトリクスを分析する場合
- データに抜けがあってはいけない場合
- 下流のパイプライン処理が進まないようにしたい場合
1番目の場合は Stateful Merger の処理が必須になります。ただし、2番目、3番目のケースでは 11/21 のデータが正しく処理されていないわけなので、11/20 分処理済みの段階にデータを戻したからと言って万事解決という話ではなく、結局は分析ユーザーや下流パイプラインチームへの連絡・調整(非技術的な対応)が結局必要になります。
それも踏まえて、Stateful Merger パターンを採用する必要があるケースをもう少し整理しておきたいなと思っています。
CREATE TABLE ... LIKE ...
Transactional Writer パターンは、エラーが発生した際にもデータコンシューマーに重複/不完全なデータを見せないようにするために、書き込みをトランザクションに含めて、正しく処理されたデータ一式しか見せないようにするアプローチです。
このデザインパターンの本題とは直接関係ないのですが、この章では以下の SQL 文が紹介されています。
CREATE TEMPORARY TABLE changed_devices_file1 (LIKE dedp.devices);
COPY changed_devices_file1 FROM '/data_to_load/dataset_1.csv'
CSV DELIMITER ';' HEADER;
MERGE INTO dedp.devices AS d USING changed_devices_file1 AS c_d
-- ... ommitted for brevity
この1文目の CREATE TABLE ... LIKE ... という構文を知りませんでした。確かに一時テーブルを既存テーブルと同じ定義で作成したいというシチュエーションは数多くあるのですが、今まで以下のようなことをしていました。
CREATE TABLE ... AS SELECT ... FROM ... WEHRE 1 = 0
CREATE TABLE ... LIKE ... は DBMS によって制約やその他のプロパティーが引き継がれる、継がれないの違いはあるようので注意が必要なのですが、今後は積極的に使っていきたいですね。
- Redshift
- NOT NULL 制約、分散スタイル、ソートキーは引き継がれる
- プライマリキー制約と外部キー制約は引き継がれない
- デフォルト値が引き継がれるかはオプション次第
- Snowflake
- デフォルト値や制約、クラスターキーは引き継がれる
- 権限が引き継がれるかはオプション次第
Mergeで難しいのは集約などが入った場合
Updates という節で、Merger パターンと Stageful Merger パターンの2つのパターンを書籍では説明していますが、例は両方ともソースでテーブルのデータをターゲットテーブルに同期させる処理になっています。
一方、Update(Merge)がべき等性に問題をもたらすケースって、多くは集計やウィンドウ関数などを含むケースのような気がします。
結局、そういうケースでは Fast Metadata Cleaner パターンや Late Data Integrator パターン(3章で紹介)などのように、一定の範囲で洗い替えを行うアプローチなんだと思います。
データエンジニアリング領域でべき等性の概念を有名にしたらしい記事
4 章の最初に紹介されているのですが、以下の記事がデータエンジニアリング領域においてべき等性の概念を有名にしたらしいです。必読です。
Functional Data Engineering — a modern paradigm for batch data processing