etl-incremental-patterns について
ETL Incremental Patterns は、増分ロード、バックフィル、パイプラインのオーケストレーションに関するリファレンススキルです。データソースの機能、テーブル規模、更新要件に応じたロード戦略の選択を支援します。
主な機能
- 01タイムスタンプベースの増分ロード
- 02変更データキャプチャの処理
- 03日付範囲の並列バックフィル
- 04テーブル交換による全件更新
- 05パイプラインステップの状態パターン
- 06ロード戦略の判断マトリクス
活用シーン
- 01タイムスタンプを使った大規模テーブルの同期
- 02CDC の INSERT、UPDATE、DELETE イベントの適用
- 03履歴データの並列ロード
- 04交換パターンを使った重要テーブルの更新
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ majesticlabs-dev/ majestic-marketplace --skill etl-incremental-patternsインストール後、Claude Code で/etl-incremental-patternsを入力して読み込まれたか確認します。
詳細
ETL の増分処理パターン
増分データ読み込みとバックフィル操作のパターンです。
バックフィル戦略
from datetime import date, timedelta
from concurrent.futures import ThreadPoolExecutor, as_completed
def backfill_date_range(
start: date,
end: date,
process_fn: callable,
parallel: int = 4
) -> None:
"""Backfill data for a date range."""
dates = []
current = start
while current <= end:
dates.append(current)
current += timedelta(days=1)
# Process in parallel with controlled concurrency
with ThreadPoolExecutor(max_workers=parallel) as executor:
futures = {executor.submit(process_fn, d): d for d in dates}
for future in as_completed(futures):
d = futures[future]
try:
future.result()
print(f"Completed: {d}")
except Exception as e:
print(f"Failed: {d} - {e}")
# Usage
backfill_date_range(
start=date(2024, 1, 1),
end=date(2024, 3, 31),
process_fn=process_daily_data,
parallel=4
)
増分読み込みパターン
タイムスタンプベースの増分処理
def incremental_by_timestamp(table: str, timestamp_col: str) -> pd.DataFrame:
last_run = get_last_run_timestamp(table)
query = f"""
SELECT * FROM {table}
WHERE {timestamp_col} > :last_run
ORDER BY {timestamp_col}
"""
df = pd.read_sql(query, engine, params={'last_run': last_run})
if not df.empty:
set_last_run_timestamp(table, df[timestamp_col].max())
return df
変更データキャプチャ(CDC)
def process_cdc_events(events: list[dict]) -> None:
for event in events:
op = event['operation'] # INSERT, UPDATE, DELETE
data = event['data']
if op == 'DELETE':
soft_delete(data['id'])
else:
upsert(data)
スワップによる全件リフレッシュ
def full_refresh_with_swap(df: pd.DataFrame, table: str) -> None:
temp_table = f"{table}_temp"
df.to_sql(temp_table, engine, if_exists='replace', index=False)
with engine.begin() as conn:
conn.execute(text(f"DROP TABLE IF EXISTS {table}_old"))
conn.execute(text(f"ALTER TABLE {table} RENAME TO {table}_old"))
conn.execute(text(f"ALTER TABLE {temp_table} RENAME TO {table}"))
conn.execute(text(f"DROP TABLE {table}_old"))
パイプラインのオーケストレーション
from enum import Enum
from dataclasses import dataclass, field
class StepStatus(Enum):
PENDING = "pending"
RUNNING = "running"
SUCCESS = "success"
FAILED = "failed"
SKIPPED = "skipped"
@dataclass
class PipelineStep:
name: str
func: callable
dependencies: list[str] = field(default_factory=list)
status: StepStatus = StepStatus.PENDING
error: str | None = None
class Pipeline:
def __init__(self, name: str):
self.name = name
self.steps: dict[str, PipelineStep] = {}
def add_step(self, name: str, func: callable, depends_on: list[str] = None):
self.steps[name] = PipelineStep(name, func, depends_on or [])
def run(self) -> bool:
for step in self._topological_sort():
# Skip if dependencies failed
if any(self.steps[d].status == StepStatus.FAILED for d in step.dependencies):
step.status = StepStatus.SKIPPED
continue
step.status = StepStatus.RUNNING
try:
step.func()
step.status = StepStatus.SUCCESS
except Exception as e:
step.status = StepStatus.FAILED
step.error = str(e)
return all(s.status == StepStatus.SUCCESS for s in self.steps.values())
def _topological_sort(self) -> list[PipelineStep]:
# Implementation of topological sort for dependency ordering
...
読み込み戦略の選択マトリックス
| シナリオ | パターン | 使用するタイミング |
|---|---|---|
| 小規模なテーブル(<100K 行) | 全件リフレッシュ | 日次/時間単位の読み込み |
| タイムスタンプのある大規模なテーブル | タイムスタンプベースの増分処理 | 継続的な同期 |
| ソースが CDC に対応 | CDC イベント | リアルタイム更新 |
| 1 回限りの履歴データ読み込み | 並列バックフィル | 初期移行 |
| 重要なテーブル | スワップパターン | ダウンタイムゼロのリフレッシュ |
コメント
Markdown 対応