バックフィルとは?PostgreSQL・DynamoDBで過去データを遡って埋める方法

目次

「新しいカラム追加したけど、既存データが全部NULLなんだが…」

テーブルに新しいカラムを追加した。新規レコードにはちゃんと値が入る。でも、過去の数万件は全部NULL。ダッシュボードの集計がおかしくなる。CSVエクスポートすると歯抜けだらけ。こんな経験、ありませんか?

この「過去データを遡って埋める・修正する処理」のことを、エンジニアリングの現場では「バックフィル(backfill)」と呼びます。本記事では、バックフィルの基本概念から、PostgreSQLとDynamoDBそれぞれでの実装パターン、やらかしがちな失敗と対策までを解説します。

バックフィルとは? ― 過去データを「後から埋める」処理

バックフィル(backfill)は、「back(過去に遡って)」+「fill(埋める)」で、既存データに対して後から値を追加・修正する処理のことです。

【バックフィル前】
id | name     | email              | plan
1  | 田中太郎  | tanaka@example.com | NULL  ← 昔のデータ、planカラムがない時代
2  | 佐藤花子  | sato@example.com   | NULL
3  | 鈴木一郎  | suzuki@example.com | free  ← 新規データはちゃんと入ってる

【バックフィル後】
id | name     | email              | plan
1  | 田中太郎  | tanaka@example.com | free   ← 遡って埋めた
2  | 佐藤花子  | sato@example.com   | premium ← 遡って埋めた
3  | 鈴木一郎  | suzuki@example.com | free

どんな場面で必要になる?

バックフィルが必要になるシーンは、大きく3つあります。

  • スキーマ変更後の既存データ補完:新しいカラムを追加したが、過去レコードにはデフォルト値やビジネスロジックに基づいた値をセットしたい
  • データ移行・統合:別システムのデータを取り込んだ後、フォーマットの違いや欠損値を修正する
  • 集計ロジック変更への遡及適用:分析用のカラムや計算結果を、過去データにも反映させたい

いずれも「今後のデータは正しいが、過去のデータが古いまま」という状態を解消するのがバックフィルの役割です。

PostgreSQLでのバックフィル実装

PostgreSQLでのバックフィルは、基本的にUPDATE文で行います。ただし、大量データを一括更新するとテーブルロックやWAL肥大化の問題が起きるため、バッチ分割が鉄則です。

基本:単純なUPDATEでのバックフィル

小規模(数千件以下)なら、シンプルなUPDATE文で十分です。

-- planがNULLの既存ユーザーにデフォルト値をセット
UPDATE users
SET plan = 'free'
WHERE plan IS NULL;

-- 別テーブルの情報を参照してバックフィル
UPDATE users u
SET plan = s.plan_name
FROM subscriptions s
WHERE u.id = s.user_id
  AND u.plan IS NULL;

大量データ:バッチ分割で安全に実行

数万〜数百万件を一気にUPDATEすると、トランザクションが膨れてテーブルロックが長引き、本番に影響が出ます。1,000〜10,000件ずつ分割して処理するのが安全です。

-- バッチ分割でバックフィル(1,000件ずつ)
DO $$
DECLARE
  batch_size INT := 1000;
  affected INT;
BEGIN
  LOOP
    UPDATE users
    SET plan = 'free'
    WHERE id IN (
      SELECT id FROM users
      WHERE plan IS NULL
      LIMIT batch_size
      FOR UPDATE SKIP LOCKED  -- ロック中の行はスキップ
    );

    GET DIAGNOSTICS affected = ROW_COUNT;
    RAISE NOTICE 'Updated % rows', affected;

    EXIT WHEN affected = 0;

    -- 各バッチ間で少し待って負荷分散
    PERFORM pg_sleep(0.1);
  END LOOP;
END $$;

FOR UPDATE SKIP LOCKEDを使うことで、他のトランザクションがロックしている行をスキップし、デッドロックを回避できます。psqlの基本コマンドと使い方を押さえていれば、このスクリプトをpsqlから直接実行できます。

DynamoDBでのバックフィル実装

DynamoDBはRDBと違い、UPDATE文で一括処理はできません。全件スキャンして1件ずつ(またはバッチで)更新するアプローチになります。

基本:Scanして1件ずつUpdateItem

// backfill-dynamo.mjs
import { DynamoDBClient, ScanCommand, UpdateItemCommand } from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({ region: 'ap-northeast-1' });
const TABLE_NAME = 'Users';

async function backfill() {
  let lastKey = undefined;
  let totalUpdated = 0;

  do {
    // Scanでplanが未設定のアイテムを取得
    const scanResult = await client.send(new ScanCommand({
      TableName: TABLE_NAME,
      FilterExpression: 'attribute_not_exists(plan)',
      ExclusiveStartKey: lastKey,
    }));

    for (const item of scanResult.Items ?? []) {
      await client.send(new UpdateItemCommand({
        TableName: TABLE_NAME,
        Key: { id: item.id },
        UpdateExpression: 'SET plan = :plan',
        ExpressionAttributeValues: {
          ':plan': { S: 'free' },
        },
      }));
      totalUpdated++;
    }

    lastKey = scanResult.LastEvaluatedKey;
    console.log(`Updated so far: ${totalUpdated}`);
  } while (lastKey);

  console.log(`Backfill complete: ${totalUpdated} items updated`);
}

backfill();

大量データ:BatchWriteItemで高速化

1件ずつUpdateItemだと、100万件で数時間かかることもあります。DynamoDBのBatchWriteItemは最大25件を1リクエストで処理できるので、スループットが大幅に上がります。ただし、BatchWriteItemは「PutItem(上書き)」なので、既存の属性を消さないよう全属性を含める必要がある点に注意です。

また、DynamoDBにはプロビジョンドキャパシティの上限があるため、大量バックフィルではWCU(書き込みキャパシティユニット)を事前に引き上げるか、オンデマンドモードに切り替えるのが安全です。

やらかしがちな失敗と対策

バックフィルは「既存データを書き換える」処理なので、失敗するとリカバリが大変です。実務で踏みやすい地雷を3つ紹介します。

失敗1:本番で一括UPDATEして障害発生

PostgreSQLで100万件を1トランザクションでUPDATEすると、テーブル全体がロックされ、アプリケーションのレスポンスが止まります。必ずバッチ分割し、本番実行前にステージング環境でテストしてください。

失敗2:バックアップを取らずに実行

バックフィルのロジックにバグがあると、正しかったデータまで壊れます。実行前のスナップショット取得は絶対。PostgreSQLならpg_dump、DynamoDBならオンデマンドバックアップかポイントインタイムリカバリ(PITR)を有効にしておきましょう。

失敗3:進捗が追えなくて途中で止まったか分からない

バックフィル中にエラーで止まった場合、「どこまで終わったか」が分からないと再実行が怖い。冪等性(何度実行しても結果が同じ)を意識した設計にしておけば、途中から再実行しても安全です。WHERE plan IS NULLのように「未処理の行だけ対象にする」条件を入れるのが基本パターン。

バックフィルの知識をさらに深める関連記事

バックフィルの実装を理解したら、データベース運用やインフラ管理の周辺知識も強化しておきましょう。

データベース・CLI操作

インフラ・運用

まとめ:バックフィルは「地味だけど避けて通れない」DB運用スキル

本記事のポイントを整理します。

  • バックフィルとは、既存データに対して後から値を追加・修正する処理のこと
  • PostgreSQLではUPDATE文が基本。大量データはバッチ分割+SKIP LOCKEDで安全に
  • DynamoDBではScan → UpdateItemの逐次処理。WCU上限に注意
  • 実行前のバックアップは必須pg_dumpやDynamoDBのPITRを活用
  • 冪等性を担保して、途中で止まっても再実行できる設計にする

バックフィルは華やかな機能開発ではないけれど、スキーマ変更やデータ移行のたびに必ず発生する地味で重要な作業です。「とりあえずUPDATE全件流しとこ」で本番障害を起こす前に、この記事のパターンを引き出しに入れておいてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次