头像迁移到 S3 兼容的 R2 后加载时间过长

你好,

我刚迁移到 R2,一切都非常顺利。所有图片都使用了 S3 CDN 链接。但我发现了一个问题:头像加载时间很长。平均需要 3 到 4 秒,无论我是点击用户头像还是查看帖子内部。这是否正常?

嗯,我怀疑可能是以下三种问题之一,但对我来说最有可能的是即时调整大小。

1. 即时头像调整大小

当你将上传迁移到 R2 时,它移动了原始图像;然而,Discourse 使用多种不同尺寸的头像(例如帖子中为 45px,用户卡片中为 120px)。

如果这些特定优化尺寸迁移不完全,或者尚未生成,Discourse 必须在用户点击时同步生成它们:

  1. Discourse 从 R2 下载原始头像到本地服务器
  2. 使用 ImageMagick 调整大小
  3. 将新尺寸上传回 R2
  4. 将浏览器重定向到新 URL,此过程需要 3 到 4 秒

验证方法: 硬刷新页面——如果头像第一次加载需要 3-4 秒,但第二次加载立即显示,那么这正是正在发生的事情。

修复方法: 随着用户浏览和尺寸生成,这将自然修复。但你可以通过强制服务器在后台预生成所有头像来立即修复它,方法是 SSH 进入你的服务器并运行:

./launcher enter app
rake avatars:refresh

2. 3 秒 IPv6 超时

如果头像在多次刷新后每次仍然需要 3-4 秒,它们可能遇到了网络超时。

Cloudflare R2 API 端点是双栈的,即它们同时使用 IPv4 和 IPv6。如果你的服务器 Droplet 分配了 IPv6 地址,但主机的 IPv6 网关路由不正确,Ruby 内部连接到 R2 存储桶时将首先尝试 IPv6,挂起 3 秒(这是 Linux TCP 的默认超时),失败,然后立即使用 IPv4 成功。

验证方法: SSH 进入服务器并运行:

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

如果它挂起几秒钟并失败,则服务器的 IPv6 已损坏,导致每个内部 S3 API 检查都遭受 3 秒延迟。

修复方法: 你需要修复主机控制面板中的 IPv6 路由,或者甚至完全禁用 Droplet 上的 IPv6。

3. Gravatar 延迟

如果你的网站配置为检查 Gravatar 更新,它可能在渲染头像之前会 ping Gravatar 的外部服务器。如果服务器的出站连接缓慢(通常也与 DNS 或 IPv6 有关),它可能会阻止头像渲染。

验证方法: 在服务器上运行此命令
curl -I -6 https://gravatar.com
如果它挂起 3 秒,则 IPv6 已损坏(见上文)

与 Gravatar 相关的修复: 在你的 Discourse 设置中,转到 automatically download gravatars(自动下载 Gravatar),暂时关闭它,看看是否能修复——我认为这不是问题,但如果是,你可以保持设置关闭,或者如上所述修复 IPv6 路由,或者更改 DNS 解析器。

感谢您的快速回复。我想我之前已经尝试过 rake avatars:refresh 了,但我不完全确定。

以前对我有效、能让头像立即打开的方法是:第一次点击时它会加载,第二次点击时就能瞬间打开。但这可能是由于缓存导致的。另外,我刚刚测试了您的第二个建议,返回的是“HTTP/2 301”,并附带其他几行信息。第三个建议的情况也相同。由于我需要恢复快照,我将在几天后再次运行 avatars:refresh。再次感谢!

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,因为针对 Cloudflare 和 Gravatar 的 curl 命令结果看起来都符合预期。方便的时候请尝试运行 rake avatars:refresh,并告诉我是否有效。

嗨,Lilly,我仍然遇到同样的问题。即使执行了 rake avatars:refresh 也一样。/latest 页面也有同样的问题。我尝试清理了浏览器和 Cloudflare 的缓存,但仍未成功。也许我需要再等一段时间?我目前是在一个有 4500 名用户的论坛上进行测试。

不要再清除浏览器或 Cloudflare 的缓存了——当你为大量用户运行 rake avatars:refresh 时,操作并不会立即完成。相反,它会将成千上万个任务放入 Sidekiq 后台处理,具体耗时取决于服务器的 CPU 性能,可能需要数小时。抱歉,我本应该提到 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 拒绝直接从 Cloudflare R2 CDN 提供头像。相反,Discourse 会积极拦截请求,将图片从 R2 下载到本地服务器的 /tmp 文件夹,然后使用 Ruby 将图片提供给浏览器。因此,我认为这完全绕过了 CDN,也解释了 3 秒的延迟——我怀疑服务器是在每次请求时都手动获取并加载文件:grimacing:

Discourse 仅在少数非常特定的场景下使用 avatar_proxy,通常是因为隐私或安全设置强制服务器屏蔽外部 URL。

请在“管理”>“站点设置”中检查以下设置:

找到 external system avatars url(外部系统头像 URL)——如果该框中有任何内容(例如 /letter_avatar_proxy/v4/...),请将其清空。这应该能阻止 Discourse 代理默认的字母头像。同时也值得检查 uploaded avatars allowed groups(允许上传头像的组),确保其设置为 TL_0

另外,请再次检查 DISCOURSE_S3_CDN_URL,确保其正确无误,没有尾随斜杠或拼写错误?

重新映射自定义头像:
看起来您的数据库中仍然包含原始的 R2 存储桶 URL,而不是新的 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://<your-raw-cloudflare-url>.r2.cloudflarestorage.com" "https://cdn.your-domain.com"

然后,以防万一,使用 // 代替 https:// 再运行一次。

顺便问一下,出于好奇,您使用的是哪家主机服务?我有和您类似的通用设置,但尚未遇到此问题。所以我也很感兴趣您的配置,并想尝试复现一下这个问题。

我得到的 URL 显示的是 S3 CDN 的链接,我可以在浏览器中打开这张图片。

暂时先不用 S3 了,因为我现在确实不需要它。

而且我已经使用 Advinservers 很长时间了。

谢谢你的帮助,非常感谢。

我已经完成了进一步的测试,并确认该问题与 R2 无关。即使在使用正确配置的 AWS S3 时,相同的行为(帖子上的头像加载缓慢,以及点击用户名时加载缓慢)仍然存在。此外,这也不是防火墙问题,因为 ufw status 确认防火墙当前已禁用。我计划本周末在暂存环境中进行更多测试,在那里我可以长时间运行论坛,并在必要时将其离线,而不会有任何问题。

您是否同时配置了站点和 S3 CDN?

是的,我两个都在用。这个存储桶通过 CloudFront 连接到 S3 CDN URL,Discourse CDN URL 也是如此。

在进行 R2 测试时,我没有使用 Discourse CDN URL。

除了那个无法使用的那个?

那么,没有使用 Discourse CDN 的 R2 配置就是你遇到问题的地方?

我不假装完全理解你的问题,也不完全清楚头像图片的工作原理,但我建议在进一步测试之前先配置好 CDN。问题可能在于你迁移到了新的存储桶,而这些图片需要重新生成。

这里的头像加载自看起来像 Discourse CDN 的地址:https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/lilly/48/555832_2.png

用户个人资料在首次加载后,后续加载速度会变快吗?

同样,我不保证我完全理解,也没有查看过代码,但我猜测这些图片是由 Discourse CDN 提供的,而 Discourse 则依赖后续请求从 CDN 获取数据。我认为这解释了为什么在没有 Discourse CDN 的 R2 版本上无法正常工作(或工作缓慢)。

也许我没有完全理解你的意思,但我有两个运行 R2 对象存储的网站,它们没有遇到这个问题。:woman_shrugging:t2:

他们难道没有 Discourse CDN 吗?如果没有,那可能是我搞错了。如果有的话,那么问题可能出在:当你有 S3 存储桶时,却缺少 Discourse CDN,这可能导致了该问题。

不过,头像在使用 S3 和不使用 S3 时表现不同,这看起来确实有些奇怪。

实际上,我用 4 种不同的配置进行了测试:

  1. R2,不使用 Discourse CDN

  2. R2,使用 Discourse CDN

  3. AWS S3,使用 Discourse CDN

  4. AWS S3,不使用 Discourse CDN

在所有情况下,我都使用了 S3 CDN URL:files.mydomain.com

对于使用 Discourse CDN 的情况,我使用了:cdn.mydomain.com

问题在于,在任何场景下,头像加载始终非常缓慢。

如果我打开一个主题,我会看到头像逐个加载。不过,这种情况只会出现一次。例如,如果我进入 admin/users,我只会看到昵称,然后头像才开始逐个加载。

如果我点击一个昵称,用户卡片会打开,头像会在 3-4 秒后出现。这也只会出现一次;如果再次点击,头像会立即出现,这很可能是由于缓存的原因。

PS:在进行每次测试时,我会从尚未使用 S3/R2 时恢复快照,删除存储桶,然后重新开始。

我猜这跟对象存储配置没什么关系。我觉得你的头像在这四种设置下都加载慢,因为瓶颈不在存储服务商,而是你的站点 Droplet 和你的存储桶之间的网络延迟,或者你的服务器 CPU 在实时调整图片大小时有点吃力。我不清楚你的服务器/CDN 配置,也不知道这里涉及的距离有多远。但我觉得一旦边缘缓存构建完成,你用哪种存储应该都不重要了,只要你坚持用一种,让缓存自己构建就行。不过我现在只是在瞎猜,因为我实在想不出其他原因了。:woman_shrugging:t2: :grinning_cat_with_smiling_eyes:

我现在也不知道该怎么想了。对于 R2,我使用的是西欧(WEUR)区域,而对于 S3,我使用的是 eu-north-1。以下是我的 VPS 规格:

AMD Turin 处理器(4 个 vCore) 8GB DDR5 ECC 内存 256GB NVMe SSD 存储 5TB 带宽(10 Gbit) 位于加利福尼亚州洛杉矶

也许下次我应该尝试使用美国的区域?我认为 R2 无法做到这一点。

这符合我的预期。生成所有头像的过程比较慢。Rake 任务会处理这项工作,但耗时较长,尤其是在用户数量较多的情况下。首次访问某个用户时,系统会生成头像,并需要几秒钟时间来运行外部程序,将图像数据处理为各种尺寸。此后,一切都会恢复正常。我想你只需要运行 Rake 任务,等待它生成各种尺寸的头像即可?

是的,我想这就是我想表达的意思,但可能没有解释清楚:

但我记得你说的是每次加载都很慢。:thinking:

我认为每次更改 S3 配置或清除缓存时,你都在从头开始。如果用户头像很多,这将花费很长时间。