直した。また壊れた。これを5回繰り返した。
最初は単なる重複エラーだと思った。修正してデプロイしたら、次はタイムアウトで落ちた。
※この記事は、Claude Codeで1人開発しているSNS運用SaaS「ThreadPost」の開発日記です。
SNS運用を自動化しませんか?
ThreadPostなら、投稿作成・画像生成・スケジュール管理まで全てAIにお任せ。
核心回答:なぜ5回もシステムが壊れたのか
ニュース分析機能とRSS収集の網羅性を高めようとした。結果としてシステム全体が連鎖的に崩壊した。
全体のアーキテクチャを無視して、目の前のエラーだけを潰す部分的な修正を繰り返したのが原因だ。

- 総コミット数: 44件
- 新機能: 1件
- バグ修正: 5件
- 壊れた回数: 5回
終わらないモグラ叩き
RSSフィードを増やせば、ニュースの網羅性が上がりユーザー体験は爆発的に向上する。
だから僕は、ニッチな専門メディアのフィードを次々とシステムに登録していった。

「feat: 博多グルメカテゴリ追加」
「feat: ハイラックス・SUVカテゴリ追加」
福岡のローカルメディアや、車中泊専門のブログ。合計248個のフィードを一気に流し込んだ。
Claude Codeに指示を出して、数分で全カテゴリの初期設定が終わった。最高にワクワクする瞬間だった。
数時間後、システムが完全に沈黙した。
エラーログを見ると、データベースの重複エラーが大量に吐き出されていた。
「fix: カテゴリ分類時の重複エラーを修正」
同じURLの記事が同時に処理され、データベースのユニーク制約に引っかかっていた。
PostgreSQLでは、同じトランザクション内で同じ行を2回更新しようとするとエラーになる。
Claude Codeに指示して、保存前に重複を弾く処理を入れた。デプロイ完了。動いた。
翌朝、またシステムが落ちていた。
今度はRSS収集のバッチ処理がタイムアウトしていた。
「fix: RSS収集をInngestに移行(タイムアウト対策)」
VercelのCronジョブには60秒の実行時間制限がある。
248個のフィードを直列で取得して、画像を探して、AIで分類する。そんな重い処理が60秒で終わるわけがなかった。
1つのサイトのレスポンスが遅いだけで、バッチ全体が道連れになっていた。
業界標準のワークフローエンジンである「Inngest」を導入した。
これで最大2時間まで非同期で処理が回せるようになった。完璧だ。
また落ちていた。
今度はAnthropicのAPIがJSONパースエラーを吐いて停止していた。
「fix: 記事データの不正Unicode文字を除去(APIエラー対策)」
一部の古いブログのRSSに、孤立サロゲートペアと呼ばれる不正なUnicode文字が混ざっていた。
AIにデータを渡す前に、テキストをサニタイズする処理を追加した。
しんたろー:
1個直すたびに「これで終わり」と思ってた。馬鹿すぎる。エラーが連鎖してるのに、モグラ叩きみたいに場当たりで直してたわ。
次から次へと違うエラーが出る。
Claude Codeに「エラーになってるログ全部見て、根本どこか特定して」と投げた。
「fix: usedURLs除外をJS側に変更(SQLタイムアウト対策)」
原因はSQLのタイムアウトが連鎖していることだった。
過去に投稿した記事を除外するために、SQLの条件式に大量のURLを突っ込んでいた。
件数が増えるほどクエリの実行計画が悪化し、データベースが悲鳴を上げていた。
この処理をJavaScript側に移して、ついでに記事の取得数も動的に調整する仕組みに変えた。
これで5回目の修正だ。ようやく本番環境が安定した。
RSS収集の完了時間は2時間。Vercelの限界を遥かに超えた泥臭いバッチ処理が完成した。
深夜のヘッダーオーバーフロー
深夜2時。画面の前の僕は完全に疲弊していた。
新しい機能を追加するワクワク感はとうに消え失せ、ただひたすら無機質なログと睨み合っていた。
「Supabase」のSQLフィルタを使えば、重複記事の除外なんて一瞬で終わるはずだった。
「.not().in()」メソッドに、使用済みのURLリストを渡すだけだ。
「fix: 使用済み記事URLフィルタをJS側で処理(HeadersOverflow対策)」
URLリストが222件を超えた瞬間、APIリクエスト自体が拒否されるようになった。
HTTPヘッダーがオーバーフローしたのだ。
一般的なWebサーバーは、HTTPヘッダーのサイズを8KBから16KB程度に制限していると言われている。
各50文字から100文字あるURLを222件もエンコードしてクエリストリングに詰め込めば、20KBを軽く超える。
「Supabase」のJavaScriptクライアントは、GETリクエストのパラメータをヘッダーに含めて送信する仕様だった。
結果として、データベースに到達する前のインフラ層でリクエストが弾かれていた。
しんたろー:
「お前、どこから来てるんだよ」ってガチで声に出た。DBのエラーじゃなくて、HTTPプロトコルの限界にぶち当たるとか聞いてない。
SQLのIN句に大量のIDを突っ込んだら、クエリが長すぎてパフォーマンスが落ちた。
全件取得してからJavaScriptの配列メソッドで除外する方式に書き換えた。
「fix: 記事重複選択バグを修正(1000件制限対策)」
さらに追い打ちをかけるように、デフォルト取得上限1000件の壁が立ちはだかった。
使用済みの記事が1000件を超えると、古いURLが取得できなくなり、また重複投稿が始まる。
ページネーションを実装して、全件取得できる仕組みに変えた。
ここまで読んだあなたに
今なら無料で全機能をお試しいただけます。設定後は完全放置でプロ品質の投稿を毎日生成。
記事をゼロにする究極の断捨離
記事の重複を避けるために、過去のURLをSQLで除外しようとした。
URLが500件を超えた途端、クエリがタイムアウトして記事が1件も取得できなくなった。
「重複を避ける」ために「記事をゼロにする」という、究極の断捨離をシステムが勝手に実行していた。
取得件数を動的に調整するロジックを組んでいなかったのが原因だ。
「fix: usedUrls件数に応じて記事取得数を動的調整」
使用済みのURLが584件ある状態で、上限を400件に設定してデータベースに問い合わせる。
当然、取得した400件はすべて「使用済み」として除外され、手元には何も残らない。
除外される分を見越して、取得上限を「設定値の3倍」と「設定値+使用済み件数」の大きい方に動的に変更した。
これでやっと、空っぽのニュースリストとおさらばできた。
落とし穴:Bing News APIの謎タイムアウト
これで完璧だと思ったが、甘かった。
Bing News検索のAPIが時々謎のタイムアウトを起こすようになった。
Google NewsのRSSをリダイレクト問題で捨てたから、今はBingに完全に依存している状態だ。
APIの呼び出し制限に引っかかっているのか、単なるネットワークの揺らぎなのか、ログからは全く判別できない。
リトライ処理を入れたが、それでも10回に1回は失敗する。
外部APIに依存する機能は、相手の機嫌次第で簡単にシステムが崩壊する。
今日の数字
| 指標 | 結果 | 比較対象 |
|---|---|---|
| RSS収集完了時間 | 2時間 | 以前のVercel Cronは60秒 |
| ヘッダーサイズ | 20KB超 | 一般的なサーバー上限は8KB〜16KB |
| 削減したAIコスト | 年間約$520 | 外注エンジニアなら1日で飛ぶ金額 |
週10ドルから15ドルのAIコストを削減するために、152個のフィードをルールベースの分類に統合した。
AIによるテキスト分類は1回あたり約0.01ドルかかる。
「feat: AIコスト不要の152フィードをマージ」
月間数万件の処理を考えると、これをルールベース化するだけで年間数百ドルの直接的なコスト削減になる。
しんたろー:
Claude Codeに全部任せたら、平気でAPIを無駄撃ちするブルートフォースなコード書いてきやがった。これ放置してたら破産するぞ。
よくある質問
Q: なぜGoogle NewsのRSSを削除したのか?
Google NewsのRSSは内部でリダイレクトを多用している。標準的なRSSパーサーでは最終的な記事のURLに到達できず、404やパースエラーを頻発させる。Bing News APIの方が構造が安定しており、開発の費用対効果が高い。
Q: Inngestの導入でインフラコストは跳ね上がらないのか?
Inngestはイベント駆動型の料金体系で、小規模なバッチ処理なら無料枠に収まる。VercelのProプランに課金して実行時間を延ばすよりも、外部のワークフローエンジンに逃がした方がトータルの運用コストは安くつく。
Q: 記事の取得元ドメインはどうやって分散させているのか?
特定のドメインに偏らないよう、アプリケーション側でラウンドロビン方式の抽選ロジックを組んでいる。プールから最低200件を取得し、50件選出する際にドメインを均等に配分する仕組みだ。
泥臭いデバッグの末に、ようやく安定したニュース収集基盤が完成した。

この記事が参考になったら、ThreadPostを試してみませんか?
投稿作成・画像生成・スケジュール管理まで、全てAIにお任せできます。
ThreadPostをもっと知る