【ITニュース解説】How upload_tracker.py Keeps a Re-Run From Double-Posting Your Stock Batch
2026年10月21日に「Dev.to」が公開したITニュース「How upload_tracker.py Keeps a Re-Run From Double-Posting Your Stock Batch」について初心者にもわかりやすく解説しています。
ITニュース概要
バッチ処理が中断し再実行すると、ファイルが重複アップロードされアカウント評価に悪影響を与える問題が発生した。これを解決するため、完了したファイルのみを記録するシンプルなPythonスクリプトを開発。これにより、中断しても未完了ファイルだけを再実行でき、重複を回避し処理の信頼性を高めた。
ITニュース解説
システム開発の現場では、ファイルをアップロードしたり、データを更新したりするような処理を、まとめて自動的に実行する「バッチ処理」が頻繁に行われる。しかし、このバッチ処理が途中で失敗した際に、単純に「もう一度実行する」という対応を取ると、思わぬ問題が発生することがある。今回の記事では、この「部分的な失敗」から生じる深刻な問題と、それを防ぐためのシンプルな解決策について詳しく解説する。
筆者の実体験として、約40個の画像ファイルをAdobe Stockにアップロードするバッチ処理の最中に、ネットワークタイムアウトが発生したことがあった。この処理が途中で中断してしまったため、筆者は原因と思われる問題を修正し、改めて同じバッチ処理を最初から実行した。その結果、どうなっただろうか。23個のファイルが重複してアップロードされてしまったのだ。幸い、Adobeの重複検出機能がほとんどの重複ファイルを検知してくれたが、それでも審査待ちの時間が長くなったり、重複ファイルの削除対応に手間取ったりと、余計な手間と「ノイズ」が生じてしまった。特に複数のプラットフォームでコンテンツを管理している場合、このような問題はアカウントの信頼性や健全な提出履歴を維持する上で大きな障害となる。この経験から、筆者はupload_tracker.pyというスクリプトを開発した。これは地味なスクリプトだが、バッチ処理の信頼性を確保する上で極めて重要な役割を果たす。
なぜ重複投稿が単純なミスでは済まされないのだろうか。ストックフォトなどのコンテンツプラットフォームは、重複した投稿を非常に厳しく扱っている。例えば、Adobe Stockでは審査段階で重複コンテンツが拒否され、同じコンテンツが繰り返し拒否されると、アカウントの品質に問題があるとフラグが立てられる可能性がある。Vecteezyでも同様に、すでにカタログに存在するコンテンツは審査で差し戻される。Dreamstimeでは、取り込みの段階で重複が検出される。各プラットフォームは、CSV形式のメタデータ(コンテンツの情報)に関してもそれぞれ異なる厳密な形式を要求する。例えば、Adobeは5列のヘッダー、Vecteezyは4列、Dreamstimeは15列といった具合だ。これはmake_metadata.pyのような別のスクリプトで対応できる部分だが、同じ画像ファイルが複数のアップロードセッションで送られてしまった場合、メタデータだけでは解決できない。
本当の落とし穴は「部分的な失敗」だ。バッチ処理は途中で停止することが頻繁にある。例えば、サーバーのメモリ不足、低速なネットワーク接続、プラットフォーム側の一時的な不具合など、様々な要因で40ファイル中17ファイル目の処理で停止することがある。このような状況で、もしリカバリ策が単に「もう一度実行する」ことであるならば、それはまだ見つけていないバグを抱えているのと同じだ。この再実行によって、すでに処理済みのファイルが再度処理され、重複が生じる可能性が生まれる。
この問題の解決策として開発されたのがupload_tracker.pyである。このスクリプトの最大の特徴は、Pythonの標準ライブラリのみで構成されていることだ。つまり、追加のライブラリをインストールする必要がなく、外部の依存関係も持たず、データベースサーバーなども不要である。これは、システムのセットアップを簡素化し、環境間の互換性の問題を減らす上で大きなメリットとなる。
upload_tracker.pyは、シンプルな「プレーンテキストの台帳ファイル」を用いて状態を管理する。この台帳ファイルは、主に以下の3つの質問に答える役割を果たす。
- まだ完了していないアイテムは何か?
- 特定のアイテムは完了したか?
- 現在の全体のステータスはどうなっているか?
このスクリプトを設計する上で最も重視されたのは「トラッカーは、完了したことだけを記録し、開始したことは決して記録しない」という原則だ。あるファイルが「完了」とマークされるのは、そのアップロードが実際に成功したことを確認した後だけである。もし処理がファイルの途中で停止した場合、そのファイルは依然として「未完了」の状態のままだ。そして、次の実行時には、この未完了のファイルだけが再度処理されることになる。もしアップロードの開始と同時に「完了」とマークしてしまうような「楽観的な」設計にしてしまうと、筆者の最初のテスト版トラッカーのように、テストバッチで重複投稿が発生してしまう。この問題を解決するための修正は、たった一行の順序変更だった。「確認してから記録する」という順序が、この問題を防ぐ上で決定的に重要となる。
なぜこの順序が重要なのか。それは、トラッカーの信頼性が「最も弱い瞬間」、すなわちクラッシュする瞬間に左右されるからだ。「失敗してから記録する」という設計(実際の成功を確認してから記録する)では、クラッシュしてもデータが失われるリスクがない。もし途中でクラッシュしても、まだ成功が記録されていないため、次の実行で安全に再試行できる。しかし、「記録してから失敗する」という設計(成功したと仮定して先に記録し、その後に実際の処理が失敗する)では、クラッシュが発生すると、実際には失敗しているにもかかわらず「完了」と誤って記録された状態が残り、その結果として重複が生じる可能性が生じてしまう。
このupload_tracker.pyは、筆者のバッチ処理フローに以下のように組み込まれている。
まず、アセットファイル(元となる画像など)を生成する。次に、各プラットフォーム向けのメタデータCSVファイルを作成する。そして、ここでupload_tracker.py pendingコマンドを実行し、まだアップロードされていないファイルの一覧を取得する。この「未処理リスト」に基づいて、各プラットフォームへのアップロードを一つずつ実行する。そして、各ファイルのアップロードが成功したことが確認できたら、そのファイルをupload_tracker.py doneコマンドで「完了」とマークする。
このステップ3がupload_tracker.pyの存在意義そのものだ。この仕組みによって、バッチ処理は「冪等性」を持つようになる。これは、同じ操作を何度繰り返しても同じ結果が得られるという性質のことだ。つまり、途中で中断して何度再実行しても、最初の一度だけ正しくすべてのファイルが処理され、二度目以降はすでに完了しているため何もせず終了する。筆者は実際に、このバッチを意図的に5回再実行したが、最初の完全な実行後には、未処理リストは毎回空だったという。
ただし、一つ正直な限界がある。このトラッカーはプラットフォーム自体の状態を知らないということだ。Adobeに問い合わせて「このファイルは本当にアップロードされたか?」と尋ねることはできない。トラッカーは、アップロードステップが「完了した」と報告することを信頼している。もしアップロードステップが誤った情報を報告すれば、トラッカーも誤った情報を記録してしまう。したがって、アップロードステップ自体はシンプルで正直な設計に保つことが、台帳の正確性を維持する上で重要となる。
なぜSQLiteやJSONのようなデータベースを使わず、わざわざプレーンテキストファイルを選んだのだろうか。筆者はSQLiteや、ロック機構を持つJSONファイルでの状態管理も検討したという。これらも良い選択肢だが、リソースが非常に限られた環境(例えば1 vCPU、2 GB RAMのサーバー)では、「最も単純で、破損する可能性が低いものが正しい選択」となる。プレーンテキストファイルであれば、もし台帳ファイルが何らかの理由で破損したとしても、人間が直接読み解き、手作業で修正することができる。現在の状態を正確に把握し、対応することが容易なのだ。一方、複雑なデータベースや状態ファイルの場合、深夜にバッチ処理が失敗した際に、その状態ストアをデバッグすることは非常に困難で、避けたい問題だった。
この「シンプルさ」と「堅牢性」という哲学は、パイプライン全体の他のスクリプトにも貫かれている。例えば、make_metadata.pyは、各プラットフォームが要求する厳密なヘッダーのみを書き出し、余計なものは一切含めない。これは、わずかなヘッダーの不一致でもプラットフォームが即座に拒否するためだ。また、pack_product.pyは、ファイルをZIP圧縮し、Pillowという最小限の画像ライブラリのみを使ってプレビュー画像やサムネイルを生成する。すべての処理は、同じ小型サーバー上で、一つずつ順番に実行され、リソース消費量も正確に測定されている。
もし過去の自分にアドバイスするとしたら、筆者は「失敗の原因はアップロードそのものではなく、『再実行が安全だ』という思い込みにあった」と伝えるだろう。一度以上実行される可能性のあるすべてのバッチ処理パイプラインには、「試行」ではなく「完了」を記録する台帳が必要であり、それは可能な限り「退屈な」、つまり最もシンプルで分かりやすいファイル形式であるべきだ。この仕組みは、最初の大規模な部分的な失敗が発生する「前」に構築すべきであり、失敗した「後」ではない。なぜなら、部分的な失敗は必ず発生するものであり、それによって生じる重複コンテンツは、審査時間やアカウントの評判に悪影響を与え、その代償は計り知れないからだ。
このupload_tracker.pyは、筆者が開発した「パイプラインスターターキット」を構成する4つのスクリプトの一つであり、単一コア、2GBメモリのサーバーで39個の製品と856MBの出力をパッケージ化した実績がある。これは、限られたリソースでも堅牢なシステムを構築できることを示している。
システムエンジニアを目指す皆さんにとって、このような実例から学べることは多いだろう。コードの複雑さや最新技術を追求するだけでなく、シンプルさ、堅牢性、そして予期せぬ失敗に対する回復力(リカバリ)を考慮した設計がいかに重要であるかを、このupload_tracker.pyの事例は教えてくれる。バッチ処理の信頼性を確保するための「台帳」の概念、そして「確認してから記録する」という基本的な原則は、様々なシステム設計に応用できる普遍的な教訓である。