欠落している画像をどのように見つけるか?

はい、raw テキストは以下の通りです:

"It looks like \"Fung-Wong\", \"Mario\", or simply \"Typhoon #16\" will be [making landfall in Japan on Thursday](http://www.jma.go.jp/jp/typh/1416l.html):\n\n![Typhoon 16](/uploads/default/35/4608d96d1b27846f.png)"

そして、cooked テキストは以下の通りです:

"<p>It looks like “Fung-Wong”, “Mario”, or simply “Typhoon <span class=\"hashtag\">#16</span>” will be <a href=\"http://www.jma.go.jp/jp/typh/1416l.html\" rel=\"nofollow noopener\">making landfall in Japan on Thursday</a>:</p>\n<p><div class=\"lightbox-wrapper\"><a class=\"lightbox\" href=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" title=\"4608d96d1b27846f.png\"><img src=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" alt=\"Typhoon 16\" width=\"602\" height=\"500\"><div class=\"meta\">\n<svg class=\"fa d-icon d-icon-far-image svg-icon\" aria-hidden=\"true\"><use xlink:href=\"#far-image\"></use></svg><span class=\"filename\">4608d96d1b27846f.png</span><span class=\"informations\">800×664</span><svg class=\"fa d-icon d-icon-discourse-expand svg-icon\" aria-hidden=\"true\"><use xlink:href=\"#discourse-expand\"></use></svg>\n</div></a></div></p>"

これらは1行に圧縮されていると読みづらいので、整形された raw テキストを以下に示します:

"It looks like \"Fung-Wong\", \"Mario\", or simply
\"Typhoon #16\" will be [making landfall in Japan on Thursday]
(http://www.jma.go.jp/jp/typh/1416l.html):\n\n
![Typhoon 16](/uploads/default/35/4608d96d1b27846f.png)"

そして、整形された cooked テキストは以下の通りです:

"<p>
  It looks like “Fung-Wong”, “Mario”, or simply
  “Typhoon <span class=\"hashtag\">#16</span>” will be
  <a href=\"http://www.jma.go.jp/jp/typh/1416l.html\" rel=\"nofollow noopener\">
    making landfall in Japan on Thursday
  </a>:
 </p>\n
 <p>
   <div class=\"lightbox-wrapper\">
     <a class=\"lightbox\" href=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" title=\"4608d96d1b27846f.png\">
       <img src=\"/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png\" alt=\"Typhoon 16\" width=\"602\" height=\"500\">
       <div class=\"meta\">\n
         <svg class=\"fa d-icon d-icon-far-image svg-icon\" aria-hidden=\"true\">
           <use xlink:href=\"#far-image\"></use>
         </svg>
         <span class=\"filename\">4608d96d1b27846f.png</span>
         <span class=\"informations\">800×664</span>
         <svg class=\"fa d-icon d-icon-discourse-expand svg-icon\" aria-hidden=\"true\">
           <use xlink:href=\"#discourse-expand\"></use>
         </svg>\n
       </div>
     </a>
   </div>
 </p>"

参考までに、私のサイトにはこのような投稿がかなり(100件以上)あります:

[1] pry(main)> Post.where("raw ~* :regex AND cooked !~* :regex", regex: '/uploads/default/[0-9]+/').count
=> 135

生コンテンツと加工コンテンツで URL の形式が異なるべきではありません。上記の投稿を再構築(rebake)してみてください。投稿メニューの Rebuild HTML または post.rebake! コマンドで行えます。この投稿に「uploads」関連のカスタムフィールドは存在しますか?post.custom_fields コマンドですべてのカスタムフィールドを確認できます。

その特定の投稿にある他のすべてのカスタムフィールドは以下の通りです(HTML再構築コマンドを実行する前):

  id: 43,
  user_id: 1,
  topic_id: 36,
  post_number: 3,
  created_at: Mon, 22 Sep 2014 05:05:16 UTC +00:00,
  updated_at: Mon, 22 Sep 2014 05:11:22 UTC +00:00,
  reply_to_post_number: nil,
  reply_count: 0,
  quote_count: 0,
  deleted_at: nil,
  off_topic_count: 0,
  like_count: 0,
  incoming_link_count: 0,
  bookmark_count: 0,
  avg_time: 58,
  score: 1.2,
  reads: 6,
  post_type: 1,
  sort_order: 3,
  last_editor_id: -1,
  hidden: false,
  hidden_reason_id: nil,
  notify_moderators_count: 0,
  spam_count: 0,
  illegal_count: 0,
  inappropriate_count: 0,
  last_version_at: Mon, 22 Sep 2014 05:11:22 UTC +00:00,
  user_deleted: false,
  reply_to_user_id: nil,
  percent_rank: 0.585365853658537,
  notify_user_count: 0,
  like_score: 0,
  deleted_by_id: nil,
  edit_reason: "画像のローカルコピーをダウンロードしました",
  word_count: 34,
  version: 2,
  cook_method: 1,
  wiki: false,
  baked_at: Sun, 14 Apr 2019 09:28:00 UTC +00:00,
  baked_version: 2,
  hidden_at: nil,
  self_edits: 2,
  reply_quoted: false,
  via_email: false,
  raw_email: nil,
  public_version: 2,
  action_code: nil,
  image_url: "/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png",
  locked_by_id: nil

uploads フィールドは確認できませんが、おそらくお探しのものは image_url でしょうか?HTML再構築コマンドを実行する前の値は以下の通りでした:

/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png

HTML再構築コマンドを実行すると、image_url フィールドの値が以下のように変更されたようです:

https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png

調理済みのテキスト内のすべての URL も更新されているようです:

"<p>
  「Fung-Wong」、「Mario」、または単に
  「台風<span class=\"hashtag\">#16</span>」が
  <a href=\"http://www.jma.go.jp/jp/typh/1416l.html\" rel=\"nofollow noopener\">
    木曜日に日本に上陸するようです
  </a>:
 </p>\n
 <p>
   <div class=\"lightbox-wrapper\">
     <a class=\"lightbox\" href=\"https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png\">
       <img src=\"https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png\" alt=\"Typhoon 16\" width=\"602\" height=\"500\">
       <div class=\"meta\">\n
         <svg class=\"fa d-icon d-icon-far-image svg-icon\" aria-hidden=\"true\">
           <use xlink:href=\"#far-image\"></use>
         </svg>
         <span class=\"filename\">4608d96d1b27846f.png</span>
         <span class=\"informations\">800×664</span>
         <svg class=\"fa d-icon d-icon-discourse-expand svg-icon\" aria-hidden=\"true\">
           <use xlink:href=\"#discourse-expand\"></use>
         </svg>\n
       </div>
     </a>
   </div>
 </p>"

4608d96d1b27846f.png01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png の関係は何でしょうか?寸法は同じで、一目見ただけでは同じように見えますが、明らかに異なるファイルです:

$ diff /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png
Binary files /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png and /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png differ

$ ls -l /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png
-rw-r--r-- 1 chris www-data 150319 Jan 19 01:14 /var/discourse/shared/standalone/uploads/default/35/4608d96d1b27846f.png

$ ls -l /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png
-rw-r--r-- 1 chris chris 95005 Jul  3 15:25 /var/discourse/shared/standalone/uploads/default/original/2X/0/01bb9fb7e29c2b65fd663cdc58705d1720f8fea7.png

そして、もちろん、百万ドルの問いは残ります:/uploads/default/35/4608d96d1b27846f.png を新しいアップロード方式に移行するにはどうすればよいでしょうか?

アップロードが新しい方式に正しく移行されていないようです。SiteSetting.migrate_to_new_scheme = true という設定変更自体が、この状況を解決するはずです。なぜあなたの環境で発生しないのかはわかりません。新しい方式に移行されていないアップロードの数を確認してください。以下のコマンドを実行して結果を確認してください。

Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count
SiteSetting.migrate_to_new_scheme = true
Jobs::MigrateUploadScheme.new.execute(nil)
Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count

いいえ、これらはカスタムフィールドではありません。カスタムフィールドの値を取得するには、post.custom_fields コマンドを使用してください。

あ、すみません!カスタムフィールドの意図を完全に誤解していました。

もしかしたら間違っているかもしれませんが、その特定の投稿にはカスタムフィールドが含まれていないようです。

[1] pry(main)> Post.find_by(:id => 43).custom_fields
=> {}

さて、これは興味深いですね…

[2] pry(main)> Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count
=> 0
[3] pry(main)> Post.find_by(:id => 43).image_url
=> "https://{{SITE FQDN}}/uploads/default/35/4608d96d1b27846f.png"

ご提供いただいたクエリに一致する結果は存在しないようです。これは想定された動作でしょうか?

また:

この質問への答えについて、何かご存知でしょうか?

アップロードが新しいスキームへ移行済みであるようです。しかし、投稿が新しいスキームの URL に正しく再マッピングされていないため、これらの投稿は現在、不整合な状態となっています。調査を進めるために、サイトの認証情報を PM で送っていただけますか?空いた時に詳しく調査します。

ああ、それが心配していたことでした。残念ながら、これはプライベートなインストールであり、外部の第三者にサーバーの(root)アクセス権を付与する許可があるかどうか確信が持てません。この問題をトラブルシューティングするために、他にできることはないでしょうか?

4 月末頃から画像が表示されない問題が発生していましたが、今夜になって初めて調査を開始しました。

rake posts:missing_uploads
26766 の投稿アップロードが欠落しています。

22693 のアップロードが欠落しています。
そのうち 22683 は旧方式のアップロードです。
535188 件の投稿のうち 9352 件に影響が出ています。

安定版を使用しています。このスレッドの最新投稿を考慮すると、次に何をすべきか確信が持てません。

編集:特定の GIF ファイル 1 つを選択し、ライブサーバーのアップロードディレクトリには存在しないことを確認しました。一方、バックアップサーバーの tombstone ディレクトリには存在していました。そこで、バックアップサーバーからこのファイルを scp でライブサーバーの対応する tombstone ディレクトリ(discourse/shared/standalone/uploads/tombstone/default/39/ee8670816301d4c4.gif)に転送し、その後上記の rake タスクをテストとして再度実行しました。

その結果、該当する投稿に画像が表示されるようになり、全体の数値は以下の通り減少しました。

26750 の投稿アップロードが欠落しています。

22692 のアップロードが欠落しています。
そのうち 22682 は旧方式のアップロードです。
535190 件の投稿のうち 9336 件に影響が出ています。

ライブサーバー上の tombstone ディレクトリのサイズは 138MB ですが、バックアップサーバーでは 9.5GB です。そこで、このディレクトリを rsync で同期し、rake タスクを再度実行して、報告される数がさらに減少することを期待しています。

@kansaichris ご確認したところ、アップロードが欠落している投稿は 3 件のみとのことです。その場合、該当する投稿の生データを直接編集し、正しいアップロード URL を設定してください。

@skl 旧方式のアップロードが多数あります。バックアップサーバーから「tombstone」アップロードをコピーした後、以下のコマンドを実行して新方式へ移行してください。

rake posts:missing_uploads    # `rsync` でファイルがコピーされたことを確認
rake uploads:recover          # rsync 後に欠落したアップロードがある場合
SiteSetting.migrate_to_new_scheme = true
Jobs::MigrateUploadScheme.new.execute(nil)
Upload.by_users.where("url NOT LIKE '%/original/_X/%' AND url LIKE '%/uploads/default%'").count
# カウントが 0 であることを確認
rake posts:missing_uploads    # 再度ステータスを確認

@vinothkannans さん、ご助力ありがとうございます。ご指示に従って実行しました(完了まで約 12 時間かかりました)。その結果、数値はいくらか減少しました。

22,614 件の投稿アップロードが欠落しています。
19,830 件のアップロードが欠落しています。
19,830 件のうち 19,821 件は旧方式のアップロードです。
535,224 件の投稿のうち 7,339 件に影響が出ています。

まだ欠落しているアップロードがあるため、tombstone フォルダ以外も確認したところ、ライブサーバー上の uploads/default には 22,885 の空ディレクトリが表示されていました(バックアップサーバーでは 10 個の空ディレクトリです)。また、バックアップサーバーとの間で 10GB 以上のサイズ差もあるため、現在、uploads/default をバックアップからライブサーバーへ rsync で同期し、その後再度ご指示の手順を実行する予定です。

追記:rake posts:missing_uploads は CPU 依存のシングルスレッドタスクのようですが、30 時間以上実行され続けているため、一時的にサーバーを専用 CPU インスタンスにスケールアップしました。画像は一時的に復元されたようですが、旧方式での表示となっています。おそらく、Discourse の更新により、当初の削除が発生したのでしょう。

Hmm…もし本当にアップロードが欠落している投稿が 3 件だけなら、なぜ cooked テキストが新しいアップロード方式を使っているのに、raw テキストが古いアップロード方式を使っている投稿が 135 件も存在するように見えるのでしょうか?

[1] pry(main)> Post.where("raw ~* :regex AND cooked !~* :regex", regex: '/uploads/default/[0-9]+/').count
=> 135

raw カラムと cooked カラム間でアップロード URL の形式が不一致になっているためです。posts:missing_uploads の rake タスクは cooked カラムのみを基準にアップロードをチェックします。不一致しているアップロード URL を何らかの方法で修正する必要があります。データベースを直接確認しない限り、お手伝いすることができません。

:crossed_fingers:

ああ、なるほど。posts:missing_uploads タスクが cooked カラムのみをチェックするとは知りませんでした。これで不一致が説明がつきますね。:+1:

また、SiteSetting.migrate_to_new_schemetrue に設定して開始される移行プロセスも、同様に cooked カラムの値のみをチェックする、と理解してよろしいでしょうか?

新しいスキームへの移行は、rawcooked の両方にある URL を置き換えるはずです。しかし、あなたのケースではそれが起こっていません。

そう思います。影響を受けた投稿の last_updated_at カラムも確認できます。

タスクがエラーで中止され、各試行で同じエラーが表示されます。

[2019-07-26T09:18:56.829375 #572]  WARN -- : Badly formed IFD: undefined method `map' for nil:NilClass
....rake aborted!
ArgumentError: negative length -2 given
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:89:in `readframe'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:116:in `examine'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:34:in `block in initialize'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:34:in `open'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/exifr-1.3.6/lib/exifr/jpeg.rb:34:in `initialize'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/worker/jhead.rb:40:in `new'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/worker/jhead.rb:40:in `oriented?'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/worker/jhead.rb:27:in `optimize'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:122:in `block (5 levels) in optimize_image'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim/handler.rb:41:in `process'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:122:in `block (4 levels) in optimize_image'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:120:in `each'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:120:in `block (3 levels) in optimize_image'
/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/discourse_image_optim-0.26.2/lib/image_optim.rb:247:in `block in with_timeout'
Tasks: TOP => posts:missing_uploads

最新版ですか、それとも以前のバージョンですか?

最新の安定版「2.3.2 +4」

おそらく、最新のベータ版が必要です。

セルフホスティングの場合は、安定版を使用する理由はほとんどありません。実際には、サポートがより難しくなります。

クライアントは安定版のままであることを特に要望しています。私がこのプロジェクトを引き継いだ際、その点については非常に固執していました。