OpenWebUI调优
大量性能提升、流畅度优化
当我们的用户量增加到一定的程度时(比如当我的实时在线用户超过100人时),即便服务器的硬件配置再高,也会变得非常卡顿,这是因为OpenWebUI本身机制上有很多坑,接下来我们在GPT-5的帮助下逐步优化,整体流畅度可以提升10倍以上。
首先,OpenWebUI默认是单线程运行的,这也是导致卡顿的罪魁祸首,最大的问题根源,我们可以通过下面这个命令查看进程数:
# 1) 用宿主机的 ps 能力查看容器内进程
docker top open-webui -eo pid,comm,args | sed 's/\x00/ /g' | egrep -i "uvicorn|gunicorn|open-webui|python" || docker top open-webui
# 2) 粗略计数(看到几行 uvicorn/gunicorn 通常就是几个 worker)
docker top open-webui | egrep -i "uvicorn|gunicorn" | wc -l
对此,我们需要进行多进程配置,此时我们需要修改compose文件:
services:
postgre:
image: docker.1panel.live/library/postgres@sha256:4d89c904835259bc58876520e56267ca07a4ebd6a027f7814bbbf91b50d685be
container_name: postgre
restart: always
environment:
- POSTGRES_USER=st
- POSTGRES_PASSWORD=STshentong
- POSTGRES_DB=openwebui
volumes:
- ./postgres_data:/var/lib/postgresql/data
network_mode: host #端口5432
open-webui:
image: ghcr.nju.edu.cn/ovinc-cn/openwebui:latest
container_name: open-webui
volumes:
- ./open-webui:/app/backend/data
restart: always
environment:
- DATABASE_URL=postgresql://st:STshentong@localhost:5432/openwebui # 连接到 openwebui 数据库
- UVICORN_WORKERS=6
- WEBSOCKET_MANAGER=redis
- ENABLE_WEBSOCKET_SUPPORT=True
- WEBSOCKET_REDIS_URL=redis://localhost:6379
- REDIS_URL=redis://localhost:6379
- WEBUI_NAME=ChatST
- AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST=1
- AIOHTTP_CLIENT_TIMEOUT_OPENAI_MODEL_LIST=1
- USER_AGENT=${USER_AGENT:-Mozilla/5.0 (compatible; OpenWebUI/1.0; +https://github.com/open-webui)}
- WEBUI_SECRET_KEY=85fafa5e-0992-4d9b-a84c-6679646040f3
- LICENSE_KEY=enterprise
- ORGANIZATION_NAME=ST-STUDIO
- CUSTOM_NAME=ChatST
build:
args:
USER_AGENT: $USER_AGENT
network_mode: host #端口8080
ulimits:
nofile:
soft: 1048576
hard: 1048576
depends_on:
- postgre
- redis
redis:
image: docker.1panel.live/library/redis:latest
container_name: redis
restart: always
network_mode: host #端口6379
ulimits:
nofile:
soft: 1048576
hard: 1048576
tika:
image: docker.1panel.live/apache/tika:latest-full
container_name: tika
network_mode: host #端口9998
restart: always
注意,在这份compose文件中,相较于我们之前的版本有很多的修改项,首先我们将数据库的版本号固定了,采用SHA256的形式写死在文件中,这样可以避免PostgreSQL版本升级造成的变更;然后我们在环境变量中配置了进程数为6个,这可以大幅提升并发性能,大幅改善多用户场景卡顿问题(具体来说,进程数建议设置为服务器核心数或略少于核心数即可,比如我在8H的服务器上使用6进程);另外需要特别注意的是,要开启多进程必须要确保ws的管理器是redis,这里我们也在环境变量中显式指定了;然后我们还显式指定了ulimit文件描述符nofile限制为1048576,虽然默认值大概率就是这个,但是写死更好一些。
修改compose文件后需要重建一下发生修改的容器:
docker compose up -d --force-recreate open-webui redis
然后我们同样可以通过上面的指令去查看多workers配置是否生效。然后也可以验证一下我们的ulimit设置有没有生效:
docker exec -it open-webui sh -lc 'ulimit -n; grep -i "open files" /proc/1/limits'
docker exec -it redis sh -lc 'ulimit -n; grep -i "open files" /proc/1/limits'
docker exec -it redis sh -lc 'redis-cli CONFIG GET maxclients'
这样我们在docker层面和项目本身的优化就差不多了,主要还是进程数的提升。接下来我们就要进行数据库层面的优化了。数据库层面将有很多的优化项,首先我们查询一下数据库的当前参数:
# 进入容器执行一组常用视图(不会改配置)
docker exec -it postgre bash -lc '
psql -U st -d openwebui <<SQL
SELECT version();
SELECT name, setting FROM pg_settings
WHERE name IN (
'\''shared_buffers'\'','\''effective_cache_size'\'','\''work_mem'\'','\''maintenance_work_mem'\'',
'\''max_wal_size'\'','\''checkpoint_timeout'\'','\''checkpoint_completion_target'\'',
'\''wal_compression'\'','\''jit'\'','\''random_page_cost'\'','\''effective_io_concurrency'\'',
'\''max_connections'\'','\''synchronous_commit'\''
) ORDER BY name;
-- 看连接与等待(是否有锁/连接等待)
SELECT state, wait_event_type, wait_event, count(*)
FROM pg_stat_activity WHERE datname='\''openwebui'\'' GROUP BY 1,2,3 ORDER BY 4 DESC;
-- 死元组(表膨胀的信号)
SELECT relname, n_dead_tup
FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 15;
-- 检查检查点频率
SELECT * FROM pg_stat_bgwriter;
SQL'
通过这个命令查看当前配置,然后我们可以一步到位修改参数(请注意下面这套参数是基于8GB的物理内存的服务器,如果内存大/小,需要按照比例进行修改):
# 1) 打开观察面板(强烈建议先做)——需重启一次
docker exec -it postgre bash -lc "
echo \"
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 10000
pg_stat_statements.track = all
\" >> /var/lib/postgresql/data/postgresql.auto.conf"
docker compose restart postgre
docker exec -it postgre bash -lc "psql -U st -d openwebui -c 'CREATE EXTENSION IF NOT EXISTS pg_stat_statements;'"
# 2) 一组通用的、风险低的参数(除 * 标注外都支持 reload)
docker exec -it postgre bash -lc '
psql -U st -d openwebui <<SQL
-- * 需要重启:shared_buffers
ALTER SYSTEM SET shared_buffers = '\''1GB'\'';
-- 预估 OS+PG 可用缓存(通常取 shared_buffers 的 3x)
ALTER SYSTEM SET effective_cache_size = '\''3GB'\'';
-- 排序/哈希的工作内存(并发高别贪大)
ALTER SYSTEM SET work_mem = '\''32MB'\'';
-- VACUUM/重建索引用
ALTER SYSTEM SET maintenance_work_mem = '\''512MB'\'';
-- WAL/检查点(更平滑)
ALTER SYSTEM SET max_wal_size = '\''4GB'\'';
ALTER SYSTEM SET min_wal_size = '\''1GB'\'';
ALTER SYSTEM SET checkpoint_timeout = '\''15min'\'';
ALTER SYSTEM SET checkpoint_completion_target = 0.9;
ALTER SYSTEM SET wal_compression = on;
-- SSD 默认:更积极的并行/预取与成本估计
ALTER SYSTEM SET random_page_cost = 1.2;
ALTER SYSTEM SET effective_io_concurrency = 200;
-- JIT 对短小 OLTP 常常负收益
ALTER SYSTEM SET jit = off;
-- 可选:把慢 SQL 门槛设为 500ms,便于排查
ALTER SYSTEM SET log_min_duration_statement = '\''500ms'\'';
SELECT pg_reload_conf();
SQL'
# 3) 重启以让 shared_buffers 生效
docker compose restart postgre
# 4) 验证
docker exec -it postgre bash -lc "psql -U st -d openwebui -c \"SELECT name, setting FROM pg_settings WHERE name IN ('shared_buffers','effective_cache_size','work_mem','maintenance_work_mem','max_wal_size','checkpoint_timeout','wal_compression','jit','random_page_cost','effective_io_concurrency');\""
修改后我们可以使用下面的指令验证一下关键项的修改是否到位:
# 看 shared_buffers 是否到 1GB(这条最直观)
docker exec -it postgre bash -lc "psql -U st -d openwebui -c 'SHOW shared_buffers;'"
# 同时核实来源(来自哪个文件/是否 pending_restart)
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \"SELECT name, setting, source, sourcefile FROM pg_settings WHERE name IN ('shared_buffers','shared_preload_libraries');\""
# 确认 pg_stat_statements 已预加载且可用
docker exec -it postgre bash -lc \
\"psql -U st -d openwebui -c \\\"SHOW shared_preload_libraries; SELECT extname, extversion FROM pg_extension WHERE extname='pg_stat_statements'; SELECT count(*)>0 AS can_query FROM pg_stat_statements;\\\" \"
也可以使用下面的指令查询有没有什么修改需要等待重启才能完成,若有,重启数据库容器即可:
docker exec -it postgre bash -lc "psql -U st -d openwebui -c \"SELECT name, pending_restart FROM pg_settings WHERE pending_restart;\""
然后这时有一个注意事项,当我们修改了这些参数后,系统盘的占用会快速增加,这主要是因为我们开启了大量的日志记录用于分析,他会写入大量的日志文件(前提是当前有大量用户正在使用),这时我们需要修改PostgreSQL的日志存储逻辑:
docker exec -it postgre bash -lc '
psql -U st -d openwebui <<SQL
ALTER SYSTEM SET logging_collector = on; -- 需重启
ALTER SYSTEM SET log_destination = '\''csvlog'\''; -- 写 CSV,便于分析
ALTER SYSTEM SET log_directory = '\''log'\''; -- 相对 PGDATA,即 /www/postgres_data/log
ALTER SYSTEM SET log_filename = '\''postgresql-%Y-%m-%d_%H%M%S.csv'\'';
ALTER SYSTEM SET log_rotation_age = '\''1h'\'';
ALTER SYSTEM SET log_rotation_size = '\''200MB'\'';
ALTER SYSTEM SET log_truncate_on_rotation = on;
-- 如果不是在排查期,慢日志阈值建议先提到 1s,避免量太大
ALTER SYSTEM SET log_min_duration_statement = '\''1s'\'';
SELECT pg_reload_conf();
SQL'
# 启用 logging_collector 需要重启
docker restart postgre
将日志存放在Postgre容器的数据卷内,也就是我们的数据盘中(前提是你的服务器分系统盘和数据盘),并且写了上限为200MB滚动记录。有了日志记录之后,我们就可以在业务跑一段时间之后来分析慢查询了,通过查看SQL查询最慢的几条记录来进行针对性的优化:
docker exec -it postgre bash -lc \
"PAGER=cat psql -U st -d openwebui -P pager=off -c \"
SELECT
round((total_exec_time/1000)::numeric,1) AS total_s,
calls,
round(mean_exec_time::numeric,1) AS mean_ms,
rows,
query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;\""
这个命令可以查出最慢的20条,如果需要进一步筛选也可:
docker exec -it postgre bash -lc \
"PAGER=cat psql -U st -d openwebui -P pager=off -c \"
SELECT
calls,
round(mean_exec_time::numeric,1) AS mean_ms,
round((total_exec_time/1000)::numeric,1) AS total_s,
rows,
query
FROM pg_stat_statements
WHERE calls >= 100
ORDER BY mean_exec_time DESC
LIMIT 20;\""
然后我们将查询的结果发给AI让他帮我们撰写索引创建命令即可,创建了相应的索引后慢查询的速度会有显著提升;此外,我们还可以开启lz4压缩来减少大json的体积,先看是否支持lz4压缩,理论上Postgre 14以上的版本默认都支持:
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \"SET default_toast_compression='lz4'; SHOW default_toast_compression;\""
然后开启lz4压缩:
# 写入 postgresql.auto.conf
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \"ALTER SYSTEM SET default_toast_compression='lz4';\""
# 让配置生效(这个参数支持 reload,不必重启)
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \"SELECT pg_reload_conf();\""
# 验证
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \"SHOW default_toast_compression;\""
将大字段存储策略确保为EXTENDED:
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \"ALTER TABLE chat ALTER COLUMN chat SET STORAGE EXTENDED;\""
这里给出在我的生产环境中有效的提速索引创建命令:
# 1) “未分组(folder_id IS NULL)+ 用户 + 归档筛选 + 时间倒序”
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \
\"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chat_user_arch_upd_folder_null
ON chat (user_id, archived, updated_at DESC)
WHERE folder_id IS NULL;\""
# 2) “指定文件夹 + 用户 + 归档筛选 + 时间倒序”
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \
\"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chat_folder_user_arch_upd
ON chat (folder_id, user_id, archived, updated_at DESC);\""
docker exec -it postgre bash -lc "psql -U st -d openwebui -c 'ANALYZE chat;'"
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \
\"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chat_user_pinned_arch_upd
ON chat (user_id, pinned, archived, updated_at DESC);\""
# A) chat:归档 + 时间阈值(覆盖你的 DELETE 与按归档列出的列表)
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \
\"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chat_arch_upd
ON chat (archived, updated_at);\""
# B) credit_log:按时间清理/查询
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \
\"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_credit_log_created_at
ON credit_log (created_at);\""
# 建完跑统计
docker exec -it postgre bash -lc "psql -U st -d openwebui -c 'ANALYZE chat; ANALYZE credit_log;'"
# 启用扩展(一次性)
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \"CREATE EXTENSION IF NOT EXISTS pg_trgm;\""
# 给标题建 trigram 索引(大小写不敏感检索)
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \
\"CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_chat_title_trgm
ON chat USING gin (lower(title) gin_trgm_ops);\""
docker exec -it postgre bash -lc "psql -U st -d openwebui -c 'ANALYZE chat;'"
让chat表维护更积极:
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -c \"
ALTER TABLE chat SET (
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_vacuum_threshold = 1000,
autovacuum_analyze_scale_factor= 0.02,
autovacuum_analyze_threshold = 500
);\""
运营自检:
# 1) 索引是否真的被用到(看扫描次数)
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -P pager=off -c \"
SELECT relname AS table, indexrelname AS index, idx_scan
FROM pg_stat_user_indexes
WHERE relname IN ('chat','credit_log')
ORDER BY idx_scan DESC;\""
# 2) 表膨胀/死元组(观察 chat 是否被及时清理)
docker exec -it postgre bash -lc \
"psql -U st -d openwebui -P pager=off -c \"
SELECT relname, n_live_tup, n_dead_tup, vacuum_count, autovacuum_vacuum_count
FROM pg_stat_user_tables
WHERE relname IN ('chat','credit_log')
ORDER BY n_dead_tup DESC;\""
# 3) 慢 SQL Top20(已在用)
最后,因为我们对数据库启用了日志记录,时间久了可能会占用很多空间,因此对之前的清理脚本也进行了一些优化:
#!/bin/bash
# 安全开关(出错直接退出)
set -Eeuo pipefail
# Docker 容器 / 数据库信息
CONTAINER_NAME="postgre"
PG_USER="st"
DATABASE_NAME="openwebui"
# 受保护用户
PROTECTED_USERS="'wyk','wwf','syx'"
# PostgreSQL 日志清理配置(log_directory 我们之前设在 /www/postgres_data/log)
LOG_DIR="/www/postgres_data/log"
LOG_RETENTION_DAYS=7 # 保留 7 天
echo "清理未激活且 45 天未登录的用户..."
docker exec -i "${CONTAINER_NAME}" \
psql -U "${PG_USER}" -d "${DATABASE_NAME}" <<EOSQL
DELETE FROM "user"
WHERE role = 'pending'
AND last_active_at < EXTRACT(EPOCH FROM NOW()) - (45 * 24 * 60 * 60)
AND name NOT IN (${PROTECTED_USERS});
EOSQL
echo "清理 90 天前未归档的聊天记录..."
docker exec -i "${CONTAINER_NAME}" \
psql -U "${PG_USER}" -d "${DATABASE_NAME}" \
-c "DELETE FROM \"chat\"
WHERE updated_at <= EXTRACT(EPOCH FROM DATE_TRUNC('day', NOW() - INTERVAL '90 day'))::INTEGER
AND archived = false;"
echo "清理 60 天前的文件上传记录..."
docker exec -i "${CONTAINER_NAME}" \
psql -U "${PG_USER}" -d "${DATABASE_NAME}" \
-c "DELETE FROM \"file\"
WHERE updated_at < EXTRACT(EPOCH FROM NOW()) - (60 * 24 * 60 * 60);"
echo "清理 7 天前的 credit_log 记录..."
docker exec -i "${CONTAINER_NAME}" \
psql -U "${PG_USER}" -d "${DATABASE_NAME}" \
-c "DELETE FROM \"credit_log\"
WHERE created_at < EXTRACT(EPOCH FROM NOW()) - (7 * 24 * 60 * 60);"
echo "对受影响的表执行 VACUUM (ANALYZE)..."
docker exec -i "${CONTAINER_NAME}" \
psql -U "${PG_USER}" -d "${DATABASE_NAME}" \
-v ON_ERROR_STOP=1 -c \
"VACUUM (ANALYZE) \"user\"; VACUUM (ANALYZE) chat; VACUUM (ANALYZE) \"file\"; VACUUM (ANALYZE) credit_log;"
echo "数据库清理完成。"
echo "清理超过 ${LOG_RETENTION_DAYS} 天的 PostgreSQL CSV 日志(目录:${LOG_DIR})..."
if [ -d "${LOG_DIR}" ]; then
BEFORE_SIZE=$(du -sh "${LOG_DIR}" | awk '{print $1}')
# 只删我们配置生成的 CSV 日志
find "${LOG_DIR}" -type f -name "postgresql-*.csv" -mtime +${LOG_RETENTION_DAYS} -print -delete
AFTER_SIZE=$(du -sh "${LOG_DIR}" | awk '{print $1}')
echo "PG 日志:清理前 ${BEFORE_SIZE} -> 清理后 ${AFTER_SIZE}"
else
echo "警告:日志目录 ${LOG_DIR} 不存在,跳过 PG 日志清理。"
fi
echo "清理超过 7 天未修改的向量数据库目录..."
find /www/open-webui/vector_db -mindepth 1 -type d -mtime +7 -exec rm -r {} +
echo "所有清理任务已完成。"
最后更新于