From 5ce12771b96dd1dff966b40d2b095a713351168f Mon Sep 17 00:00:00 2001 From: stefanfeng Date: Wed, 9 Sep 2026 11:06:03 +0800 Subject: [PATCH] fix(avatar): persist SQLite WAL across releases --- digital-avatar-app/docker-compose.yml | 9 +++++---- digital-avatar-app/docs/H5_PRODUCTION_DEPLOYMENT.md | 12 ++++++------ digital-avatar-app/scripts/verify-deployment.py | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/digital-avatar-app/docker-compose.yml b/digital-avatar-app/docker-compose.yml index d6c3339..9a58e3f 100644 --- a/digital-avatar-app/docker-compose.yml +++ b/digital-avatar-app/docker-compose.yml @@ -12,14 +12,15 @@ services: env_file: - .env environment: - DATABASE_URL: sqlite:////app/avatar.db - UPLOAD_DIR: /app/uploads + DATABASE_URL: sqlite:////data/db/avatar.db + UPLOAD_DIR: /data/uploads CHAT_MODEL_CONFIG_URL: http://host.docker.internal:8000/api/ai-models/runtime/digital-avatar extra_hosts: - "host.docker.internal:host-gateway" volumes: - - ${AVATAR_DB_PATH:?AVATAR_DB_PATH must point to the persistent avatar.db}:/app/avatar.db - - ${AVATAR_UPLOAD_DIR:?AVATAR_UPLOAD_DIR must point to persistent uploads}:/app/uploads + # Mount the directory, not only avatar.db: SQLite WAL/SHM files must survive recreation. + - ${AVATAR_DB_DIR:?AVATAR_DB_DIR must contain the persistent avatar.db}:/data/db + - ${AVATAR_UPLOAD_DIR:?AVATAR_UPLOAD_DIR must point to persistent uploads}:/data/uploads expose: - "8000" ports: diff --git a/digital-avatar-app/docs/H5_PRODUCTION_DEPLOYMENT.md b/digital-avatar-app/docs/H5_PRODUCTION_DEPLOYMENT.md index cceb2b5..b6e0f57 100644 --- a/digital-avatar-app/docs/H5_PRODUCTION_DEPLOYMENT.md +++ b/digital-avatar-app/docs/H5_PRODUCTION_DEPLOYMENT.md @@ -61,10 +61,10 @@ WECHAT_VIRTUAL_PRODUCT_2=<100元套餐商品ID> WECHAT_VIRTUAL_PRODUCT_3=<1000元套餐商品ID> WECHAT_VIRTUAL_PRODUCT_4=<10000元套餐商品ID> -AVATAR_DB_PATH=/srv/digital-avatar/data/avatar.db +AVATAR_DB_DIR=/srv/digital-avatar/data/db AVATAR_UPLOAD_DIR=/srv/digital-avatar/data/uploads -DATABASE_URL=sqlite:////app/avatar.db -UPLOAD_DIR=/app/uploads +DATABASE_URL=sqlite:////data/db/avatar.db +UPLOAD_DIR=/data/uploads CHAT_MODEL_CONFIG_URL=http:///api/ai-models/runtime/digital-avatar EMBEDDING_API_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 EMBEDDING_API_KEY= @@ -86,7 +86,7 @@ CHAT_ATTACHMENT_CLEANUP_MINUTES=60 如生产 AI 配置中心不可用,还应提供当前项目支持的 `OPENAI_API_KEY`、`OPENAI_BASE_URL`、`CHAT_MODEL` 等兜底配置。数据库文件与上传目录必须从宿主机显式挂载,不能存放在容器临时层。 -`AVATAR_DB_PATH` 和 `AVATAR_UPLOAD_DIR` 必须是已备份的宿主机绝对路径,编排缺少任一变量都会直接拒绝构建或启动,防止误挂空卷造成用户、分身或知识库“丢失”的假象。 +`AVATAR_DB_DIR` 和 `AVATAR_UPLOAD_DIR` 必须是已备份的宿主机绝对路径,编排缺少任一变量都会直接拒绝构建或启动,防止误挂空卷造成用户、分身或知识库“丢失”的假象。SQLite 必须挂载整个数据库目录,不能只挂载 `avatar.db` 单文件,否则 `avatar.db-wal` 和 `avatar.db-shm` 会留在容器临时层,换容器后可能出现数据状态回退。 `EMBEDDING_API_URL` 同时支持 OpenAI 兼容基础地址(如上面的 `/v1`)和完整的 `/v1/embeddings` 地址,后端会统一请求 `/embeddings`。发布后必须在后端容器内执行一次最小向量探针,确认返回向量数量和维度,而不能只检查 `/api/health`。 @@ -103,7 +103,7 @@ App 与 H5 积分充值使用会会支付体系的 `payment-v3/payment/pay`, ```bash BACKUP_DIR="backups/$(date +%Y%m%d-%H%M%S)" mkdir -p "$BACKUP_DIR" -cp /srv/digital-avatar/data/avatar.db "$BACKUP_DIR/" +cp /srv/digital-avatar/data/db/avatar.db "$BACKUP_DIR/" tar -C /srv/digital-avatar/data -czf "$BACKUP_DIR/uploads.tgz" uploads ``` @@ -122,7 +122,7 @@ python3 scripts/verify-deployment.py \ https://digital.99hui.com "$APP_GIT_SHA" \ --backend-container avatar-backend \ --frontend-container avatar-frontend \ - --expected-db-source /srv/digital-avatar/data/avatar.db \ + --expected-db-source /srv/digital-avatar/data/db \ --expected-upload-source /srv/digital-avatar/data/uploads docker compose exec avatar-backend python -c 'import embeddings; v=embeddings.embed(["部署向量探针"]); print(len(v), len(v[0]))' ``` diff --git a/digital-avatar-app/scripts/verify-deployment.py b/digital-avatar-app/scripts/verify-deployment.py index 7a629b6..52867e4 100644 --- a/digital-avatar-app/scripts/verify-deployment.py +++ b/digital-avatar-app/scripts/verify-deployment.py @@ -22,7 +22,7 @@ def main(): parser.add_argument("expected_sha", help="Full Git commit SHA being deployed") parser.add_argument("--backend-container", help="Backend container name for image and mount checks") parser.add_argument("--frontend-container", help="Frontend container name for image checks") - parser.add_argument("--expected-db-source", help="Required host source mounted as the SQLite database") + parser.add_argument("--expected-db-source", help="Required host directory mounted for SQLite and its WAL files") parser.add_argument("--expected-upload-source", help="Required host source mounted as the upload directory") args = parser.parse_args()