圈小蛙

Synapse/Matrix后台管理中媒体统计信息无法访问的解决方案

Synapse是用 Python/Twisted + Rust 编写的 Matrix 服务端,近期版本升级后,在第三方管理后台面板
synapse-admin进行后台管理时,出现媒体统计信息无法访问的情况。具体表现为请求以下路径,出现卡死/Timeout:

/_synapse/admin/v1/statistics/users/media?dir=b&limit=25&order_by=media_length
/_synapse/admin/v1/statistics/users/media?limit=1
/_synapse/admin/v1/statistics/users/media?order_by=media_length

分析发现:Synapse出现了HTTP缓冲或长连接阻塞。该接口需要遍历 local_media_repositoryremote_media_cache 进行全表聚合与排序。当媒体表数据量大、缺失复合索引或数据库被行级锁/VACUUM 阻塞时,查询会严重卡死,直到触发 120s 超时。

彻底排查与解决步骤

1. 宿主机内部直接直连 Synapse 测试

在服务器宿主机终端,直接请求 Synapse 容器的 8008 端口:

docker exec -it synapse-server curl -i -H "Authorization: Bearer <你的ACCESS_TOKEN>" \
  "http://127.0.0.1:8008/_synapse/admin/v1/statistics/users/media?dir=b&limit=25&order_by=media_length"

2. 检查 PostgreSQL 慢查询与锁

进入 PostgreSQL 执行,查看该查询是否正处于锁等待或全表扫描:

SELECT pid, now() - query_start AS duration, query, state, wait_event_type, wait_event 
FROM pg_stat_activity 
WHERE query LIKE '%statistics%' OR query LIKE '%media%'
ORDER BY duration DESC;

3. 补全 Media 表索引

如果数据库中媒体数据量大,缺少索引会导致大范围 Seq Scan:

-- 检查并为本地媒体表创建聚合与排序索引
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_local_media_user_length 
ON local_media_repository (created_by, media_length);

-- 统计信息分析更新
ANALYZE local_media_repository;
ANALYZE remote_media_cache;

4. 避免全量字段排序(API 传参调整)

在数据量极大时,按 order_by=media_length 排序代价极高。可以尝试:

5. 客户端/反向代理超时调整

调大Nginx代理超时时间:

proxy_read_timeout 300s;
proxy_send_timeout 300s;

经过多番测试,这些问题都排除了,最后从一个报错信息直接锁定了最终且唯一的根本原因

File "/usr/local/lib/python3.13/site-packages/synapse/util/json.py", line 48, in _handle_extra_mappings
    raise TypeError(
        "Object of type %s is not JSON serializable" % obj.__class__.__name__
    )
TypeError: Object of type Decimal is not JSON serializable

根本原因剖析

  1. PostgreSQL 的聚合机制:在数据库中执行 SUM(media_length) 时,PostgreSQLbigint/integer 执行 SUM 运算后返回的字段数据类型是 NUMERIC
  2. Python 数据类型转换:Python 的数据库驱动(psycopg2 / psycopg3)将 PostgreSQL 的 NUMERIC 映射成了 Python 的 decimal.Decimal 对象。
  3. JSON 序列化崩溃与协程丢失:在把查询结果组装成 JSON 返回给客户端时,Synapse 的 JSON 编码器 synapse.util.json 没有处理 Decimal 类型,直接抛出了 TypeError
  4. 为什么表现为“卡死 120 秒”:由于该异常发生在 Twisted 的后台序列化线程池中(Unhandled error in Deferred),异常未能向外传递给 HTTP Request 处理器去输出 500 错误,导致 HTTP 连接一直处于挂起状态,直到 120 秒客户端超时断开连接。

解决方案:为 Synapse 的 JSON 编码器增加 Decimal 支持

让 Synapse 的 JSON 序列化器能够自动将 Decimal 转换为 int

1、进入 Synapse 容器修改 json.py

docker exec -it synapse-server bash

2、编辑 /usr/local/lib/python3.13/site-packages/synapse/util/json.py 文件(或通过宿主机挂载修改):在文件开头的 import 处引入 Decimal

from decimal import Decimal

3、在 _handle_extra_mappings(obj: Any) 函数中加入 Decimal 处理分支:

def _handle_extra_mappings(obj: Any) -> Any:
if isinstance(obj, Decimal):
return int(obj)
# ... 保留原有的其他判断与 raise TypeError ...

4、保存后在宿主机重启 Synapse 容器:

docker compose restart synapse

修复并重启后,再次调用媒体统计接口,将秒级直接返回完整的统计 JSON。

Exit mobile version