公開ログを開いたら、その言葉が一度も出てこなかった
2026年8月21日、俺は新しい記事を本番に公開した。記事本体は問題なく表示され、本文中の図解画像も2枚とも正しく届いていた。サイトマップも更新され、検索エンジンへの通知も200で返ってきた。ここまではいつも通りだった。
問題は、記事のアイキャッチ画像を縮小した「サムネイル」だった。本番に画像ファイルそのものは存在していたのに、サムネイル専用のファイルだけが見当たらなかった。エラーは出ていない。警告も出ていない。公開作業を担当する仕組みは、最後まで「正常に終わりました」という顔をしていた。
気になって、俺は記事を本番へ運ぶ仕組みのソースコードを開いた。すると、サムネイルをコピーする処理は、ちゃんと書いてあった。書いてあるのに、動いていなかった。今日はその理由と、理由を調べていく過程で見えてきた、もっと奇妙な事実について書く。
正直に言うと、俺は最初「今日 公開した記事だけの、単発の事故だろう」と決めてかかっていた。自分の記事のサムネイルが1枚 無い。それだけの話だと思って、原因のコードだけ直して終わらせるつもりだった。だが、調べる範囲を広げてみたら、話はそんなに単純ではなかった。同じコードは、今日 初めて動いたわけではない。少なくとも2ヶ月前から本番で使われ続けている。それなのに、今日まで誰も気づかなかった。「なぜ今日 気づけたのか」を先に言っておくと、理由はきれいなものではない。単に、たまたま画像を目視で見比べたときに、あるはずのファイルが無いことに気づいただけだ。仕組みの側から知らされたのではない。
仕組みは在った。探す形が違っていた
__thumb という【ディレクトリ】を探すコード
俺が記事を本番へ運ぶ仕組みの中を読んでいくと、サムネイル画像だけを別扱いでコピーする一節があった。中身を要約すると、こういう処理だ。「__thumb」と「__mobile_thumb」という名前のディレクトリを探し、もしそのディレクトリが存在していたら、その中身をまるごと本番へコピーする。
ディレクトリの存在確認には、PHP の is_dir() という関数が使われていた。この関数は名前の通り、渡したパスが「存在していて、かつディレクトリである」ときだけ真を返す。それ以外はすべて偽になる。
処理そのものは、条件分岐 1本だけのシンプルな作りだった。「もし is_dir() が真を返したら、ディレクトリの中身を再帰的にコピーする」という if 文が1つあるだけで、その if の外側には何も書かれていなかった。つまり、条件が偽だったときに何をするかは、最初から決められていなかった。決めていなかったのではなく、決める必要が無いと思われていた、と言った方が正確かもしれない。「見つからなければ、それは対象が無いということだ」という前提が、コードの形として固定されていた。
実物は【ファイル名の一部】に __thumb が付く形だった
ところが、当サイトが実際にサムネイル画像を保存している形は違った。image__thumb.png のように、ファイル名の途中に「__thumb」という文字列が挟まっている。ディレクトリではなく、1枚の画像ファイルの名前の一部だった。
俺が DocumentRoot 全体を調べても、「__thumb」という名前のディレクトリは1つも見つからなかった。当サイトのサムネイルは、最初からディレクトリ形式で保存されたことが一度も無かった、ということになる。is_dir() は毎回、静かに偽を返し続けていた。処理はそのまま次へ進み、何も起きなかった。
この処理が想定していた構造そのものは、決して珍しいものではない。画像を扱うプラットフォームの中には、1枚の画像に対して複数のサイズを作り、「オリジナル用のフォルダ」と「サムネイル用のフォルダ」を分けて管理する設計が広く使われている。おそらくこのコードは、そういう別プラットフォームでの経験や、将来そう変えるつもりだった設計を先取りして書かれたのだろう。だが当サイトの実際の運用は、1枚のファイルの名前に接尾辞を足すだけの、もっと単純な形で進んでいた。設計の意図と、実際の運用が、途中でずれてしまっていた。
公開ログ47本を全部開いた。もっと奇妙だった
「サムネイル」という言葉が、47本のうち0本に無かった
今日の1件だけの出来事なら、たまたま今回のパターンだけが例外だったと考えられる。念のため、俺は公開作業のログを全部さかのぼって調べることにした。対象は2026年6月23日から今日までの47本、記事番号でいうと85本目から今回の132本目まで、当サイトが本番公開のたびに残してきた記録だ。
47本すべてを、サムネイルに関する言葉で検索した。結果は0件。今日公開した記事のログには、アイキャッチ本体のコピー、本文画像2枚のコピー、サイトマップの更新、検索エンジンへの通知、メール送信まで、正常に終わったことがきちんと書かれている。その中に、サムネイルについての記述だけが、成功も失敗も、警告すらも、一度も現れていなかった。47本を1件ずつ開いて、同じ結果が続くのを見ていくのは、正直、気味が悪かった。何かが起きて0件なのではない。最初から一度も動いていないから0件なのだと、途中で分かってしまったからだ。
それでも大半の記事は、本番に在った
ここで俺は、もう一つ奇妙なことに気づいた。ログには一度も出てこないのに、本番サーバーには、大半の記事にサムネイル画像が実際に存在している。念のため、俺は公開済みの記事すべてを1件ずつ、DB に登録されているアイキャッチのパスと、対応するサムネイルファイルの有無で突き合わせてみた。数えたとき、欠けていたのは5本だった。2026年4月に公開した古い記事が2本、ここ数日で公開した新しい記事が3本。俺はその場で新しい3本を先に直し、本番に配置した。古い2本は、あとで手が空いたときに直すつもりで残した。
そのあと、この続きの調べものと文章の仕上げを、作業を任せた相手に渡した。相手は自分の目で確かめ直すために、もう一度 同じ突き合わせをやった。相手が数えたときに残っていたのは2本だけだった。新しい3本は俺がすでに直していたので、相手の目には最初から映らなかったからだ。相手はその2本を新たに直し、今日 公開するこの記事の分も合わせて配置した。今この記事を書いている時点で、公開済みの記事すべてにサムネイルが揃っている。今日1日で本番に配置したサムネイルは、俺の分と相手の分を合わせて、合計6本になる。
ここでもう一つ、正直に書いておきたいことがある。俺は最初に「5本 欠けていた」という数字を持っていた。だが、その数字を、次に確かめる相手にちゃんと渡さなかった。相手は自分の実測だけを頼りに数え直し、「2本」という正しい答えを出した。相手の実測は正しい。渡された時点でもう3本は直っていたのだから、相手が見た世界では、最初から2本しか欠けていなかったように見えて当然だった。もし俺が黙っていなければ、相手は「以前は5本だった」ことも知った上で作業できていたはずだ。数字は、自分の頭の中で薄れて消えるのではない。次の人に渡さなかった瞬間に、もう存在しないことになる。測ることと直すことを、複数の人で交代しながらやるときは、直した記録に「いつ・誰が・何を・何本」を書いて渡さない限り、最初の数字は必ず消える。
ファイルの更新時刻を見ると、記事によってばらつきがある。本体画像とほぼ同時刻、差にして1秒未満のものもあれば、半日近く、時には12時間以上 後になっているものもあった。もし単一の仕組みが決まったタイミングで動いているなら、この時刻差はもっと揃っているはずだ。ばらつきが大きいという事実そのものが、「毎回同じ手順で作られたわけではなさそうだ」という推測を後押ししていた。あくまで推測であって、確定した事実ではないことは、重ねて断っておく。
今回調べた範囲では、クーロンジョブや他のスクリプトの中に、サムネイルをコピーする別経路を見つけることはできなかった。単一の仕組みが背景で動いているのか、その都度、誰かの手作業で補われてきたのか、この記事を書いている時点では判別できていない。ここは正直に「分からない」と書いておく。分からないことを分かったふりで埋めるのは、今日のテーマそのものに反する。
調べ方も書いておく。まず、俺は定期実行の設定を一通り確認した。決まった時刻に自動で動く処理の一覧を見て、サムネイルという単語やファイルコピーに関係しそうな処理が無いかを探したが、それらしいものは見当たらなかった。次に、サーバー上に置かれている他のスクリプトファイルを、ファイル名や中身のキーワードで一通り洗った。ここでも該当するものは出てこなかった。「見つからなかった」ことは、「存在しない」ことの証明にはならない。今回の調査範囲の外に、まだ見ていない場所が残っている可能性は十分にある。だからこそ「特定できていない」と、確定した事実のように書かないことにしている。
| 経路 | 探していた形 | ファイルの実物の形 | 0件のときログを出すか | 今回の結果 |
|---|---|---|---|---|
| サムネイルコピー | ディレクトリ __thumb | ファイル名の一部 __thumb | 出さない | 最初の確認で5本が本番に無かった |
| 本文画像コピー(先頭一致方式) | ファイル名の先頭が記事番号 | 先頭に共通の接頭辞が付く | 出さない | 一致しないが実害なし(下記) |
| 本文 img タグ抽出コピー | 本文 HTML の img タグをそのまま拾う | 仮定なし | 出す(件数を明示) | 直近5本で全数確認できた |
なぜ気づかなかったか ── HTTP 200 は、正しさを保証しない
記事本体も、他の画像も、全部200だった
記事のトップページは正常に表示されていた。本文中の画像2枚も正常に表示されていた。サムネイルというのは、SNS でシェアされたときや一覧ページの小さなカードで使われるだけの、目立たない存在だ。記事本体を目視で確認しても、まず気づけない。
公開作業を担当する仕組みの「成功しました」という表示も、その通りだった。処理の途中で例外が発生したわけでも、タイムアウトしたわけでもない。仕組みから見れば、何一つ間違ったことをしていない。ただ、条件に合致する対象が最初から存在しなかったので、何もコピーしなかっただけだ。
実は、今回 俺が気づけたのは、自分の勘の良さでは全くなかった。本番公開のたびに開く公開前後チェックの手順書に、以前からこう書いてあった。「サムネイルファイルも忘れず個別確認する」。理由まで添えてある。数ヶ月前に別の記事を公開したとき、記事本体と本文画像は配置されたのにサムネイルだけが本番に無く、404になっていたことがある、という一文だ。俺はその一文を、ただ言われた通りに実行しただけだった。本文が200だから大丈夫、で終わらせずに、サムネイルの URL を個別に叩いたのは、その一文があったからだ。もしその手順書の1行が無ければ、俺は今回も気づかずに終わっていたと思う。
読む側の目線に立つと、この失敗はさらに見つけにくい。サムネイルは、記事一覧のカードや、SNSでシェアされたときのプレビュー画像に使われる。記事本体のページを開いている限り、その画像は表示すらされない。「今日 公開した記事を、一覧ページで見比べる」という手順を踏まなければ、欠けていることに気づく機会そのものが無かった。
「暗黙の失敗」という言葉
この形の失敗には、名前がついている。Google が公開している SRE(Site Reliability Engineering)の指針書には、監視すべき指標としてエラー率を挙げる節で、こう書かれている。
"Errors: The rate of requests that fail, either explicitly (e.g., HTTP 500s), implicitly (for example, an HTTP 200 success response, but coupled with the wrong content), or by policy."
(日本語訳: エラー:失敗したリクエストの割合。明示的な失敗(HTTP 500 等)もあれば、暗黙の失敗(HTTP 200 の成功応答なのに、中身が間違っている場合)もあり、ポリシー上の失敗もある)
今回のケースはさらに一段階、奇妙だった。中身が間違っていたのではなく、中身がそもそも作られなかった。しかもそれを知らせる HTTP レスポンスすら存在しない。処理の内側で、静かに条件が不成立になって、それで終わっていた。俺は少し前に archives/130 で「『0件』は安心ではない、『N件』は危険ではない」という話を書いたばかりだったが、今回はそれをまた自分の手で踏むことになった。0という数字は、無事だったことの証明にはならない。
死んだ経路が、別の経路に助けられていた
本文の図解画像は、なぜ無事だったのか
ソースコードを追っているうちに、俺はもう一つ、うまく機能していない箇所を見つけてしまった。本文中の図解画像をコピーする処理の一つに、ファイル名の先頭が記事番号と一致するものだけを対象にする、というパターンが書かれていた。コメントには「記事番号で始まるファイル名」という例が残っていた。
ところが実物のファイル名は、先頭に共通の接頭辞(サイトの識別子)が付いてから記事番号が続く形になっている。このパターンにも、実は一致していなかった。正直、この時点で俺は少し嫌な予感がした。1つの仕組みの中に、2つ目の死んだ経路が見つかったからだ。
同じ役割を、仮定の無い経路が肩代わりしていた
それでも本文の図解画像は、俺が確認した直近5本の記事で、すべて正しく本番に届いていた。理由を調べたら、別の処理がすでにその役目を果たしていた。本文の HTML から <img src="..."> というタグを正規表現で拾い出し、そのパスにあるファイルをそのままコピーする、という方式だ。この方式は、ファイル名がどんな形をしているかを一切 仮定していない。だから記事番号と一致するかどうかに関係なく、本文に書かれている画像は漏れなく運ばれていた。
この2つの経路の違いは、「何を根拠に対象を見つけるか」という一点に尽きる。先頭一致方式は、ファイル名という「名前の約束事」を根拠にしていた。約束事は、書いた人と読む人の間で共有されていなければ意味を持たない。今回のように、途中で命名規則が変わっていれば、根拠そのものが崩れる。一方、本文タグ抽出方式が根拠にしているのは、「実際にその記事で使われているかどうか」という事実そのものだ。命名規則がどう変わろうと、本文に書かれてさえいれば見つかる。名前の約束事に頼る設計より、実際に使われている事実に頼る設計の方が、変化に強かった。
ここで俺が気づいたのは、死んだ経路が在っても、別の経路がたまたま同じ役割を果たしていると、誰も気づかないということだ。当サイトの過去の記事を振り返ると、俺が archives/127 で書いた「読まなくていい棚に59本を積んでいた」という話も、似た構造をしている。仕組みとしては存在しているのに、実際に使われる経路からは外れている状態が、外からは正常に見えてしまう。
複数の経路を持つこと自体は、悪くない
誤解しないでほしいのは、同じ役割を果たす経路が複数あること自体は、悪い設計ではないということだ。むしろ、片方が壊れてももう片方が動く、という意味では冗長性があって望ましい。問題は、冗長性が「意図して作られたもの」なのか、「たまたま結果的にそうなっていただけ」なのかを、誰も把握していなかった点にある。前者なら、どちらか一方が死んだときに気づく仕組みを別途 用意しておくべきだし、後者なら、たまたま生き残っていた側が将来 仕様変更で無くなったとき、初めて全体が壊れる。今回のサムネイル探索コードは、明らかに後者だった。
「0件」の扱い方は、業界ではもう決着がついている
ここまでは当サイト固有の話だった。ここからは視点を変えて、「0件だったときにどう振る舞うか」という論点そのものを、開発者が日常的に触れている道具の公式ドキュメントから確かめていく。結論を先に言えば、この論点はすでに広く議論され、複数の道具がそれぞれ答えを出している。当サイトが今回 踏んだ穴も、決して特殊な事情ではなかった。
is_dir() は「無い」と「違う形」を区別しない
今回の根っこにある一番小さな部品は、PHP の公式マニュアルにこう書かれている。
"Returns true if the filename exists and is a directory, false otherwise."
(日本語訳: ファイル名が存在し、かつディレクトリであれば true、それ以外は false)
「それ以外」という一言が、今回の事故の核心だ。パスが存在しない場合と、パスは存在するが別の形(ファイルだった、シンボリックリンクだった、権限が無かった)である場合とを、この関数はまったく区別しない。どちらも同じ「false」という1ビットの答えに畳み込まれる。仕様として正しい挙動だが、呼び出す側が「false = 存在しない」としか読んでいなければ、「false = 実は違う形で存在している」という可能性を見落とす。
Git のパターンにも、同じ境界線がある
似た境界線は、開発者が毎日触っている別の仕組みにもある。Git の .gitignore の公式ドキュメントを確認すると、パターンの末尾に区切り記号(スラッシュ)を付けるかどうかで、そのパターンがディレクトリだけに一致するのか、ファイルとディレクトリの両方に一致しうるのかが変わる、と説明されている。書き方1つで「何を対象にしているつもりか」が変わる、という点は、今回のサムネイル探索のコードが抱えていた問題と同じ形をしている。
Bash のグロブ展開は、既定で「見つからない」を隠す
シェルスクリプトを書くときにも、同じ罠が待ち構えている。GNU Bash の公式マニュアルによれば、ワイルドカードを含むパターンがどのファイルにも一致しなかった場合、既定の設定では、パターンの文字列そのものがそのまま次の処理に渡されてしまう。nullglob というオプションを設定して初めて、一致しなかった場合に「何も無い」として扱われるようになる。failglob というオプションを使えば、一致しなかった時点でエラーにして処理を止めることもできる。どちらも既定ではオフになっている。
| 仕組み | 0件のときの既定動作 | 変更できるか |
|---|---|---|
| PHP is_dir() | 「存在しない」も「ディレクトリでない」も、同じ false | 変更不可(言語仕様) |
| Bash のグロブ展開(既定) | パターン文字列そのものを、そのまま渡してしまう | nullglob / failglob で変更可 |
| Git .gitignore | 末尾に区切り記号があれば、ディレクトリのみに一致 | パターンの書き方で制御 |
| GitHub Actions upload-artifact | 既定は warn(警告のみで処理は続く) | if-no-files-found: error で失敗させられる |
IAB が「寛容であること」を、正式に見直した
「送信は厳格に、受信は寛容に」の半分だけが残っていた
ネットワークプロトコルの世界には、長年「Postel の法則」と呼ばれる考え方があった。「自分が送るものには厳格に、他人から受け取るものには寛容に」という原則で、初期のインターネットの相互運用性を支えてきたとされる。今回のサムネイル探索コードも、振る舞いとしてはこの後半、「寛容に」に近い。想定と違う形に出会っても、エラーにせず、静かに次へ進んだ。
ところが IAB(Internet Architecture Board)が2023年に公開した RFC 9413 という文書は、この考え方に正面から異を唱えている。
"Tolerating unexpected input instead conceals problems, making it harder, if not impossible, to fix them later."
(日本語訳: 予期しない入力を許容することは、問題を隠してしまい、後になってそれを直すことを困難、あるいは不可能にする)
今回のケースに、そのまま当てはまる指摘だった。「ディレクトリが見つからなければ何もしない」という寛容な振る舞いは、コードを一見 安全に見せる。だが実際には、想定と実物がずれているという問題そのものを隠してしまい、47本ぶんのログを全部さかのぼって初めて気づけるところまで、発見を先送りしていた。
「病的なフィードバックサイクル」という表現
Postel の法則が提唱された当時、インターネットに接続する実装は今よりずっと少なく、種類もばらばらだった。想定外の入力を受け取っても落ちずに動き続けることは、当時としては現実的な安全策だったのだろう。ただ、実装が増え、自動化が進み、人の目が届く頻度が減った今の環境では、その「落ちずに動き続ける」という美点が、そのまま「誰にも気づかれずに動き続ける」という欠点に反転する。今回のサムネイル探索コードは、まさにその反転が起きた実例だった。
同じ文書には、もう一段強い表現も出てくる。
"An implementation that reacts to variations in the manner recommended in the robustness principle enters a pathological feedback cycle."
(日本語訳: 堅牢性原則が推奨する形で変動に反応する実装は、病的なフィードバックサイクルに入る)
「寛容であれ」という指針を、送信側にも受信側にも当てはめ続けると、想定外の形式がそのまま既成事実として世の中に広まり、次の実装がそれに合わせてさらに緩くなる、という循環が起きる、という指摘だ。今回、ディレクトリ形式のサムネイルという「想定」だけが古いまま残り、実際のファイル形式という「現実」との間に、誰にも気づかれない溝ができていた。溝を埋めなかったのは、コードが黙ってそこを避けて通り続けたからだった。
「0件を異常にする」選択肢は、もう製品にある
GitHub Actions は、既定を選ばせる設計にした
「寛容にするか、厳格にするか」を、開発者自身が選べるようにした実例が、すでに広く使われている製品の中にある。GitHub Actions の upload-artifact というアクションは、指定したパターンに一致するファイルが1つも見つからなかったとき、既定では警告(warn)だけを出してジョブを続行する。だが設定を if-no-files-found: error に変えるだけで、0件だった時点でジョブそのものを失敗させることができる。
「見つからなかったのに、警告だけで終わった」という報告
この既定の warn 挙動をめぐっては、実際に困った開発者からの報告が残っている。GitHub 上の Issue #532 では、指定したパターンが文字通りには一致せず、警告だけを出して静かにファイルをスキップしてしまった事例が報告されている。今回のサムネイル探索コードとまったく同じ形の困りごとが、まったく別のチームでも起きていたことになる。
ここで大事なのは、GitHub Actions がこの2つの選択肢を「両方 用意した」という点だ。どちらか一方が絶対に正しいわけではない。日々のビルドで多少の欠けがあっても止めたくない場面もあれば、1件でも欠けたら即座に気づきたい場面もある。大事なのは、その2択を、開発者自身に選ばせる形で設計に組み込んだことだ。今回のサムネイル探索コードには、その選択肢そのものが存在していなかった。選ばなかったのではなく、選ぶという発想自体が、コードを書いた時点では無かったのだと思う。
「0件を異常として扱うか、正常な範囲として扱うか」は、コードを書く前に決めておくべき設計判断だ、というのが、ここまで並べた4つの一次情報が示している共通点だった。判断を先送りにすると、既定値のほうが勝手に選ばれる。そして多くの言語やツールの既定値は、寛容な側、つまり黙って何もしない側に倒れている。
当サイトの場合、サムネイルという対象は「無くても記事本体の閲覧には支障が無い」という性質を持つ。だからといって、0件を放置してよい理由にはならない。SNSでのシェア表示や一覧ページの見え方に直接 影響する以上、読者への影響が小さいだけで、影響そのものは確かに存在する。今回の対応として選んだのは、既存の死んだ経路をそのまま消してしまうことではなく、0件だったときに明示的にログを残す一文を足すことだった。壊れている構造を急いで作り直すより、まず「壊れていることが分かる」状態にする方を優先した。
明日、確認できること
今回の教訓は、当サイトだけの特殊な事情ではない。画像・PDF・CSS・JavaScript ──「本文とは別の経路でコピーしている」ファイルは、多くのサイトに存在する。そして別経路であるほど、日々の目視確認から外れやすい。自分のサイトを運用しているなら、明日から確認できることがいくつかある。
1つずつは小さな確認だが、まとめて実行できるものではない。優先度をつけるなら、まず「別経路の洗い出し」と「実物との突き合わせ」を最初にやってほしい。俺自身、今回の事故もこの2つさえ先にできていれば、公開の時点で気づけていたはずだと思っている。
- 画像・PDF・CSS など「本文とは別経路でコピーしている」ファイルが無いか、洗い出す
- その経路が探しているファイル名・ディレクトリ名の「形」を、実物と1つずつ突き合わせる
- 0件だったときにログが出るかどうかを、コードの if 文の中まで読んで確かめる
- 「出ていないログ」を、公開作業のログ全体から一度 検索してみる(無いことは、無いと分かって初めて安心できる)
- 複数の経路が同じ役割を持っていないか確認する。片方が死んでいても、もう片方が肩代わりしていることがある
- HTTP 200 が返っていることを「正しく届いた」の根拠にしない。届いたはずのファイルの実在を、個別に確認する
- ディレクトリの存在確認に
is_dir()のような関数を使っているなら、false が返る理由が複数あることを前提に設計する - シェルスクリプトでワイルドカードを使っているなら、
nullglobやfailglobの設定を確認する - 「0件を異常として扱うか」を、既定値に任せず、自分で決めて明示的に書く
- 更新頻度の低いファイル(サムネイルのように、目立たず、壊れても気づきにくいもの)ほど、意識して個別に点検する
サイト全体のリンク切れを機械的に洗い出したいときは、当サイトが無料で提供しているリンクチェッカーが使える。ページ内のリンク切れを一括で検出できるので、今回のような「実は届いていないファイル」を見つける入口の一つになる。また、本番と過去のスナップショットを比較して差分を確認したいときは、WEBサイトの状況チェック・比較ツールも役に立つ。今日と昨日で何が変わったかを機械的に突き合わせられれば、「気づいたら5本消えていた」という今回のような発見を、もっと早い段階でできるようになる。
当サイトの現在地
今回の件について、当サイトが今どこまで対応できているかを、正直に書いておく。「直した」と「効いている」は別物だ、という教訓は俺がarchives/131でも書いたばかりだが、今回もまったく同じ順番でしか確かめられなかった。まず実際にファイルを配置し、次にその配置が正しいことをハッシュ値で確認し、その上でようやく「直った」と言えるようになる。
そして今回、その途中で俺自身、もう1つ恥ずかしい間違いをやった。欠けていた今回のサムネイルを、俺は最初、幅400ピクセル・高さ210ピクセルという当サイトの実際のサイズを確認せずに、なんとなく600×315ピクセルで作って配置した。作業としては「配置できた」ので、その場では気づかなかった。既存のサムネイルを1枚も見ずに、頭の中の記憶だけでサイズを決めていたからだ。既存ファイルと並べて確かめて初めて、サイズが違うことに気づいた。「実物と突き合わせる」ことの大切さを、この記事の中で何度も書いておきながら、自分の直した箇所では最初それをやっていなかった。
- ✅ 実施済 サムネイルを運ぶ経路を追加した(既存の死んでいた経路は消さず、そのまま残してある)
- ✅ 実施済 今回のサムネイルを、正しいサイズ(400×210ピクセル)に作り直して本番に配置した。最初に作った600×315ピクセル版は、既存ファイルとの突き合わせで自分の間違いに気づいて差し替えた
- ✅ 実施済 過去の公開ログ47本を全数さかのぼって確認し、「サムネイル」という言葉が1件も出現していないことを確かめた
- ✅ 実施済 公開済みの記事すべてを1件ずつ実物のファイルと突き合わせた。最初の確認で5本の欠落を見つけ、まず直近の記事3本を先に本番へ配置。作業を引き継いだ相手が残り2本を見つけて配置し、今回のこの記事の分と合わせて、今日1日で本番に配置したサムネイルは合計6本になった
- 🔧 着手中 0件のときに明示的にログを出す処理(else 節)を足す作業。今回の記事執筆時点では、まだ実装していない
- 📋 予告 「今回いくつ運んだか」を件数として必ずログに出す設計に、他の経路と揃える
このテーマの、これまで
「0件」や「見えないところで起きている静かな失敗」というテーマは、俺がこの連載で繰り返し向き合ってきたものだ。archives/131では、俺が確認せずに「直した」と判断したことがそのまま本番に残っていた話を書いた。archives/130では、探す針の作り方によって0件という答えの意味がまったく変わることを書いた。archives/128では、出力そのものは存在していたのに、誰もそれを測っていなかったという話を書いた。archives/124では、比べる相手を用意していなかったために、効いている証拠を無効化してしまった話を書いた。archives/105では、1,000件規模の宿題を6日間で畳んだ、公開作業そのものの記録を書いた。
今回参照した一次情報は次の通りだ。
- RFC 9413(IAB・2023年6月公開): Maintaining Robust Protocols
- Google SRE Book: Monitoring Distributed Systems
- Git 公式ドキュメント: gitignore
- PHP 公式マニュアル: is_dir()
- GNU Bash Reference Manual: Filename Expansion
- GitHub Actions: upload-artifact / Issue #532
今回のように「仕組みは在った。動く気もあった。ただ探す形が違っていた」というだけの理由で、誰にも気づかれないまま公開が続くことがある。落ちたわけではないので、監視の仕組みも鳴らない。派手な失敗ではないぶん、静かな失敗の方が、実は長く放置されやすい。
今回の件で俺が一番 印象に残ったのは、コードを書いた誰かを責める話ではまったく無い、ということだった。is_dir() は仕様通りに動いていたし、if 文もその通りに書かれた通りに動いていた。すべての部品は、それぞれの役割を正しく果たしていた。ただ、部品と部品のつなぎ目、「この関数が偽を返す理由は1つとは限らない」という前提を、誰も明示的に書いていなかった。それだけのことで、複数の記事のサムネイルが、静かに欠け続けていた。しかも俺は、それを直している最中に、自分でも同じ形の間違い(実物を見ずにサイズを決める)を重ねていた。人ごとにして読める話ではない、と自分に言い聞かせながら、この記事を書いた。明日、自分のサイトの「別経路でコピーしているファイル」を、一つでいいので実物のファイル名と突き合わせてみてほしい。
WEBサイト