Files
planet/docs/technical/zh/ops-planet-sh-startup.md
2026-04-29 23:43:54 +08:00

6.3 KiB
Raw Blame History

planet.sh 启动性能优化

背景

planet.sh 管理所有服务的启动/停止/重启。原有实现存在以下问题:

  1. AI Provider 每次都重新构建(即使代码未变)
  2. 杀端口速度极慢(最长等 45 秒)
  3. 端口绑定检测用 Python 子进程(每次 ~300ms
  4. 无参 restartrestart -b 行为不一致

问题一AI Provider 每次重建

根因

构建戳文件存放在 /tmp/WSL/Linux 重启后 /tmp 被清空,导致三个条件中的"戳文件非空"这一条始终不满足,进而判定需要重建:

# 三个条件必须同时成立才跳过重建
image_exists AND stamp_non_empty AND fingerprint_match

修复

将戳文件路径从 /tmp/ 改到持久路径:

AI_PROVIDER_BUILD_STAMP_FILE="$HOME/.cache/planet/aiprovider_build.sha256"

写入时确保目录存在:

write_ai_provider_build_stamp() {
    mkdir -p "$(dirname "$AI_PROVIDER_BUILD_STAMP_FILE")"
    compute_ai_provider_build_fingerprint > "$AI_PROVIDER_BUILD_STAMP_FILE"
}

fingerprint 计算提速

原实现对整个 aiprovider/ 打 tar 包再算 SHA大目录下耗时可达数秒。改为 find + stat(只读文件元信息,不读内容):

compute_ai_provider_build_fingerprint() {
    find aiprovider \
        -type f \
        ! -path '*/__pycache__/*' \
        ! -name '.env' \
        ! -name '.env.*' \
        ! -name '*.pyc' \
        ! -name '*.pyo' \
        | LC_ALL=C sort \
        | xargs -r stat --format="%Y %s %n" 2>/dev/null
    sha256sum docker-compose.yml docker-compose.simple.yml 2>/dev/null
    python3 "$SCRIPT_DIR/scripts/compute_aiprovider_dependency_fingerprint.py" 2>/dev/null
}

速度提升约 10 倍大量小文件场景误报率相同mtime+size 变化 ≡ 文件被修改)。

.env.env.* 被排除在 fingerprint 外。它们属于运行期配置,不应该因为修改模型、密钥或 Base URL 触发镜像重建。

Docker build context 收敛

AI Provider 镜像只需要根目录的 pyproject.tomluv.lockaiprovider/ 代码。仓库中还包含前端静态大图、PDF、历史数据和 Unreal 资料,如果 build context 使用整个仓库,transferring context 会浪费大量时间。

当前通过根目录 .dockerignore 收敛上下文:

**

!pyproject.toml
!uv.lock
!aiprovider/
!aiprovider/**

aiprovider/.env
aiprovider/.env.*
!aiprovider/.env.example

Dockerfile 也从全仓复制改为只复制 AI Provider 代码:

COPY pyproject.toml uv.lock /app/
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev

COPY aiprovider /app/aiprovider

uv sync 使用 BuildKit cache mount 后,首次构建仍可能受网络影响;后续构建会复用 /root/.cache/uv,依赖下载不再重复从零开始。

运行期配置来源

planet.sh 启动 AI Provider 前会生成临时 env-file并把它传给 Compose 或手动 docker run fallback。配置优先来自

  1. aiprovider/.env
  2. ~/.zshrc 中简单的 export AI_...=...AI_...=...

默认解析是静态的,只覆盖 AI Provider、镜像、代理相关变量避免执行交互 shell 初始化。如果确实需要复杂 shell 展开,可以显式启用:

PLANET_LOAD_ZSHRC_ENV=source ./planet.sh start -a

如果排查时需要忽略个人 shell 配置:

PLANET_LOAD_ZSHRC_ENV=0 ./planet.sh start -a

跳过重建的原理

fingerprint 一致时不执行 docker compose build,而是:

docker start planet_aiprovider   # 启动已存在的容器,几秒内完成

docker stop 停容器,不删镜像;cleanup_exit_containers 删已退出容器,不删镜像。下次 docker start 会从现有镜像直接创建并启动容器。

问题二:杀端口速度慢

原因

wait_for_port_release 默认最多等 45 秒15 次 × 3 秒)。

修复

将后台进程清理场景的超时缩短至 3 秒TERM→1.5s→KILL→1.5s

PORT_RELEASE_ATTEMPTS=15
PORT_RELEASE_INTERVAL=0.2   # 每次等 0.2s,总计 3s

# cleanup_backend_processes / kill_port_if_requested
wait_for_port_release "$port" 15 0.2

wait_for_port_release 增加可选参数,允许不同场景使用不同超时:

wait_for_port_release() {
    local port="$1"
    local max_attempts="${2:-$PORT_RELEASE_ATTEMPTS}"
    local interval="${3:-$PORT_RELEASE_INTERVAL}"
    ...
}

问题三:端口检测用 Python

原因

can_bind_portpython3 -c "import socket..." 检测端口,每次调用约 300ms。

修复

优先使用系统工具(~10msPython 作为兜底:

can_bind_port() {
    local port="$1"
    if command -v ss >/dev/null 2>&1; then
        ! ss -tlnH 2>/dev/null | awk '{print $4}' | grep -qE ":${port}$"
        return
    fi
    if command -v lsof >/dev/null 2>&1; then
        [ -z "$(lsof -tiTCP:"${port}" -sTCP:LISTEN 2>/dev/null)" ]
        return
    fi
    python3 - "$port" <<'PY'
import sys, socket
p = int(sys.argv[1])
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(("", p)); s.close(); sys.exit(0)
except OSError:
    sys.exit(1)
PY
}

问题四restart 行为不一致

现象

  • restart -b:停全部服务 → 检查 AI Provider fingerprint → 按需重建 → 启动
  • restart(无参):停全部服务 → AI Provider 总是判定需要重建(因戳文件在 /tmp

修复

修复戳文件路径后,无参 restart 同样使用 stop + startfingerprint 检查正常生效,行为与 restart -b 完全一致。无需额外代码变更。

其他:移除不必要的 sleep

启动链路中两处 sleep 3 在实际已有健康检查覆盖的情况下多余,已移除:

  • start_backend_service:数据库健康检查通过后的 sleep 3
  • restart_database_service:重启后的等待 sleep 3

相关文件

  • planet.sh — 全量修改
  • .dockerignore — 收敛 AI Provider Docker build context
  • aiprovider/Dockerfile — 只复制 AI Provider 代码,并为 uv sync 启用 BuildKit cache mount
  • docker-compose.yml / docker-compose.simple.yml — 读取 planet.sh 生成的运行期 env-file
  • scripts/compute_aiprovider_dependency_fingerprint.py — 依赖 fingerprint未改动