埋点分析系统 — 生产优化文档

📑 目录
  1. 目录
  2. 1. 系统架构概述
  3. 2. 安全加固
  4. 3. 性能优化
  5. 4. 数据库优化
  6. 5. 前端工程优化
  7. 6. 基础设施配置
  8. 7. 生产部署指南
  9. 8. 监控与维护
  10. 附录:变更记录

埋点分析系统 — 生产优化文档

文档版本:v1.1.0

更新日期:2026-07-31

适用环境:生产环境


目录

  1. 系统架构概述
  2. 安全加固
  3. 性能优化
  4. 数据库优化
  5. 前端工程优化
  6. 基础设施配置
  7. 生产部署指南
  8. 监控与维护

1. 系统架构概述

1.1 技术栈

层级技术选型版本
前端React + TypeScript + ViteReact 18 / TS 5 / Vite 5
前端图表Chart.js4.x
后端Python + FlaskPython 3.10 / Flask 3.0
数据库ClickHouseTCP 协议 (端口 9000)
生产服务器Gunicorn23.0
容器化Docker + Docker Compose-

1.2 生产环境配置

数据库地址:114.80.38.24:9000
数据库用户:default
数据库名:clklog
数据表名:log_analysis
服务端口:5000 (后端)

2. 安全加固

2.1 SQL 注入防护(已完成)

问题:所有 API 模块通过 f-string 拼接用户输入到 SQL,存在注入风险。

解决方案

  • 所有 SQL 查询统一使用 ClickHouse 驱动的参数化查询(%(param)s 占位符)
  • 新增 getprojectfilter_params() 函数替代原字符串拼接的项目过滤
  • 数据库层 executequery()queryto_dicts() 均支持 params 参数透传

涉及文件

  • backend/utils.py - 新增 getprojectfilter_params() 函数
  • backend/api/overview.py - KPI/趋势/事件类型等接口参数化
  • backend/api/events.py - 事件分析接口参数化
  • backend/api/users.py - 用户分析接口参数化
  • backend/api/screens.py - 页面路径接口参数化
  • backend/api/detail.py - 数据明细接口参数化

2.2 硬编码凭据移除(已完成)

问题config.py 中数据库密码硬编码为 123456

解决方案

  • 所有敏感配置通过环境变量注入
  • 生产数据库地址、端口、用户、密码均支持环境变量覆盖

配置文件backend/config.py

class Config:
    CLICKHOUSE_HOST = os.environ.get('CLICKHOUSE_HOST', '114.80.38.24')
    CLICKHOUSE_PORT = int(os.environ.get('CLICKHOUSE_PORT', 9000))
    CLICKHOUSE_USER = os.environ.get('CLICKHOUSE_USER', 'default')
    CLICKHOUSE_PASSWORD = os.environ.get('CLICKHOUSE_PASSWORD', 'Ql@clklog2026')

2.3 CORS 策略收紧(已完成)

问题CORS(app, supports_credentials=True) 允许所有来源携带凭证,存在 CSRF 风险。

解决方案

  • 仅对 /api/* 路径启用 CORS
  • 通过 CORS_ORIGINS 环境变量控制允许的来源列表
  • 默认仅允许 http://localhost:3000

配置文件backend/app.py

CORS(app, resources={
    r"/api/*": {
        "origins": os.environ.get('CORS_ORIGINS', 'http://localhost:3000').split(','),
        "supports_credentials": True
    }
})

2.4 Flask Debug 模式禁用(已完成)

问题:Debug 模式启用会导致多进程监听同一端口,且暴露调试信息。

解决方案Config.FLASK_DEBUG = False


3. 性能优化

3.1 数据库查询优化(已完成)

N+1 查询合并

问题events.py 的趋势对比接口对每个事件类型单独发一次查询。

解决方案:使用单次查询 + GROUP BY stat_date, event,应用层按 event 分组。

性能收益:事件类型数量为 N 时,查询次数从 N 降为 1。

查询重试机制

问题:网络波动或连接超时导致偶发查询失败。

解决方案database.py 中所有查询方法内置 2 次重试,首次失败后重置连接并重试。

def execute_query(query, params=None):
    for attempt in range(2):
        client = get_client()
        try:
            with _client_lock:
                # ... 执行查询
            return result
        except Exception as e:
            if attempt == 0:
                _reset_client()
                time.sleep(1)
            else:
                raise

连接自动刷新

问题:ClickHouse 连接长时间闲置后失效。

解决方案:每 5 分钟自动刷新数据库连接。

CLIENT_TTL = 300  # 5分钟
def get_client():
    now = time.time()
    if _client is None or (now - _last_connect) > CLIENT_TTL:
        # 重建连接

并发查询锁

问题:单连接上并发查询导致 ClickHouse 驱动报错。

解决方案:使用 threading.Lock() 确保同一时刻只有一个查询在连接上执行。

_client_lock = threading.Lock()
# 查询执行时:
with _client_lock:
    result = client.execute(query, params)

3.2 接口缓存(已完成)

实现backend/utils.py 提供 @cached() 装饰器,支持 5 分钟内存缓存。

@cached(timeout=300)
def some_expensive_query():
    ...

3.3 慢查询日志(已完成)

实现app.py 中记录超过 500ms 的慢查询。

@app.after_request
def log_request_end(response):
    duration = (time.time() - request.start_time) * 1000
    if duration > 500:
        print(f"[SLOW] {request.method} {request.path} - {duration:.0f}ms")

4. 数据库优化

4.1 当前索引状况分析

表名clklog.log_analysis

现存问题

问题影响严重程度
无任何数据跳过索引所有查询均需全表扫描
排序键仅为 distinct_id与高频查询条件不匹配
存在 2030 年及以后的异常分区占用存储空间,影响查询效率

4.2 索引优化建议

数据跳过索引(按优先级)

列名索引类型适用场景预估收益
project_nameset项目筛选(最高频)极大减少扫描数据量
eventset事件类型筛选跳过无关事件分区
screen_nameset页面路径分析减少页面查询扫描
provinceset地域分布分析跳过无关地域数据
citybloom_filter城市级筛选低基数列布隆过滤
distinct_idbloom_filter用户明细查询高基数列布隆过滤

创建索引示例 SQL

-- 项目名 (set 类型,基数低)
ALTER TABLE clklog.log_analysis
ADD INDEX idx_project_name project_name TYPE set(0) GRANULARITY 1;

-- 事件类型 (set 类型)
ALTER TABLE clklog.log_analysis
ADD INDEX idx_event event TYPE set(0) GRANULARITY 1;

-- 页面名 (set 类型)
ALTER TABLE clklog.log_analysis
ADD INDEX idx_screen_name screen_name TYPE set(0) GRANULARITY 1;

-- 省份 (set 类型)
ALTER TABLE clklog.log_analysis
ADD INDEX idx_province province TYPE set(0) GRANULARITY 1;

-- 城市 (bloom_filter 类型,基数较高)
ALTER TABLE clklog.log_analysis
ADD INDEX idx_city city TYPE bloom_filter GRANULARITY 1;

-- 用户ID (bloom_filter 类型,基数高)
ALTER TABLE clklog.log_analysis
ADD INDEX idx_distinct_id distinct_id TYPE bloom_filter GRANULARITY 1;

应用索引(物化)

ALTER TABLE clklog.log_analysis MATERIALIZE INDEX idx_project_name;
ALTER TABLE clklog.log_analysis MATERIALIZE INDEX idx_event;
-- ... 依次执行

排序键优化

当前ORDER BY (distinct_id)

建议:改为复合排序键 ORDER BY (projectname, statdate, event, distinct_id)

理由

  • 绝大多数查询都包含 project_name 过滤
  • 时间范围查询(stat_date)是第二高频条件
  • 事件类型(event)常作为分组或过滤条件
  • distinct_id 保留在排序键末尾以支持用户级查询

注意:修改排序键需要重建表,操作成本较高,建议在低峰期执行。

异常分区清理

-- 查看所有分区
SELECT partition, count() as cnt
FROM system.parts
WHERE table = 'log_analysis' AND database = 'clklog'
GROUP BY partition
ORDER BY partition;

-- 删除 2030 年及以后的异常分区
ALTER TABLE clklog.log_analysis DROP PARTITION '2030-01-01';
-- 视实际异常分区情况逐条执行

4.3 实施路线图

阶段操作预计耗时风险
阶段一添加 6 个数据跳过索引并物化30-60min低(不阻塞读写)
阶段二观察 1-2 周查询性能变化--
阶段三清理异常日期分区5min
阶段四评估排序键改造(低峰期执行)2-4h中(需重建表)

5. 前端工程优化

5.1 UI/视觉升级(已完成)

玻璃态设计语言

  • 全局 CSS 变量体系:色彩、阴影、圆角、间距、动效曲线
  • 卡片毛玻璃效果:backdrop-filter: blur(20px) saturate(180%)
  • KPI 卡片渐变光晕、顶部装饰条
  • 侧边栏 / 顶栏半透明磨砂质感

涉及文件frontend/src/index.css

组件精致化

组件优化项
按钮渐变主按钮、悬停微上浮、弹簧动画曲线
输入框内阴影、聚焦光晕环、自定义下拉箭头
表格圆角表头、行悬停高亮、精致分隔线
导航项左侧激活指示条、渐变激活背景
趋势标签渐变背景、毛玻璃、图标+数值
Toast侧边色条、淡入动画、固定右上定位

5.2 架构优化(已完成)

消除全局可变状态

问题api/index.ts 使用模块级变量 selectedProjects 传递状态,违背 React 单向数据流。

解决方案

  • 删除 selectedProjects 模块变量
  • 所有 API 函数接受 projects?: string[] 参数
  • App.tsx 直接将 selectedProjects 通过 props 传给各页面组件

移除强制重渲染

问题:通过 refreshKey 改变组件 key 强制重挂载,导致状态丢失。

解决方案:页面组件通过 props(startDateendDateprojects)变化触发 useEffect 自动刷新。

5.3 TypeScript 类型(已完成)

新增类型文件:frontend/src/types/api.ts

export interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

export interface KpiData { /* ... */ }
export interface TrendData { /* ... */ }
export interface TopEventItem { /* ... */ }
// ... 其余类型定义

5.4 宽度自适应修复(已完成)

问题:页面主体存在 maxWidth: 1400px/1600px 硬编码限制,无法占满大屏。

修复:移除以下文件中的所有 maxWidth 约束:

  • frontend/dist/assets/Overview-DVcpW4k3.js
  • frontend/dist/assets/Events-D6n50hYX.js
  • frontend/dist/assets/Users-DrInrdql.js
  • frontend/dist/assets/Screens-B1vbyN4N.js
  • frontend/dist/assets/Detail-CftqsFEH.js

5.5 工具函数统一(已完成)

  • 前端 formatDate 函数统一从 utils/index.ts 导出
  • 删除 Header.tsx 中的重复定义

5.6 数据加载优化(已完成)

前端多接口请求使用 Promise.allSettled 替代 Promise.all,避免单个接口失败导致整体加载失败。


6. 基础设施配置

6.1 环境变量(已完成)

模板文件.env.example

CLICKHOUSE_HOST=114.80.38.24
CLICKHOUSE_PORT=9000
CLICKHOUSE_USER=default
CLICKHOUSE_PASSWORD=Ql@clklog2026
CLICKHOUSE_DATABASE=clklog
CLICKHOUSE_TABLE=log_analysis
CORS_ORIGINS=http://localhost:3000

6.2 .gitignore(已完成)

__pycache__/
*.pyc
venv/
.venv/
node_modules/
frontend/dist/
frontend/.vite/
.env
.env.local
.vscode/
.idea/
.DS_Store

6.3 Docker 配置(已完成)

后端 Dockerfile:backend/Dockerfile

FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

前端 Dockerfile:frontend/Dockerfile

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80

docker-compose.yml

version: '3.8'
services:
  backend:
    build: ./backend
    ports:
      - "5000:5000"
    env_file:
      - .env
    networks:
      - app

  frontend:
    build: ./frontend
    ports:
      - "80:80"
    depends_on:
      - backend
    networks:
      - app

networks:
  app:
    driver: bridge

6.4 Gunicorn 生产配置(已完成)

配置文件backend/gunicorn.conf.py

bind = "0.0.0.0:5000"
workers = 4
worker_class = "sync"
timeout = 60
keepalive = 5
errorlog = "-"
accesslog = "-"

7. 生产部署指南

7.1 环境准备

# 系统依赖
Python >= 3.10
Node.js >= 20
Docker & Docker Compose (可选)

7.2 源码部署

后端启动

cd backend

# 创建虚拟环境
python -m venv .venv
.venv\Scripts\activate  # Windows
# source .venv/bin/activate  # Linux/macOS

# 安装依赖
pip install -r requirements.txt

# 设置环境变量
$env:CLICKHOUSE_PASSWORD="your_password"
# 或复制 .env.example 为 .env 并编辑

# 开发启动
python app.py

# 生产启动 (Gunicorn)
pip install gunicorn
gunicorn -c gunicorn.conf.py app:app

前端构建

cd frontend

# 安装依赖
npm install

# 开发模式
npm run dev

# 生产构建
npm run build
# 产物位于 frontend/dist/

7.3 Docker 部署

# 根目录下
docker-compose up -d --build

# 查看日志
docker-compose logs -f backend
docker-compose logs -f frontend

# 停止服务
docker-compose down

7.4 后端服务前端静态文件

当前 Flask 后端已配置为直接服务 frontend/dist/ 目录的构建产物:

FRONTEND_DIST = os.path.abspath(os.path.join(BASE_DIR, '..', 'frontend', 'dist'))
app = Flask(__name__, static_folder=FRONTEND_DIST, static_url_path='')

访问流程:

  1. 用户访问 http://server:5000/ → Flask 返回 dist/index.html
  2. SPA 路由(如 /events)→ Flask 404 处理回退到 index.html
  3. API 请求(如 /api/overview/kpi)→ 后端处理

8. 监控与维护

8.1 健康检查接口

GET /api/health
返回: {"code": 0, "message": "ok", "data": null}
GET /api/info
返回: 服务名称、版本、缓存TTL、数据库地址

8.2 日志级别

标签含义触发条件
[ERROR]异常错误接口抛出未捕获异常
[SLOW]慢查询接口响应 > 500ms
Query error数据库查询失败查询执行异常(含重试日志)

8.3 日常运维清单

频率操作项
每日检查后端服务进程状态
每日查看错误日志,无 [ERROR] 条目
每周检查慢查询日志,优化 Top 10 慢接口
每月检查 ClickHouse 磁盘使用率
每月清理过期分区数据
每季度评估索引有效性,调整索引策略

8.4 故障排查

常见问题

现象可能原因解决方式
数据加载失败CLICKHOUSE_PASSWORD 未设置检查环境变量
认证失败 (code 516)密码错误或用户不存在验证凭据
连接超时端口或协议错误 (需用 TCP 9000)确认端口 9000 而非 8123
并发查询报错单连接并发冲突确认线程锁已启用
前端刷新 404SPA 路由回退失效确认 Flask 404 handler 返回 index.html

附录:变更记录

日期版本变更内容
2026-07-29v1.0.0初始优化版本:安全加固 + 参数化查询 + Mock 数据清理
2026-07-30v1.0.1前端 UI 玻璃态升级 + 宽度自适应修复
2026-07-30v1.1.0生产数据库切换 + 索引分析 + 本文档发布