
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_repository 和 remote_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 排序代价极高。可以尝试:
- 移除
order_by=media_length,改用默认的order_by=user_id先验证接口是否正常返回:GET /_synapse/admin/v1/statistics/users/media?dir=b&limit=25 - 减少单页数量(如
limit=10)。
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
根本原因剖析
- PostgreSQL 的聚合机制:在数据库中执行
SUM(media_length)时,PostgreSQL 对bigint/integer执行SUM运算后返回的字段数据类型是NUMERIC。 - Python 数据类型转换:Python 的数据库驱动(psycopg2 / psycopg3)将 PostgreSQL 的
NUMERIC映射成了 Python 的decimal.Decimal对象。 - JSON 序列化崩溃与协程丢失:在把查询结果组装成 JSON 返回给客户端时,Synapse 的 JSON 编码器
synapse.util.json没有处理Decimal类型,直接抛出了TypeError。 - 为什么表现为“卡死 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。
圈小蛙