# 真实数据量性能与压力测试方案

## 概述

本文档定义在大规模数据场景下的系统性能与可用性测试方案，模拟 10 万级用户、百万级登录日志、权限树爆炸等极端情况，验证系统在各数据量级别下的功能可用性、性能表现和运维能力。

---

## 1. 测试目标与范围

### 1.1 测试目标

| 目标 | 指标 | 说明 |
|-----|------|-----|
| 用户规模支撑 | 100,000 用户 | 系统在 10 万用户规模下功能正常 |
| 登录日志规模 | 1,000,000 条 | 百万级日志下的查询、导出性能 |
| 权限树规模 | 500+ 权限节点 | 权限树爆炸场景下的加载性能 |
| 筛选搜索可用性 | 响应时间 < 2s | 大数据量下筛选搜索不超时 |
| 批量操作稳定性 | 1000 条/批 | 批量操作不出现 OOM 或超时 |

### 1.2 测试范围

- 用户列表查询与筛选
- 登录日志查询与导出
- 设备列表查询与管理
- 权限树加载与渲染
- 仪表盘统计加载
- 批量导入/导出操作

---

## 2. 测试数据准备

### 2.1 数据规模规划

| 数据类型 | 小规模 | 中规模 | 大规模 | 极端规模 |
|---------|-------|-------|-------|---------|
| 用户数 | 1,000 | 10,000 | 50,000 | 100,000 |
| 登录日志 | 10,000 | 100,000 | 500,000 | 1,000,000 |
| 设备数 | 2,000 | 20,000 | 100,000 | 200,000 |
| 角色数 | 10 | 50 | 100 | 200 |
| 权限数 | 50 | 200 | 500 | 1,000 |
| 用户角色关联 | 1,500 | 15,000 | 75,000 | 150,000 |
| 登录日志保留天数 | 7 天 | 30 天 | 90 天 | 180 天 |

### 2.2 测试数据生成脚本

#### 2.2.1 用户数据生成

```python
# scripts/generate_test_users.py
import random
import string
from datetime import datetime, timedelta

def generate_users(count: int, start_id: int = 1):
    """生成测试用户数据"""
    statuses = [0, 1, 1, 1, 1, 2, 3]  # 权重: 14%未激活, 57%活跃, 14%锁定, 14%禁用
    users = []

    for i in range(count):
        user_id = start_id + i
        created_at = datetime.now() - timedelta(
            days=random.randint(0, 365),
            hours=random.randint(0, 23)
        )
        users.append({
            'id': user_id,
            'username': f'testuser_{user_id}',
            'email': f'testuser_{user_id}@test.com',
            'phone': f'138{random.randint(10000000, 99999999)}',
            'password': '$2a$10$dummy_hash_for_test_data',
            'status': random.choice(statuses),
            'created_at': created_at.isoformat(),
            'updated_at': created_at.isoformat(),
        })

    return users

# 生成 10 万用户
users = generate_users(100000)
```

#### 2.2.2 登录日志数据生成

```sql
-- 使用 PostgreSQL 生成大规模登录日志
-- 登录日志生成函数
CREATE OR REPLACE FUNCTION generate_login_logs(
    user_count INT,
    logs_per_user INT
) RETURNS VOID AS $$
DECLARE
    i INT;
    j INT;
    user_ids INT[];
    statuses INT[];
    login_types INT[];
BEGIN
    -- 准备用户 ID 数组
    SELECT array_agg(id) INTO user_ids FROM users LIMIT user_count;

    statuses := ARRAY[0, 1, 1, 1, 1, 1, 1, 1, 1, 1];  -- 90% 成功
    login_types := ARRAY[1, 2, 3, 4];

    FOR i IN 1..logs_per_user LOOP
        FOR j IN 1..user_count LOOP
            INSERT INTO login_logs (
                user_id,
                login_type,
                device_id,
                ip,
                location,
                status,
                fail_reason,
                created_at
            ) VALUES (
                user_ids[j],
                login_types[1 + floor(random() * 4)::int],
                'device_' || user_ids[j] || '_' || i,
                '192.168.' || (1 + floor(random() * 255))::int || '.' || (1 + floor(random() * 255))::int,
                CASE floor(random() * 5)::int
                    WHEN 0 THEN '北京'
                    WHEN 1 THEN '上海'
                    WHEN 2 THEN '深圳'
                    WHEN 3 THEN '杭州'
                    ELSE '广州'
                END,
                statuses[1 + floor(random() * 10)::int],
                CASE
                    WHEN statuses[1 + floor(random() * 10)::int] = 0
                    THEN CASE floor(random() * 3)::int
                        WHEN 0 THEN '密码错误'
                        WHEN 1 THEN '账号已锁定'
                        ELSE '账号已禁用'
                    END
                    ELSE NULL
                END,
                NOW() - (random() * 365 || ' days')::interval
            );
        END LOOP;

        -- 每 10000 条输出进度
        IF i % 100 = 0 THEN
            RAISE NOTICE 'Generated % login cycles for % users', i, user_count;
        END IF;
    END LOOP;
END;
$$ LANGUAGE plpgsql;

-- 执行生成 100 万条日志（100 用户 x 10000 次登录）
-- SELECT generate_login_logs(100, 10000);
```

#### 2.2.3 权限树数据生成

```sql
-- 权限树生成脚本
-- 生成 500 个权限节点

INSERT INTO permissions (name, code, parent_id, sort_order, created_at) VALUES
-- 系统管理（父节点）
('系统管理', 'system', NULL, 1, NOW()),
  ('系统配置', 'system.config', 1, 1, NOW()),
  ('系统日志', 'system.logs', 1, 2, NOW()),
  ('用户管理', 'system.users', 1, 3, NOW()),
    ('用户列表', 'system.users.list', 4, 1, NOW()),
    ('用户创建', 'system.users.create', 4, 2, NOW()),
    ('用户编辑', 'system.users.edit', 4, 3, NOW()),
    ('用户删除', 'system.users.delete', 4, 4, NOW()),
    ('用户导出', 'system.users.export', 4, 5, NOW()),
  ('角色管理', 'system.roles', 1, 4, NOW()),
    ('角色列表', 'system.roles.list', 10, 1, NOW()),
    ('角色创建', 'system.roles.create', 10, 2, NOW()),
    ('角色编辑', 'system.roles.edit', 10, 3, NOW()),
    ('角色删除', 'system.roles.delete', 10, 4, NOW()),
    ('分配权限', 'system.roles.assign', 10, 5, NOW()),
  ('权限管理', 'system.permissions', 1, 5, NOW()),
  ('设备管理', 'system.devices', 1, 6, NOW()),
    ('设备列表', 'system.devices.list', 16, 1, NOW()),
    ('设备详情', 'system.devices.view', 16, 2, NOW()),
    ('设备删除', 'system.devices.delete', 16, 3, NOW()),
    ('设备信任', 'system.devices.trust', 16, 4, NOW()),
  ('登录日志', 'system.login_logs', 1, 7, NOW()),
    ('登录日志列表', 'system.login_logs.list', 21, 1, NOW()),
    ('登录日志导出', 'system.login_logs.export', 21, 2, NOW()),
  ('操作日志', 'system.operation_logs', 1, 8, NOW()),
    ('操作日志列表', 'system.operation_logs.list', 23, 1, NOW()),
    ('操作日志详情', 'system.operation_logs.view', 23, 2, NOW()),
    ('操作日志导出', 'system.operation_logs.export', 23, 3, NOW()),
-- 业务管理
('业务管理', 'business', NULL, 2, NOW()),
  ('业务数据', 'business.data', 26, 1, NOW()),
    ('数据查看', 'business.data.view', 27, 1, NOW()),
    ('数据编辑', 'business.data.edit', 27, 2, NOW()),
    ('数据删除', 'business.data.delete', 27, 3, NOW()),
    ('数据导入', 'business.data.import', 27, 4, NOW()),
    ('数据导出', 'business.data.export', 27, 5, NOW()),
  ('报表管理', 'business.reports', 26, 2, NOW()),
    ('报表查看', 'business.reports.view', 30, 1, NOW()),
    ('报表生成', 'business.reports.generate', 30, 2, NOW()),
    ('报表导出', 'business.reports.export', 30, 3, NOW()),
-- 扩展权限（模拟权限树爆炸）
('扩展功能A', 'ext_a', NULL, 3, NOW()),
('扩展功能B', 'ext_b', NULL, 4, NOW()),
('扩展功能C', 'ext_c', NULL, 5, NOW());
```

---

## 3. 测试用例

### 3.1 用户列表大规模测试

#### UL-001: 10 万用户分页查询性能

| 属性 | 内容 |
|-----|------|
| 用例编号 | UL-001 |
| 用例名称 | 10 万用户分页查询性能 |
| 测试步骤 | 1. 准备 100,000 用户数据<br>2. 调用 GET /admin/users?page=1&page_size=20<br>3. 记录响应时间<br>4. 翻页到第 5000 页 |
| 预期结果 | 1. 首次加载 < 1s<br>2. 任意页加载 < 2s<br>3. 返回数据完整 |
| 性能指标 | p95 < 2000ms |
| 数据库索引 | user_id (PK), status, created_at |

#### UL-002: 大数据量关键词搜索

| 属性 | 内容 |
|-----|------|
| 用例编号 | UL-002 |
| 用例名称 | 10 万用户关键词搜索响应时间 |
| 测试步骤 | 1. 准备 100,000 用户数据<br>2. 执行关键词搜索 "testuser_50000"<br>3. 测量响应时间 |
| 预期结果 | 1. 搜索响应 < 2s<br>2. 返回结果正确 |
| 性能指标 | p95 < 2000ms |

#### UL-003: 多条件组合筛选

| 属性 | 内容 |
|-----|------|
| 用例编号 | UL-003 |
| 用例名称 | 多条件组合筛选性能 |
| 测试步骤 | 1. 筛选 status=1, role_ids=2, created_from=2024-01-01<br>2. 测量响应时间 |
| 预期结果 | 1. 响应 < 2s<br>2. 结果正确 |
| 性能指标 | p95 < 2000ms |

#### UL-004: 用户导出 10 万级数据

| 属性 | 内容 |
|-----|------|
| 用例编号 | UL-004 |
| 用例名称 | 10 万用户 CSV 导出 |
| 测试步骤 | 1. 导出全部 100,000 用户<br>2. 测量导出时间<br>3. 验证导出文件完整性 |
| 预期结果 | 1. 导出完成 < 60s<br>2. 文件可正常打开<br>3. 数据量 = 100,000 条 |
| 性能指标 | 内存占用 < 512MB, 时间 < 60s |

---

### 3.2 登录日志大规模测试

#### LL-001: 百万登录日志分页查询

| 属性 | 内容 |
|-----|------|
| 用例编号 | LL-001 |
| 用例名称 | 100 万登录日志分页查询 |
| 测试步骤 | 1. 准备 1,000,000 登录日志<br>2. 调用 GET /admin/logs/login?page=1&page_size=50<br>3. 翻到第 10000 页 |
| 预期结果 | 1. 首页加载 < 1s<br>2. 任意页加载 < 2s |
| 性能指标 | p95 < 2000ms |

#### LL-002: 百万日志时间范围查询

| 属性 | 内容 |
|-----|------|
| 用例编号 | LL-002 |
| 用例名称 | 百万日志按时间范围筛选 |
| 测试步骤 | 1. 查询最近 30 天日志<br>2. 测量响应时间 |
| 预期结果 | 1. 响应 < 3s<br>2. 返回结果数量合理 |
| 性能指标 | p95 < 3000ms |

#### LL-003: 百万日志导出 CSV

| 属性 | 内容 |
|-----|------|
| 用例编号 | LL-003 |
| 用例名称 | 100 万登录日志 CSV 导出 |
| 测试步骤 | 1. 导出 1,000,000 条登录日志<br>2. 测量导出时间<br>3. 验证文件完整性 |
| 预期结果 | 1. 导出时间 < 120s<br>2. CSV 文件可正常打开<br>3. 数据量正确 |
| 性能指标 | 流式导出，内存占用 < 256MB |

#### LL-004: 百万日志导出 XLSX

| 属性 | 内容 |
|-----|------|
| 用例编号 | LL-004 |
| 用例名称 | 100 万登录日志 XLSX 导出 |
| 测试步骤 | 1. 导出 1,000,000 条登录日志为 xlsx<br>2. 测量导出时间 |
| 预期结果 | 1. 导出完成<br>2. 文件可正常打开 |
| 性能指标 | 内存占用 < 1GB（XLSX 单文件限制） |

---

### 3.3 设备列表大规模测试

#### DV-001: 20 万设备分页查询

| 属性 | 内容 |
|-----|------|
| 用例编号 | DV-001 |
| 用例名称 | 20 万设备分页查询性能 |
| 测试步骤 | 1. 准备 200,000 设备数据<br>2. 调用 GET /admin/devices?page=1&page_size=20 |
| 预期结果 | 1. 响应 < 2s<br>2. 数据完整 |
| 性能指标 | p95 < 2000ms |

#### DV-002: 设备列表多条件筛选

| 属性 | 内容 |
|-----|------|
| 用例编号 | DV-002 |
| 用例名称 | 设备列表多条件筛选 |
| 测试步骤 | 1. 设置筛选：status=1, is_trusted=true, user_id=100<br>2. 测量响应时间 |
| 预期结果 | 1. 响应 < 2s<br>2. 结果正确 |
| 性能指标 | p95 < 2000ms |

---

### 3.4 权限树大规模测试

#### PR-001: 500 权限节点加载

| 属性 | 内容 |
|-----|------|
| 用例编号 | PR-001 |
| 用例名称 | 500 权限节点树加载性能 |
| 测试步骤 | 1. 准备 500 个权限节点<br>2. 调用权限树接口<br>3. 测量前端渲染时间 |
| 预期结果 | 1. 接口响应 < 500ms<br>2. 前端渲染 < 1s |
| 性能指标 | 接口 p95 < 500ms, 前端渲染 < 1000ms |

#### PR-002: 1000 权限节点树爆炸测试

| 属性 | 内容 |
|-----|------|
| 用例编号 | PR-002 |
| 用例名称 | 1000 权限节点树爆炸场景 |
| 测试步骤 | 1. 准备 1000 个权限节点（5 层深度）<br>2. 前端加载权限树<br>3. 测量性能 |
| 预期结果 | 1. 加载不超时<br>2. UI 不卡顿 |
| 性能指标 | 内存占用 < 100MB |

#### PR-003: 角色权限分配大规模场景

| 属性 | 内容 |
|-----|------|
| 用例编号 | PR-003 |
| 用例名称 | 角色分配 500+ 权限性能 |
| 测试步骤 | 1. 选择有 500 个权限的角色<br>2. 勾选全部权限<br>3. 保存分配 |
| 预期结果 | 1. 保存成功 < 2s<br>2. 权限正确入库 |
| 性能指标 | p95 < 2000ms |

---

### 3.5 仪表盘统计性能测试

#### DS-001: 大数据量仪表盘加载

| 属性 | 内容 |
|-----|------|
| 用例编号 | DS-001 |
| 用例名称 | 10 万用户仪表盘统计性能 |
| 测试步骤 | 1. 准备 100,000 用户<br>2. 打开仪表盘页面<br>3. 测量加载时间 |
| 预期结果 | 1. 仪表盘加载 < 3s<br>2. 所有统计数据正确显示 |
| 性能指标 | p95 < 3000ms |

#### DS-002: 仪表盘统计数据准确性

| 属性 | 内容 |
|-----|------|
| 用例编号 | DS-002 |
| 用例名称 | 大数据量统计准确性验证 |
| 测试步骤 | 1. 获取仪表盘统计<br>2. 直接查询数据库验证 |
| 预期结果 | API 返回值与数据库 COUNT 完全一致 |
| 准确性 | 误差 = 0 |

---

### 3.6 批量操作压力测试

#### BO-001: 批量导入 1000 用户

| 属性 | 内容 |
|-----|------|
| 用例编号 | BO-001 |
| 用例名称 | CSV 批量导入 1000 用户 |
| 测试步骤 | 1. 准备 1000 用户的 CSV 文件<br>2. 执行批量导入<br>3. 测量导入时间 |
| 预期结果 | 1. 导入完成 < 30s<br>2. 全部用户创建成功<br>3. 统计数据显示正确 |
| 性能指标 | < 30s, 内存 < 512MB |

#### BO-002: 批量启用/禁用用户

| 属性 | 内容 |
|-----|------|
| 用例编号 | BO-002 |
| 用例名称 | 批量更新 1000 用户状态 |
| 测试步骤 | 1. 选择 1000 个用户<br>2. 执行批量禁用<br>3. 测量响应时间 |
| 预期结果 | 1. 操作完成 < 10s<br>2. 所有用户状态更新正确 |
| 性能指标 | < 10s |

---

## 4. 性能基准与验收标准

### 4.1 响应时间标准

| 操作类型 | p50 | p95 | p99 | 最大值 |
|---------|-----|-----|-----|-------|
| 分页列表查询（20条） | < 200ms | < 1000ms | < 2000ms | < 5000ms |
| 关键词搜索 | < 500ms | < 2000ms | < 3000ms | < 5000ms |
| 详情查看 | < 100ms | < 500ms | < 1000ms | < 2000ms |
| 创建/更新操作 | < 200ms | < 1000ms | < 2000ms | < 5000ms |
| 删除操作 | < 200ms | < 500ms | < 1000ms | < 2000ms |
| CSV 导出（10万条） | < 30s | < 60s | < 90s | < 120s |
| XLSX 导出（10万条） | < 60s | < 120s | < 180s | < 300s |

### 4.2 资源使用标准

| 资源 | 正常范围 | 告警阈值 | 严重阈值 |
|-----|---------|---------|---------|
| API 服务器 CPU | < 50% | 70% | 85% |
| API 服务器内存 | < 60% | 75% | 85% |
| 数据库 CPU | < 40% | 60% | 80% |
| 数据库内存 | < 50% | 70% | 85% |
| 数据库连接数 | < 50 | 80 | 100 |
| 磁盘 I/O | < 50% | 70% | 85% |

### 4.3 功能正确性标准

- [ ] 统计数据显示误差为 0
- [ ] 筛选结果与数据库一致
- [ ] 分页数据无遗漏无重复
- [ ] 导出数据与列表数据一致
- [ ] 批量操作无数据丢失

---

## 5. 测试执行方案

### 5.1 测试阶段规划

| 阶段 | 数据规模 | 测试内容 | 预计时间 |
|-----|---------|---------|---------|
| Phase 1 | 1,000 用户 | 基础功能验证 | 2h |
| Phase 2 | 10,000 用户 | 中等规模性能基线 | 4h |
| Phase 3 | 50,000 用户 | 大规模验证 | 8h |
| Phase 4 | 100,000 用户 | 极端规模稳定性 | 8h |

### 5.2 测试环境要求

| 环境 | 配置 | 说明 |
|-----|------|-----|
| CPU | 8 核+ | API 和数据库服务器 |
| 内存 | 16GB+ | API 和数据库服务器 |
| 磁盘 | 100GB+ SSD | 存放测试数据 |
| 数据库 | PostgreSQL 14+ | 测试数据库 |
| 网络 | 1Gbps+ | 内部网络 |

### 5.3 测试监控指标

测试过程中监控以下指标：
- API 响应时间分布
- 数据库查询时间
- 内存使用率
- CPU 使用率
- 数据库连接数
- 错误率

---

## 6. 问题记录与复盘

### 6.1 性能问题分级

| 级别 | 定义 | 处理方式 |
|-----|------|---------|
| P0 | 功能不可用 | 立即修复 |
| P1 | 性能严重不达标（> 3x） | 2天内修复 |
| P2 | 性能轻微不达标（1.5-3x） | 1周内修复 |
| P3 | 优化建议 | 规划中处理 |

### 6.2 复盘内容

每次大规模测试后记录：
- 实际性能数据与预期对比
- 发现的问题及处理方式
- 优化建议
- 下次测试重点

---

## 7. 测试脚本工具

### 7.1 locust 压力测试脚本

```python
# tests/load_test_locust.py
from locust import HttpUser, task, between
import random

class AdminUser(HttpUser):
    wait_time = between(1, 3)

    @task(3)
    def list_users(self):
        page = random.randint(1, 100)
        self.client.get(f"/api/v1/admin/users?page={page}&page_size=20")

    @task(1)
    def search_users(self):
        keyword = f"testuser_{random.randint(1, 100000)}"
        self.client.get(f"/api/v1/admin/users?keyword={keyword}")

    @task(2)
    def list_login_logs(self):
        page = random.randint(1, 1000)
        self.client.get(f"/api/v1/logs/login?page={page}&page_size=50")

    @task(1)
    def get_dashboard(self):
        self.client.get("/api/v1/admin/dashboard/stats")
```

### 7.2 k6 性能测试脚本

```javascript
// tests/load_test_k6.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 100 },   // 预热
    { duration: '5m', target: 100 },  // 稳定
    { duration: '2m', target: 200 },   // 峰值
    { duration: '5m', target: 200 },   // 持续
    { duration: '2m', target: 0 },     // 冷却
  ],
  thresholds: {
    http_req_duration: ['p(95)<2000'],
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  const res = http.get(`${__ENV.BASE_URL}/api/v1/admin/users?page=1&page_size=20`);
  check(res, {
    'status was 200': (r) => r.status === 200,
    'response time < 2s': (r) => r.timings.duration < 2000,
  });
  sleep(1);
}
```

---

## 8. 验收检查清单

### 8.1 功能验收

- [ ] 用户列表 10 万级数据加载正常
- [ ] 登录日志 100 万级数据查询正常
- [ ] 设备列表 20 万级数据管理正常
- [ ] 权限树 500+ 节点加载正常
- [ ] 批量导入/导出功能正常

### 8.2 性能验收

- [ ] 分页查询 p95 < 2s
- [ ] 搜索查询 p95 < 2s
- [ ] 10 万用户导出 < 60s
- [ ] 100 万日志导出 < 120s
- [ ] 仪表盘加载 < 3s

### 8.3 稳定性验收

- [ ] 连续压测 30 分钟无内存泄漏
- [ ] 错误率 < 0.1%
- [ ] 无 OOM 或崩溃
