ふくふくHukuhuku Inc.
EP.06Data Fetch 10分公開: 2026-09-02

差分だけ取る ── 毎回の全件取得をやめる

件数が増えると全件取得は必ず限界を迎えます。更新日時で絞る方法には落とし穴があり、素直に実装すると取りこぼします。境界の決め方を扱います。

#API#Python#落とし穴
執筆 / 監修
松尾 亮合同会社ふくふく 代表社員

データ基盤・データパイプライン構築 / BI / 生成 AI 活用支援を専門とするエンジニア (28 年)。 本記事は AI 利用ポリシーに基づき、生成 AI の補助で執筆 → 人間が監修・編集して公開しています。

プロフィール詳細
シェア

最初は全件取っても問題ありません。1000件なら数秒で終わります。ところが件数が増えると、取得に何時間もかかるようになり、途中で失敗する確率も上がります。の提供側から見ても、毎日全件を要求してくる相手は歓迎されません。

全件取得をやめる判断は、件数そのものより所要時間で決めるのが実際的です。数分で終わるうちは全件のほうが単純で安全です。1時間を超えたあたりから、失敗したときのやり直しが重くなり、差分に切り替える理由が出てきます。

そこで前回から変わったぶんだけ取ることになります。素直に「前回実行した時刻より後に更新されたもの」を要求すればよさそうですが、この素直な実装は取りこぼします。この回では、どこで取りこぼすのかと、どう境界を決めるかを扱います。

素直な実装が取りこぼす理由

前回の実行時刻を覚えておいて、次はそれ以降の更新分を取る。この方式には、データが記録される時刻と、こちらが取りに行く時刻がずれるという前提が抜けています。

たとえば 10:00 に取得を開始し、10:00:30 に完了したとします。次回は「10:00 以降」を要求します。ところが 10:00:15 に登録されたデータが、処理の都合で 10:00:40 になってから検索対象に入った場合、そのデータは今回も次回も取れません。今回の取得時点ではまだ見えず、次回は 10:00 以降なので範囲には入るはずですが、開始時刻を 10:00:30 に更新していると漏れます。

「完了時刻」を次回の起点にしない

取得にかかった時間のぶんだけ、取りこぼしの窓が開きます。次回の起点にするなら開始時刻であり、さらに安全側に少し戻すのが基本です。

境界を安全側に倒す

確実にするには、取る範囲を少し重ねます。前回の開始時刻からさらに数分〜数時間戻したところを起点にすれば、遅れて見えるようになったデータも拾えます。この「どれだけ遅れを許容するか」の線が です。

重ねて取ると同じデータを2回取ることになりますが、保存側がを持っていれば問題になりません。鍵を決めて上書きする形にしておけば、何度取っても結果は同じです。

前回の開始時刻から余裕を引いた地点を起点にする
Python
from datetime import datetime, timedelta, timezone
# 遅れて見えるようになるデータをどれだけ許容するか。# 配信元の性質で決める。分からなければ広めに取る。LATENESS = timedelta(hours=6)

def next_window(last_started_at: datetime | None) -> tuple[datetime, datetime]:    """(取得開始, 取得終了) を返す。終了は「今」ではなく開始時刻で固定する。"""    now = datetime.now(timezone.utc)    if last_started_at is None:        start = now - timedelta(days=365)     # 初回は広く    else:        start = last_started_at - LATENESS    # 前回の開始から巻き戻す    return start, now

更新日時が信用できないことがある

そもそも配信元の更新日時が当てにならない場合があります。内容を直しても更新日時を変えない実装や、一括で全件の更新日時が書き換わる実装が実在します。前者だと差分取得が変更を見逃し、後者だと差分のはずが全件になります。

更新日時が使えないときは、内容そのものを比べるしかありません。項目を並べて要約値を作り、前回のものと違えば変更あり、と判定します。全件を取る必要は残りますが、書き込みは変わったぶんだけに減らせます。下流の処理が「変わった行だけ」を対象にできるので、全体としては十分に効きます。

要約値を作るときは、比較に使わない項目を必ず除いてください。取得時刻をそのまま含めると、中身が同じでも毎回違う値になり、全件が「変更あり」と判定されます。この取り違えは実装した本人でも見落としやすく、しかも「差分取得が効いていない」という形でしか表に出ません。

配信元が を返すなら、要約値を自分で作らずに済みます。前回の版の印を添えて要求し、304 が返れば内容は変わっていないと判断できます。相手の実装に依存しますが、使えるときは最も安くつきます。

内容の要約値で変化を見る。更新日時が当てにならない配信元向け
Python
import hashlibimport json

def content_hash(record: dict, ignore: set[str] = frozenset()) -> str:    """比較に使わない項目(取得時刻など)を除いてから要約値を作る。    ここを除かないと、毎回「変わった」と判定されてしまう。"""    target = {k: v for k, v in sorted(record.items()) if k not in ignore}    blob = json.dumps(target, ensure_ascii=False, sort_keys=True)    return hashlib.sha256(blob.encode("utf-8")).hexdigest()

def changed_records(new: list[dict], known: dict[str, str], key: str = "id"):    """要約値が前回と違うものだけを返す。"""    for rec in new:        h = content_hash(rec, ignore={"fetched_at"})        if known.get(rec[key]) != h:            yield rec, h

削除は差分では取れない

差分取得には構造的な穴があります。消えたデータは差分に出てきません。更新日時で絞る方式は「変わったもの」を返すだけで、「無くなったもの」は返しようがないからです。

配信元が削除フラグを持っていればそれを使えますが、無い場合は定期的に全件を取って突き合わせるしかありません。毎日は差分、月に一度は全件、という組み合わせが現実的な落としどころです。この全件取得はの仕組みをそのまま使えます。

差分取得の方式と、取りこぼす条件
方式必要なもの取りこぼす条件
更新日時で絞る信頼できる更新日時遅れて見える/日時を更新しない実装
カーソルの続きから追記のみのデータ過去分が書き換わる場合
内容の要約値で比較全件の取得取りこぼさないが、毎回全件を読む
削除フラグ配信元の対応対応していなければ使えない

どの方式でも、定期的な全件との突き合わせは必要だと考えておいてください。差分だけで長期間回し続けると、ずれが静かに蓄積します。次回は、取得が途中で落ちたときに続きから再開する方法を扱います。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp