お知らせ・ブログ一覧へ戻る

昨日まで動いていたデータ同期が413エラーで止まるとき|毎回全件を送る設計は件数が増えると必ず壊れる理由と、未送信分だけを小分けに送る直し方


昨日まで動いていたデータ同期が413エラーで止まるとき|毎回全件を送る設計は件数が増えると必ず壊れる理由と、未送信分だけを小分けに送る直し方

昨日まで動いていたデータ同期が413エラーで止まるとき|毎回全件を送る設計は件数が増えると必ず壊れる理由と、未送信分だけを小分けに送る直し方

手元のパソコンやスプレッドシートに溜めたデータを、サーバー上の管理画面へ自動で送っている。何か月も問題なく動いていた。コードも設定も触っていない。それなのに、ある日からログに HTTP Error 413 が出て、データが届かなくなった。

この症状で最初に疑うべき原因は1つです。

同期のたびに「全件」を1回のリクエストで送っていて、データが増えた結果、受け側サーバーが決めている「1回に受け取れる大きさの上限」を越えた、です。

413は「Request Entity Too Large」(新しい表記では Content Too Large)、つまり「送ってきた中身が大きすぎるので受け取らない」という意味の応答です。プログラムが壊れたのでも、サーバーが落ちたのでもありません。送る量が、受け口の大きさを越えただけです。

この記事では、先に正しい直し方を示し、そのあとで弊社の社内ツールで実測した数字、そして「上限を上げる」で済ませてはいけない理由をまとめます。

結論:上限を上げるのではなく、送り方を変える

直し方は次の2つをセットで入れます。

  1. 未送信分だけを送る:送る前に、受け側に「すでに持っているデータのID一覧」を問い合わせ、手元にあって受け側に無いものだけを抜き出す
  2. 小分けにして送る:抜き出した分を、50件ずつのように件数を決めて区切り、複数回に分けて送る

この2つを入れると、1回のリクエストの大きさは「データの総件数」と無関係になります。総件数が1,000件でも10万件でも、1回に運ぶのは最大50件分です。データがどれだけ育っても、上限に触れなくなります。

逆に、サーバー側の上限値を引き上げるだけの対処は、壊れる日を先に延ばしているだけです。この点は後半で詳しく書きます。

なぜ「何も変えていないのに」止まるのか

全件を毎回送る方式は、作った直後はまったく問題なく動きます。データが少ないからです。

時期 件数 1回の送信サイズ 結果
作った直後 数十件 数十KB 成功
数週間後 数百件 1MB弱 成功
ある日 上限を越える件数 1MB超 413で拒否
それ以降 増え続ける さらに大きくなる 毎回413

大事なのは最後の行です。一度413が出始めたら、待っていても自然に直ることはありません。データは増える一方なので、次の実行ではもっと大きくなり、また弾かれます。一時的な通信エラーなら再実行で通りますが、413は再実行しても結果が変わらない種類のエラーです。

つまりこの方式は、「件数が増えると、いつか必ず壊れる」ことが最初から決まっている設計です。テスト時のデータ量ではまず再現しないので、作った本人も気づけません。

実例:3.1MBの全件送信が、1MBの上限で弾かれていた

弊社の社内ツールで実際に確認した数字です。手元のMacで1日2回データを集め、サーバー上の確認画面へ送る構成でした。

  • 手元のデータ:1,469件
  • サーバー側の確認画面にあったデータ:490件
  • 差の979件が、確認画面に届いていなかった
  • 全件を1回で送るときの本文の大きさ:約3.1MB
  • 受け側のWebサーバー(nginx)の上限:1MBclient_max_body_size を指定していないときの既定値)

ログを遡ると、最後に同期が成功したのは朝の実行で、同じ日の夕方の実行から413が出始めていました。1件あたり約2KBなので、490件前後でちょうど1MBに届く計算になり、件数と上限の関係とも合います。そこから気づくまでの約6週間、1日2回の実行はすべて同じエラーで終わっていました。

ここで押さえておきたいのは、集める処理そのものは止まっていなかった点です。収集も加工も毎日正常に終わり、手元のファイルは増え続けていました。止まっていたのは最後の「送る」部分だけです。処理全体が落ちていれば誰かがすぐ気づきますが、最後の一段だけが失敗する形は、画面を見に行かない限り表に出てきません。

この「動いているように見えて届いていない」状態の見つけ方は、自動処理の監視は「最後に成功した時刻」を見るで詳しく書いています。今回も決め手は、受け側にある最新データの日付が何週間も前で止まっていたことでした。

手順1:原因が本当に「大きさ」かを確かめる

直す前に、3つの数字を確認します。推測ではなく数字で切り分けるためです。

① 送信している本文の大きさ

送る直前のデータをファイルに書き出して、サイズを見ます。JSONで送っているなら、そのJSONファイルの大きさがほぼそのまま本文の大きさです。

② 受け側の上限

受け側が何で動いているかによって、上限を決めている場所が違います。

受け側 上限を決めている設定 指定しないときの値
nginx client_max_body_size 1MB
Apache LimitRequestBody バージョンやレンタルサーバーの設定で異なる(サーバー会社の仕様を確認)
CDN・WAFを前段に置いている場合 各サービスのリクエストサイズ上限 契約プランごとに異なる
外部サービスのAPI サービスごとの仕様 各サービスの公式ドキュメントで確認

nginxの既定値が1MBという点は見落としやすいところです。設定ファイルに client_max_body_size の行が無い場合、「上限なし」ではなく「1MB」で動いています。

③ 手元と受け側の件数の差

手元の件数と受け側の件数を並べます。差があり、受け側の最新データの日付が古いところで止まっていれば、その日が壊れた日です。

①が②を越えていて、③の「止まった日」とログの413の初出日が一致すれば、原因は確定です。

手順2:未送信分だけを抜き出す

受け側に「いま持っているデータのID一覧」を返す口を用意し(すでに書き出し用のAPIがあればそれを使えます)、送る前に問い合わせます。

def remote_ids():
    """受け側に既にあるID。取得できなければ None"""
    try:
        remote = get("/api/export")
        return {e["id"] for e in remote["entries"]}
    except Exception as e:
        print(f"既存IDの取得に失敗(全件を分割送信に切替): {e}")
        return None

have = remote_ids()
if have is not None:
    entries = [e for e in entries if e["id"] not in have]

ポイントは2つあります。

データ1件ごとに、変わらないIDを持たせておくこと。 行番号や配列の順番をIDの代わりにしていると、途中の1件を消しただけで以降が全部ずれ、差分が取れません。データを作った時点で固有のIDを振り、それを最後まで使い回します。

ID一覧が取れなかったときの逃げ道を決めておくこと。 上の例では、取得に失敗したら「全件を対象にして、小分け送信に進む」ようにしています。受け側で「同じIDは二重登録しない」処理を入れてあれば、全件を送り直しても重複は増えません。差分の取得と、受け側の重複排除は、どちらか片方ではなく両方入れておくと、片方が崩れても壊れません。

手順3:50件ずつに区切って送る

CHUNK = 50

added = 0
for i in range(0, len(entries), CHUNK):
    batch = entries[i:i + CHUNK]
    try:
        res = post("/api/enqueue", {"entries": batch})
    except Exception as e:
        print(f"{i}件目以降の送信に失敗: {e}")
        break
    added += res.get("added", 0)

1回の件数は、1件あたりの大きさ × 件数が、上限の10分の1程度に収まるように決めます。弊社の例では1件あたり約2KBだったので、50件で約100KB。上限1MBに対して十分な余裕があり、1件のデータが多少長くなっても触れません。

途中で失敗したら、その場で止めて「何件目から失敗したか」をログに残します。すでに送れた分は受け側に入っているので、次回の実行では手順2の差分抽出によって、残りだけが自動的に対象になります。やり直しのための特別な仕組みを作らなくても、続きから再開できるのが、差分と小分けを組み合わせる利点です。

この修正を入れて実行した結果は「追加979件・重複スキップ0件」。受け側の件数が手元と同じ1,469件になり、最新データの日付が当日になったことを、画面ではなく受け側のデータを直接数えて確認しました。修正前のファイルは日付つきの別名で残し、問題があればすぐ戻せる状態にしてあります。

「上限を上げる」だけで済ませない理由

nginxなら client_max_body_size 10m; と1行書けば、その日のうちに同期は復旧します。それでも、これを本筋の対処にしないほうがよい理由が3つあります。

1. 壊れる日が先に延びるだけ。 1MBを10MBにしても、件数が10倍になれば同じことが起きます。しかもそのときには、今回の経緯を覚えている人がいないかもしれません。

2. 送信が毎回どんどん重くなる。 全件送信は、すでに届いているデータを毎回送り直しています。今回の例でも、止まる直前は、すでに届いている数百件を1日2回、毎回送り直していました。件数が増えるほど通信時間も受け側の処理時間も延び、今度はタイムアウトという別の形で止まります。

3. 受け口を広げることは、外からの大きな送信も受け入れること。 上限は、巨大なデータを送りつけられてサーバーが詰まるのを防ぐ安全装置でもあります。必要もなく広げるものではありません。

もちろん、画像や動画のアップロードのように、1件そのものが大きい用途では上限の引き上げが正解です。判断の軸は「大きいのは1件なのか、件数なのか」です。件数が原因なら送り方を直す、1件が大きいなら上限を上げる、と分けて考えます。

なお、急ぎで復旧が必要なときに、応急処置として上限を上げ、並行して送り方を直すのは妥当な順番です。上げたまま終わりにしないことだけ決めておきます。

同じ作りの処理が他にないかを見直す

413に限らず、「毎回全部を処理する」作りは、データが増えるといつか別の上限にぶつかります。

  • スプレッドシートの全行を毎回読み直して集計する → 実行時間の上限
  • 全顧客に対して毎回APIを呼ぶ → 呼び出し回数の上限
  • 全記事を1回のリクエストで取得する → 1ページあたりの件数上限

実行時間や呼び出し回数の上限については、スプレッドシートの自動集計が途中で止まるときで切り分け方をまとめています。

自動化の仕組みを作るとき、あるいは制作会社から納品を受けるときは、次の3点を確認しておくと、数か月後の「突然止まった」を防げます。

  1. この処理は、データが10倍になっても同じ時間・同じ大きさで動くか
  2. 送る(処理する)のは「全部」か「前回からの差分」か
  3. 最後の一段だけが失敗したとき、誰がどこで気づけるか

3つ目は特に重要です。この種の失敗では、エラーはログに毎回きちんと出ます。それでも、ログは見に行かなければ気づけません。ログに書くだけでなく、「受け側の最新データが○日以上古かったら通知する」という形で、結果の側から監視しておくと、何週間も後ではなく翌日に気づけます。終了コードや実行ログではなくデータ側で健全性を見る考え方は、自動処理が「成功」と出ているのに反映されないときも参考にしてください。

まとめ

  • コードを変えていないのに同期が413で止まったら、原因は「全件を1回で送る設計のまま、データが受け側の上限を越えた」こと
  • 413は再実行しても直らない。データは増える一方なので、放置すれば毎回失敗し続ける
  • 確認するのは3つの数字:送信本文の大きさ、受け側の上限(nginxは指定なしで1MB)、手元と受け側の件数差
  • 直し方は「受け側の既存IDを取得して未送信分だけ抜き出す」+「50件ずつのように小分けで送る」のセット。1回の送信サイズが総件数と無関係になる
  • 受け側の重複排除も併せて入れておくと、差分取得に失敗しても全件の小分け送信で安全に回復できる
  • 上限の引き上げは、1件そのものが大きい用途の対処。件数が原因のときは応急処置にとどめ、送り方を直す
  • 最後の一段だけが失敗する形は表に出にくい。受け側の「最新データの日付」を監視対象にする

店舗の売上データ、予約データ、問い合わせの記録など、日々増えていくデータを扱う仕組みは、作った日のデータ量ではなく、1年後のデータ量で動くかどうかで設計の良し悪しが決まります。「いまは動いている」仕組みこそ、送り方が全件か差分かを一度確認しておくことをおすすめします。

関連記事

2026.09.21

Facebookから毎月数千円の請求が来るときの調べ方|月額ではなく「請求のしきい値」で複数回落ちる仕組みと、どの広告が動いているかを特定する順番

2026.09.21

店舗のMeta広告は配信地域を最初に確かめる|「日本」のまま出すと予算の大半が来店できない地域に流れる構造と、半径・性別・年齢の絞り方

2026.09.21

WordPressのREST APIが突然404になるとき|管理画面をサブドメインへ移した後に残る古い接続先と、認証を疑う前に確かめる順番


お知らせ・ブログ一覧へ戻る