海量存储交易

您好,

我们的存储系统偶尔会面临巨大的事务负载。我们尚未发现此类事件的具体时间表或时间模式,但它每天至少发生一次,且持续时间从 10 分钟到数小时不等。

在这些高负载期间,整个系统表现略显异常,例如读取话题时无法被正确识别,导致它们仍会出现在“新话题”和/或“未读话题”中。

似乎 Discourse 正在移动大量文件,尤其是 READ 操作显著增加。我们已经检查过外部流量是否也在增加,但结果显示并非如此。受影响的只有 Discourse 与存储之间的流量。

我们首次注意到这一行为是在从 Discourse 2.4.0.beta9 升级到 2.4.0.beta10 之后,但不确定在此之前是否已经发生过。目前我们运行的是 2.5.0.beta4 版本。

我们的 Discourse 部署在 Azure 环境中,并通过 SMBv3 挂载了 Premium Storage,通常运行良好。

有人能解释一下这是什么情况吗?起初我们怀疑是 sidekiq 任务 MigrateUploadScheme 导致的,但如果该任务确实是造成这些高负载的原因,我们应该更频繁地观察到这种现象。此外,我们也没有发现其他可能负责此问题的任务。


由于“突发 IOPS”,您可以看到在约 800k 事务/30 分钟处出现了一个峰值。当这些额度用尽后,系统会被限制到约 250k 事务/30 分钟。因此,请勿将此峰值视为异常,它只是 Azure 存储层级提供的有限/计点奖励。
通常情况下,我们每 30 分钟的事务量在 5k 到 40k 之间。

目前我们不知道应该从哪里入手排查,任何建议或提示都将不胜感激。

此致
Sascha

您是否启用了自动备份?请检查“启用自动备份”和“备份频率”站点设置。

您好。
不,备份已完全禁用。我们使用的是 PSQL 实例自身的备份保留功能以及(非自动化的)存储快照。

您能否在 Postgres 中启用统计功能,以查找运行时间较长的查询或重复查询?

您需要启用 pg_stat_statements 模块,并深入分析其生成的统计信息。

我们之前已启用统计功能以解决一些瓶颈问题。我之前的帖子 数据库性能建议(来自 Azure PSQL) 便是由此而来。

以下是过去一周耗时最长的前 10 个查询:

如果您需要完整的查询语句,请告诉我。另外,了解为何这些查询会影响存储使用情况也很有意思。

这很可能是第一个完整的查询,它的持续时间为 1 分钟,执行了 14 次,规模相当大。

您好。第一个查询将通过 DirectoryItem.refresh_period 触发/执行(我猜)。

因此,实际的查询如下:

摘要
WITH x AS (SELECT
			u.id user_id,
			SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = 2 THEN 1 ELSE 0 END) likes_received,
			SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = 1 THEN 1 ELSE 0 END) likes_given,
			COALESCE((SELECT COUNT(topic_id) FROM topic_views AS v WHERE v.user_id = u.id AND v.viewed_at > '2019-10-28 23:52:24.911261'), 0) topics_entered,
			COALESCE((SELECT COUNT(id) FROM user_visits AS uv WHERE uv.user_id = u.id AND uv.visited_at > '2019-10-28 23:52:24.911261'), 0) days_visited,
			COALESCE((SELECT SUM(posts_read) FROM user_visits AS uv2 WHERE uv2.user_id = u.id AND uv2.visited_at > '2019-10-28 23:52:24.911261'), 0) posts_read,
			SUM(CASE WHEN t2.id IS NOT NULL AND ua.action_type = 4 THEN 1 ELSE 0 END) topic_count,
			SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = 5 THEN 1 ELSE 0 END) post_count
			FROM users AS u
			LEFT OUTER JOIN user_actions AS ua ON ua.user_id = u.id AND COALESCE(ua.created_at, '2019-10-28 23:52:24.911261') > '2019-10-28 23:52:24.911261'
			LEFT OUTER JOIN posts AS p ON ua.target_post_id = p.id AND p.deleted_at IS NULL AND p.post_type = 1 AND NOT p.hidden
			LEFT OUTER JOIN topics AS t ON p.topic_id = t.id AND t.archetype = 'regular' AND t.deleted_at IS NULL AND t.visible
			LEFT OUTER JOIN topics AS t2 ON t2.id = ua.target_topic_id AND t2.archetype = 'regular' AND t2.deleted_at IS NULL AND t2.visible
			LEFT OUTER JOIN categories AS c ON t.category_id = c.id
			WHERE u.active
			AND u.silenced_till IS NULL
			AND u.id > 0
			GROUP BY u.id)
	UPDATE directory_items di SET
		 likes_received = x.likes_received,
		 likes_given = x.likes_given,
		 topics_entered = x.topics_entered,
		 days_visited = x.days_visited,
		 posts_read = x.posts_read,
		 topic_count = x.topic_count,
		 post_count = x.post_count
	FROM x
	WHERE
	x.user_id = di.user_id AND
	di.period_type = 5 AND (
	di.likes_received <> x.likes_received OR
	di.likes_given <> x.likes_given OR
	di.topics_entered <> x.topics_entered OR
	di.days_visited <> x.days_visited OR
	di.posts_read <> x.posts_read OR
	di.topic_count <> x.topic_count OR
	di.post_count <> x.post_count )

我可以提供一些背景信息,以便您更好地评估:
我们有约 43 万用户、160 万个主题(不含已删除的)和 840 万篇帖子(不含已删除的),分布在 241 个分类中,同时有 1200 万条 user_actions 记录。

但我仍然不明白,为什么慢查询会导致存储(/uploads)上出现如此大量的 READ 操作。我是否遗漏了什么?

这听起来不太对。我很困惑,如果你在 Azure 上,你是如何存储文件的?这是单容器设置吗?上传功能是如何配置的?

目录更新非常缓慢。如果你无法承担更新它的成本,可以禁用目录 https://meta.discourse.org/u。我们有一些非常具体的计划,将用户搜索功能添加到全文页面搜索中,这样你就可以不再依赖目录。

抱歉造成混淆。我将尝试解释我们如何配置 Discourse。

首先,这不是单个容器的设置。我们将其拆分,使用 Azure 自身的 Redis、PostgreSQL 和存储服务。

有 3 台虚拟机运行 Discourse + Nginx。一个独立的 Azure 文件共享 通过 SMBv3 挂载到这三台虚拟机上,该挂载点作为卷附加到 Discourse 容器中。
/public/uploads/tmp/javascript-cache/tmp/stylesheet-cache 将存储在此处。

此外,我们还使用 Azure Cache for RedisAzure Database for PostgreSQL

虚拟机磁盘、存储和数据库彼此分离。因此,数据库负载不会(或不应)影响存储或虚拟机性能,并且我们可以利用这些服务的优势(例如您上面提到的 PostgreSQL 实例上的数据库统计信息和性能建议)。

此设置还允许我们单独监控每个服务/部分,我们发现我们的 Azure 文件共享(uploads 所在位置)接收了大量的事务(如您在我第一篇帖子中所见)。这些事务主要是 READ 操作。
由于此存储(文件共享)仅由 Discourse 本身使用,我们试图找出是哪个进程/作业负责这些每天发生 1-2 次、持续几分钟到几小时的事件。

除了这些巨大的事务计数外,此设置运行得相当不错,只有一些运行缓慢的查询在少数情况下影响性能(例如,少量用户的活动摘要页面加载可能需要长达 15 秒)。

我希望我已经解释了为什么我会疑惑数据库性能如何会影响静态文件的事务计数。

此致
感谢到目前为止的付出
Sascha

附言:
我们在设置中使用自定义 Docker 镜像,我完全理解您无法/不会为自定义解决方案提供支持。
我们唯一想知道的是,哪个进程/作业/设置可能导致这些存储事务计数(部分拖慢了整个设置),以及我们可以做些什么来避免这种情况。

就性能而言,我认为最好的方案是将上传存储切换到 S3 或兼容 S3 的存储引擎,并配合 CDN 使用。使用 SMB 共享进行上传是我们从未测试过的场景。我猜测我们每天会检查上传文件的大小,这在本地环境下速度极快,但在 SMB 上会非常慢。

感谢您的澄清和建议。确实,当涉及访问大量文件时,SMB 可能会非常慢。大多数情况下这没有区别,因为频繁访问的文件已被 Nginx 缓存(我们经常将 Discourse Nginx 示例配置中的更改应用上去)。但当涉及到那些临时访问时,性能会下降。

我们一直在寻找其他存储解决方案。使用外部 S3(兼容)存储可能会可能会破坏我们部分安全理念。每个涉及的实例/服务(数据库、虚拟机、存储等)都绑定到私有网络,无法从公共互联网访问。所有公共流量均由 Azure 应用网关管理。
不幸的是,Azure Blob 存储不兼容 S3,但我们或许应该投入一些时间来利用它。目前可能的解决方案是使用 Discourse Blob Storage 插件,或在容器内直接使用 blobfuse

无论如何,感谢您的时间和帮助。是否有理由每天检查上传大小?是否有办法关闭此功能?

此致

这很可能来自这里:

我认为您可以禁用拉取热链接图片,或者在插件中创建一个猴子补丁来禁用此功能:

或者,只需通过在容器配置中添加内容,将容器中的 du 命令别名设置为无操作(no-op)即可。

非常感谢,这会有很大帮助。使用 SMB 共享运行 du 命令的开销会非常大,因为我们在此文件共享上托管了约 80 万个文件(38GB)。

由于潜在的法律法规/版权问题,我们已经禁用了 pulling_hotlinked_images

我认为将 du 命令设置别名稍微有些侵入性,虽然通过插件进行修补是个好主意。我们是否可以在构建镜像时直接应用一个 git-patch,例如使用以下内容:

def self.used(path)
    output = Discourse::Utils.execute_command('df', '-Pk', path)
    size_line = output.split("\n")[1]
    size_line.split(/\s+/)[2].to_i * 1024
end

鉴于 du 可能更可靠或准确,我认为 df 应该能满足我们的需求,并且不会破坏其他功能。