Аватары долго загружаются после переезда на совместимый с S3 R2

Здравствуйте,

Я только что перешел на R2, и все прошло отлично. Все изображения имеют ссылку на S3 CDN. Однако я заметил проблему: аватары загружаются очень долго. В среднем это занимает от 3 до 4 секунд, независимо от того, кликаю ли я на аватар пользователя или просматриваю сообщение. Это нормально?

Хм, я подозреваю, что проблема может быть одной из трёх, но наиболее вероятная — это изменение размера аватаров «на лету».

1. Изменение размера аватаров «на лету»

Когда вы перенесли загрузки в R2, оригинальные изображения были перемещены; однако Discourse использует аватары разных размеров (например, 45 пикселей для сообщений, 120 пикселей для карточки пользователя).

Если эти оптимизированные размеры не мигрировали идеально или ещё не были сгенерированы, Discourse вынужден генерировать их синхронно в момент, когда пользователь нажимает на аватар:

  1. Discourse скачивает оригинальный аватар из R2 на локальный сервер.
  2. Изменяет его размер с помощью ImageMagick.
  3. Загружает новый размер обратно в R2.
  4. Перенаправляет браузер на новый URL, и этот процесс занимает 3–4 секунды.

Для проверки: выполните жёсткую перезагрузку страницы. Если аватар загружается 3–4 секунды при первом открытии, но мгновенно при втором — это именно то, что происходит.

Для исправления: эта проблема решится сама собой по мере того, как пользователи будут просматривать сайт и размеры будут генерироваться. Но вы можете исправить это мгновенно, принудительно сгенерировав все аватары в фоновом режиме, подключившись к серверу по SSH и выполнив:

./launcher enter app
rake avatars:refresh

2. Тайм-аут IPv6 на 3 секунды

Если аватары загружаются 3–4 секунды каждый раз, даже после нескольких перезагрузок, скорее всего, происходит сетевой тайм-аут.

Конечные точки API Cloudflare R2 являются двойными (dual-stack), то есть они используют как IPv4, так и IPv6. Если на вашем сервере (Droplet) назначен адрес IPv6, но шлюз IPv6 хоста настроен неправильно, внутреннее подключение Ruby к хранилищу R2 сначала попытается использовать IPv6, зависнет на 3 секунды (это стандартный тайм-аут TCP в Linux), завершится ошибкой, а затем мгновенно успешно подключится через IPv4.

Для проверки: подключитесь к серверу по SSH и выполните:

curl -I -6 https://cloudflare.com

Если команда зависает на несколько секунд и завершается ошибкой, значит, IPv6 на сервере настроен неправильно, из-за чего каждая внутренняя проверка API S3 испытывает задержку в 3 секунды.

Для исправления: вам нужно либо исправить маршрутизацию IPv6 в панели управления хостом, либо полностью отключить IPv6 на Droplet.

3. Задержки Gravatar

Если ваш сайт настроен на проверку обновлений Gravatar, он может опрашивать внешние серверы Gravatar перед отображением аватара. Если у сервера медленное исходящее подключение (что часто также связано с DNS или IPv6), это может блокировать отображение аватара.

Для проверки: выполните на сервере команду:
curl -I -6 https://gravatar.com
Если она зависает на 3 секунды, значит, IPv6 настроен неправильно (см. выше).

Исправление, связанное с Gravatar: в настройках Discourse перейдите в раздел автоматически загружать граватары, временно отключите эту опцию и посмотрите, поможет ли это. Я не думаю, что это основная проблема, но если это так, вы можете оставить настройку отключённой, исправить маршрутизацию IPv6, как описано в пункте 2 выше, или изменить DNS-резолвер.

Спасибо за быстрый ответ. Кажется, я уже пробовал команду ‘rake avatars:refresh’ ранее, но не уверен в этом на все сто.

Раньше у меня получалось мгновенно открывать аватар, если кликнуть по нему дважды: первый раз — для загрузки, второй — для мгновенного открытия. Но, вероятно, это связано с кэшированием. Я также только что протестировал ваш второй совет, и он возвращает «HTTP/2 301» с несколькими другими строками. То же самое касается третьего совета. Я снова запущу avatars:refresh через пару дней, так как мне нужно было восстановить снимок состояния (snapshot). Ещё раз спасибо!

Gravatar

server: nginx
date: Mon, 22 Jun 2026 19:29:00 GMT
content-type: text/html; charset=utf-8
content-length: 0
content-language: en
expires: Wed, 11 Jan 1984 05:00:00 GMT
cache-control: no-cache, must-revalidate, max-age=0
x-redirect-by: Gravatar
location: https://en.gravatar.com/
alt-svc: h3=":443"; ma=86400
strict-transport-security: max-age=31536000; includeSubdomains; preload

CF

HTTP/2 301
date: Mon, 22 Jun 2026 19:27:00 GMT
content-type: text/html
content-length: 167
location: https://www.cloudflare.com/
cache-control: max-age=3600
expires: Mon, 22 Jun 2026 20:26:59 GMT
set-cookie: __cf_bm=eBP2aJ7Eg30nHPuvMMNxxKrgNtcNwKs0WDgnYyONeus-1782156420-1.0.1.1-sXpW27iuhGDF615cOfwNFybH4IMxgvZy3uA_3X_o..402T_3KSgT7CSymipL5RjdpGe3raWEqsVxQFFLPKRoDjfoT7B.0rqyDt.osbkOF98; path=/; expires=Mon, 22-Jun-26 19:57:00 GMT; domain=.cloudflare.com; HttpOnly; Secure; SameSite=None
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=QfYqSekEDPJHC2k%2BMjHN0cGjz172tmUWe2GSR8EgwNLh3TGjFYkQ0vwPxlzY1NcBcKFOMaAi4FlgjqjhETOOtHf%2BH9KdQSvqN3OME2Uh1i4nHIw%2Fy1qkvSpf4jxDchM7CaDW80tJkjBV4OqF"}],"group":"cf-nel","max_age":604800}
nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
strict-transport-security: max-age=15780000; includeSubDomains
server: cloudflare
cf-ray: a0fda5d8ecd6b26d-LAX
alt-svc: h3=":443"; ma=86400

Да, учитывая ваш ответ, я почти уверен, что проблема в пункте №1, поскольку результаты команды curl для Cloudflare и Gravatar выглядят ожидаемо. Попробуйте выполнить rake avatars:refresh, когда вам будет удобно, и дайте знать, сработало ли это.

Привет, Лилли, у меня всё ещё та же проблема. Даже после выполнения rake avatars:refresh. Проблема также проявляется в разделе /latest. Я пробовал очищать кэш в браузере и на Cloudflare, но безрезультатно. Возможно, нужно подождать? Я тестирую это на форуме с 4500 пользователями.

Больше не очищайте кэш браузера или Cloudflare — когда вы запускаете rake avatars:refresh для такого количества пользователей, это происходит не мгновенно. Вместо этого создаются тысячи задач в Sidekiq, которые обрабатываются в фоновом режиме и могут занять несколько часов в зависимости от процессора вашего сервера. Извините, я должен был упомянуть Sidekiq и то, что процесс может занять время в зависимости от количества пользователей.

Перейдите на your-forum.com/sidekiq/queues и наблюдайте за очередью. Подождите, пока она полностью опустеет. Как только Sidekiq завершит работу, все размеры должны сохраниться в вашем бакете R2, и, я думаю, загрузка аватаров должна вернуться к нормальной скорости.

Хм, кажется, происходит что-то другое. В моих очередях ничего нет. Но если я нажимаю на аватар любого пользователя, в логах tail -f log/production.log появляется сообщение: Sent file /var/www/discourse/tmp/avatar_proxy/3689d91eb5e1013beef831c585b5e62edeeecbd6.jpeg (0.2ms)

Ого, хорошо. Это, скорее всего, ключевое доказательство, указывающее на другую проблему.

avatar_proxy в логах обычно означает, что Discourse отказывается отдавать аватар напрямую через CDN Cloudflare R2. Вместо этого Discourse агрессивно перехватывает запрос, скачивает изображение из R2 в локальную папку /tmp сервера, а затем с помощью Ruby отдаёт его браузеру. То есть, похоже, CDN полностью обходится, что и объясняет задержку в 3 секунды — я подозреваю, что сервер вручную загружает и отдаёт файл при каждом запросе :grimacing:

Discourse использует avatar_proxy в нескольких очень специфических сценариях, и обычно это настройка приватности или безопасности, которая заставляет сервер маскировать внешние URL.

Проверьте следующие настройки в админ-панели: Настройки сайта:

Найдите external system avatars url — если в этом поле что-то есть (например, /letter_avatar_proxy/v4/...), удалите всё, чтобы поле было пустым. Это должно остановить проксирование Discourse для аватаров по умолчанию. Также стоит проверить uploaded avatars allowed groups и убедиться, что там указано TL_0.

Возможно, стоит также перепроверить DISCOURSE_S3_CDN_URL, чтобы убедиться, что он указан верно, без лишнего слэша в конце или опечаток.

Перенастройка пользовательских аватаров:
Похоже, что в вашей базе данных всё ещё содержатся прямые URL-адреса бакета R2 вместо вашего нового CDN-URL. Поскольку они не совпадают, ваш форум, вероятно, проксирует их в целях безопасности.

Проверьте в консоли Rails, с чем именно борется Discourse:

./launcher enter app
rails c

Выберите имя пользователя с медленно загружающимся аватаром

u = User.find_by_username("the_selected_username")
u.user_avatar.custom_upload.url

Если вывод возвращает прямой URL бакета, значит предыдущая перенастройка не охватила всё (возможно, была упущена поддомен или схема).

Чтобы исправить это, подключитесь по SSH к серверу, снова зайдите в контейнер (не в rails) (./launcher enter app) и запустите инструмент перенастройки (снова, лол), чтобы заменить прямой URL на ваш CDN-URL:

discourse remap "https://<ваш-прямой-cloudflare-url>.r2.cloudflarestorage.com" "https://cdn.your-domain.com"

Затем запустите его второй раз, используя // вместо https://, на всякий случай.

Кстати, просто из любопытства: какой хостинг-провайдер вы используете? У меня такая же общая настройка, как у вас, и я ещё не сталкивался с этой проблемой. Поэтому мне также интересно узнать о вашей конфигурации, и я хочу попробовать воспроизвести это.

полученный URL указывает на CDN S3, и я могу открыть изображение в браузере.

Пока оставлю S3, так как сейчас он мне не особо нужен.

Кроме того, я уже давно использую Advinservers.

Спасибо за помощь, очень благодарен.