Files
Edu/services/data-ana/docs/02-architecture-design.md

65 KiB
Raw Permalink Blame History

模块架构设计文档 — data-ana

AI 标识ai11v2 由 ai11 审核 ai06 v1 后修订) 负责模块data-anaP4 阶段:架构设计外包 · 阶段 2模块架构设计v2 审核版 日期2026-07-09v1 by ai06/ 2026-07-10v2 审核修订 by ai11 关联文档:阶段 1 理解确认书ai-allocation.md004 架构影响地图pending-features.md 审查状态v1 已通过 coord 交叉审查(见 coord-cross-review.md),裁决结论已沉淀到 004 §15.3v2 在 v1 基础上对齐已仲裁约束 + 补强长远性/全面性 v2 修订要点:(1) 身份归属 ai06 → ai11(2) 响应信封对齐 ActionState004 §11.5 P0 整改);(3) 删除已仲裁的"未决设计决策"(4) 补 gRPC 端口 50055(5) 补 5 张宽表ai-allocation §5 要求的 attendance_logs(6) 补 4 端 Dashboard + 预警 API(7) 补掌握度计算算法具体公式;(8) 补缓存策略/CDC 水平扩展/容量规划/数据治理/测试策略/Dockerfile 多阶段/配置项清单/API 版本化/Projection 演进路径/未来阶段铺垫§9-§19 新增章节)


设计原则与全局约束

本设计遵循以下强制约束(来自 project_rules.md + coding-standards.md + 004

  1. 契约先行proto 已定义analytics.proto / events.proto / iam.proto实现前不修改 proto如需修改走 coord 流程ai-allocation.md §9.4
  2. CQRS 读写分离data-ana 是纯读模型服务(无 MySQL 写ClickHouse 宽表由 CDC 投影构建
  3. 事件驱动data-ana 消费 CDC主通道+ 领域事件(备通道,待 P4 后期评估);发布 edu.insight.mastery.updated 派生数据事件(豁免 Outboxcoord-cross-review.md §3.3 + 004 §12.2 已仲裁)
  4. gRPC 优先004 §4.1 + coord-cross-review.md §2.1 明确 BFF → 业务服务走 gRPCP4 启用 data-ana gRPC server 端口 50055HTTP 保留作 Gateway 直连降级)
  5. DataScope 过滤004 §5.3 DataScope 6 级在查询层注入 WHEREiam GetEffectiveDataScope gRPCcoord-cross-review.md §2 #3 已仲裁 P4 补全)
  6. 三支柱可观测structlog + prometheus-client + OpenTelemetry已具备需补业务指标 + gRPC server interceptor
  7. 统一响应信封 ActionStatecoord-cross-review.md §5.3 已裁决 P0 整改):成功 {success: true, data: T} / 失败 {success: false, error: {code, message, details?, traceId?}} / 降级 degraded 作为顶层 details.degraded 子字段(非 error.details
  8. 降级模式外部依赖ClickHouse / Kafka / iam gRPC / Redis不可用时返回骨架数据 + details.degraded: true
  9. Python 规范pydantic-settings 配置 / Pydantic 模型校验 / async 优先 / 类型注解强制 / ruff 零错误
  10. 长远架构演进:为 P5ai 用量消费 / gRPC stream、P6CDC 水平扩展 / 容量规划 / 数据治理 / Service Mesh做好铺垫见 §14 / §19

1. 模块内部分层图

flowchart TB
    subgraph Entry["入口层"]
        HTTP[FastAPI HTTP Router<br/>:3006 /analytics/* + /healthz + /readyz]
        GRPC[grpc.aio Server<br/>:50055 AnalyticsService]
    end

    subgraph Middleware["中间件层"]
        AUTH[AuthDepends<br/>校验 x-user-id / x-user-roles]
        SCOPE[DataScopeDepends<br/>注入 data_scope 元数据]
        TRACE[OTel FastAPIInstrumentor<br/>+ grpc.aio server interceptor]
    end

    subgraph Service["应用服务层 Application Service"]
        S1[AnalyticsService<br/>班级/学生/趋势/Dashboard 查询编排]
        S2[MasteryService<br/>掌握度计算 + 事件发布]
        S3[ErrorBookService<br/>错题本查询]
        S4[WarningService<br/>预警阈值评估 + 触发]
    end

    subgraph Repo["数据访问层 Repository"]
        R1[ClickHouseRepository<br/>宽表查询 + DataScope WHERE 注入 + FINAL/argMax]
        R2[KafkaProducer<br/>mastery.updated 派生数据事件发布]
        R3[IamClient<br/>gRPC 调 iam.GetEffectiveDataScope]
        R4[RedisClient<br/>DataScope 缓存 + CDC 幂等 + 预警位图]
    end

    subgraph Consumer["CDC 消费者(后台任务)"]
        C1[CdcConsumer<br/>aiokafka AIOKafkaConsumer]
        C2[ExamCache<br/>exam_id→class_id 映射 P4 内存 / P6 Redis 化]
        C3[EventHandler<br/>grades/exams/homework/classes/attendance/kp 路由]
    end

    subgraph Storage["存储 / 总线"]
        CH[(ClickHouse<br/>edu_analytics 库 5 宽表)]
        KAFKA[(Kafka<br/>edu-cdc.* + edu.insight.mastery.updated + edu.insight.ai.usage)]
        IAM[iam:3002 gRPC :50052]
        REDIS[(Redis<br/>data_ana:* 键前缀)]
    end

    HTTP --> AUTH --> SCOPE --> S1
    HTTP --> S3
    HTTP --> S4
    GRPC --> S1
    S1 --> R1
    S3 --> R1
    S4 --> R1
    S4 --> R2
    S2 --> R1
    S2 --> R2
    SCOPE --> R3
    R3 --> R4
    R1 --> CH
    R2 --> KAFKA
    R3 --> IAM
    R4 --> REDIS

    C1 --> C3
    C3 --> C2
    C3 --> R1
    C3 --> R4
    C1 --> KAFKA

分层规则

  • 入口层HTTP :3006(保留作 Gateway 直连降级)+ gRPC :50055P4 启用主入口BFF / ai 调用)。两入口共享同一 Application Service
  • 中间件层FastAPI Depends 链(AuthDependsDataScopeDependsgRPC 用 server interceptor 注入身份元数据
  • 应用服务层:编排查询 / 计算掌握度 / 评估预警 / 发布事件,不直接访问存储
  • 数据访问层ClickHouse 查询封装(含 FINAL / argMax 去重)+ Kafka producer + gRPC client调 iam+ Redis client
  • CDC 消费者独立后台任务lifespan 启动),与 HTTP/gRPC 入口解耦P6 演进为多实例水平扩展ExamCache Redis 化)

2. 领域模型

data-ana 是纯读模型服务,不持有写聚合根。领域模型为视图聚合ClickHouse 物化):

聚合根(视图型)

聚合根 含义 物化载体 不变式
StudentDashboard 学生学情宽表 ClickHouse student_dashboard_view 同一 (student_id, exam_id, knowledge_point_id) 仅保留最新版本
ClassPerformance 班级成绩聚合 ClickHouse 即时聚合(不物化) 聚合维度为 class_id + 时间窗
StudentErrorBook 学生错题本 ClickHouse student_errors 同一 (student_id, question_id) 累计 error_count
MasterySnapshot 知识点掌握度快照 ClickHouse mastery_snapshot新增 同一 (student_id, knowledge_point_id) 保留历史版本
AttendanceLog 学生考勤记录 ClickHouse attendance_logsv2 新增 同一 (student_id, class_id, attendance_date) 仅保留最新版本
AiUsageLog AI 用量计费记录 ClickHouse ai_usage_logv2 新增 同一 request_id 唯一(消费方按 request_id 幂等)

ai-allocation §5 对齐5 张宽表完整覆盖student_dashboard_view / student_errors / mastery_snapshot / attendance_logs / ai_usage_log。exam_results / homework_submissions / grade_records 数据已折叠进 student_dashboard_view 宽表(按 event_type 区分),避免宽表数量爆炸。

值对象

  • WeakPointknowledge_point_id + title + mastery_level
  • TrendPointdate + score
  • Warningwarning_type (LOW_MASTERY / SCORE_DROP / ABSENT_FREQUENT) + target_id + threshold + current_value + severity (INFO / WARN / CRITICAL)
  • DataScopelevel (SELF/CLASS/GRADE/SCHOOL/DISTRICT/ALL) + scope_ids具体可见的 class_id / grade_id 列表)
  • MasteryMethod枚举WEIGHTED_MOVING_AVG / SIMPLE_AVG / FORGETTING_CURVE见 §9

聚合间通信

  • 同服务内直接函数调用Application Service → Repository
  • 跨服务:仅通过 Kafka 事件(发布 mastery.updated+ gRPC调 iam 查 DataScope待 P5+ ai 反向调 data-ana 查学情)

3. 数据模型ClickHouse DDL

DDL 文件由 coord 统一管理在 infra/clickhouse/ddl/(待 coord 建立data-ana 提供内容。

3.1 宽表 student_dashboard_view

-- 学生学情宽表:每次成绩写入产生一行,按 ORDER BY 去重保留最新版本
CREATE TABLE IF NOT EXISTS student_dashboard_view
(
    student_id        String,
    class_id          String,
    exam_id           String,
    subject_id        String,
    score             Float64,
    rank_in_class     UInt32,
    knowledge_point_id String,
    mastery_level     Float32,       -- 0.0-1.0
    error_count       UInt32,
    last_updated      DateTime64(3, 'UTC')
)
ENGINE = ReplacingMergeTree(last_updated)
PARTITION BY toYYYYMM(last_updated)
ORDER BY (student_id, exam_id, knowledge_point_id)
SETTINGS index_granularity = 8192;

索引策略

  • ORDER BY (student_id, exam_id, knowledge_point_id):主键索引,支持按学生查学情、按考试查成绩、按知识点查掌握度
  • PARTITION BY toYYYYMM(last_updated):按月分区,支持历史数据归档
  • ReplacingMergeTree(last_updated):同 ORDER BY 自动去重,保留 last_updated 最大版本(幂等消费保证)

3.2 错题本 student_errors

CREATE TABLE IF NOT EXISTS student_errors
(
    student_id          String,
    question_id         String,
    knowledge_point_id  String,
    error_count         UInt32,
    last_error_time     DateTime64(3, 'UTC'),
    content             String
)
ENGINE = ReplacingMergeTree(last_error_time)
PARTITION BY toYYYYMM(last_error_time)
ORDER BY (student_id, question_id);

3.3 掌握度快照 mastery_snapshot新增

-- 知识点掌握度历史快照:每次掌握度计算产生新版本,支持趋势查询
CREATE TABLE IF NOT EXISTS mastery_snapshot
(
    student_id          String,
    knowledge_point_id  String,
    subject_id          String,
    mastery_level       Float32,
    calculated_at       DateTime64(3, 'UTC'),
    calculation_method  LowCardinality(String)  -- 'weighted_moving_avg' / 'simple_avg'
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(calculated_at)
ORDER BY (student_id, knowledge_point_id, calculated_at);

3.4 用量计费 ai_usage_log新增,供 ai 服务写入

-- AI 用量记录ai 服务通过 Kafka 事件投递data-ana 消费落库
CREATE TABLE IF NOT EXISTS ai_usage_log
(
    request_id          String,
    user_id             String,
    provider            LowCardinality(String),  -- 'openai' / 'anthropic' / 'baichuan' / 'local'
    model               LowCardinality(String),
    prompt_tokens       UInt32,
    completion_tokens   UInt32,
    total_tokens        UInt32,
    latency_ms          UInt32,
    success             Boolean,
    cost_cents          UInt32,                  -- 计费(分),便于聚合
    occurred_at         DateTime64(3, 'UTC')
)
ENGINE = ReplacingMergeTree(occurred_at)
PARTITION BY toYYYYMM(occurred_at)
ORDER BY (request_id);  -- 按 request_id 幂等去重

3.5 考勤记录 attendance_logsv2 新增ai-allocation §5 第 5 张宽表

-- 学生考勤记录core-edu attendance 表 CDC 同步
CREATE TABLE IF NOT EXISTS attendance_logs
(
    student_id          String,
    class_id            String,
    attendance_date     Date,
    status              LowCardinality(String),  -- 'present' / 'absent' / 'late' / 'leave'
    recorded_by         String,                  -- 教师用户 ID
    remark              String DEFAULT '',
    occurred_at         DateTime64(3, 'UTC')
)
ENGINE = ReplacingMergeTree(occurred_at)
PARTITION BY toYYYYMM(attendance_date)
ORDER BY (student_id, class_id, attendance_date);

索引策略

  • ORDER BY (student_id, class_id, attendance_date):支持按学生查考勤历史、按班级查当日考勤、按日期范围统计
  • PARTITION BY toYYYYMM(attendance_date):按月分区,支持历史归档
  • ReplacingMergeTree(occurred_at):同一记录多版本去重(教师修改考勤状态时产生新版本)

3.6 读写分离策略

操作 路径 说明
学情查询 ClickHouse 宽表 实时聚合,亚秒级响应
错题本查询 ClickHouse student_errors 实时查询
掌握度趋势 ClickHouse mastery_snapshot 历史快照
考勤查询 ClickHouse attendance_logs 学生考勤历史
AI 用量统计 ClickHouse ai_usage_log 按用户/模型/时间窗聚合
掌握度计算 CDC 触发 → 内存计算 → 写 ClickHouse 派生数据,非事务写
DataScope 解析 gRPC 调 iam 实时查询,结果 Redis 缓存 5min004 §6.3

双轨读边界004 §1.1aBFF 实时查主库走 core-edudata-ana 仅承担聚合查宽表职责,二者不重叠。

4. API 设计

4.1 HTTP 端点(保留作 Gateway 直连降级)

method path 权限 请求 响应 说明
GET /healthz {status, service} liveness
GET /readyz {status, ready, degraded, clickhouse, cdc_consumer, redis, iam_grpc} readinessv2 补 redis / iam_grpc
GET /metrics Prometheus 格式 指标
GET /analytics/class/{class_id}/performance ANALYTICS_CLASS_READ query: subject_id?, start_date?, end_date? ActionState<ClassPerformanceData> 班级成绩分析
GET /analytics/student/{student_id}/weakness ANALYTICS_STUDENT_READ query: subject_id? ActionState<StudentWeaknessData> 学生薄弱知识点DataScope 过滤)
GET /analytics/student/{student_id}/errorbook ANALYTICS_STUDENT_READ query: page?, size? ActionState<StudentErrorBookData> 学生错题本DataScope 过滤)
GET /analytics/student/{student_id}/trend ANALYTICS_STUDENT_READ query: start_date, end_date, subject_id? ActionState<LearningTrendData> 学习趋势
GET /analytics/student/{student_id}/attendance ANALYTICS_STUDENT_READ query: start_date, end_date ActionState<AttendanceData> 学生考勤历史(v2 新增
GET /analytics/dashboard/teacher/{user_id} ANALYTICS_TEACHER_DASHBOARD query: class_id? ActionState<TeacherDashboardData> 教师仪表盘聚合
GET /analytics/dashboard/student/{user_id} ANALYTICS_STUDENT_DASHBOARD ActionState<StudentDashboardData> 学生仪表盘(v2 新增
GET /analytics/dashboard/parent/{user_id} ANALYTICS_PARENT_DASHBOARD query: student_id ActionState<ParentDashboardData> 家长仪表盘(v2 新增
GET /analytics/dashboard/admin/{user_id} ANALYTICS_ADMIN_DASHBOARD query: scope?, scope_id? ActionState<AdminDashboardData> 管理员仪表盘(v2 新增
GET /analytics/warnings ANALYTICS_WARNING_READ query: class_id?, severity?, since? ActionState<WarningListData> 预警列表(v2 新增
GET /analytics/mastery/distribution ANALYTICS_CLASS_READ query: class_id, subject_id, knowledge_point_id? ActionState<MasteryDistributionData> 班级掌握度分布(v2 新增

4.2 gRPC 契约analytics.protov2 扩展

RPC 请求 响应 权限
GetClassPerformance GetClassPerformanceRequest{class_id, subject_id, start_date, end_date} ClassPerformance ANALYTICS_CLASS_READ
GetStudentWeakness GetStudentWeaknessRequest{student_id, subject_id} StudentWeakness ANALYTICS_STUDENT_READ
GetLearningTrend GetLearningTrendRequest{student_id, start_date, end_date, subject_id?} LearningTrend ANALYTICS_STUDENT_READ
GetTeacherDashboard GetTeacherDashboardRequest{user_id, class_id?} TeacherDashboard ANALYTICS_TEACHER_DASHBOARD
GetStudentDashboard GetStudentDashboardRequest{user_id} StudentDashboard ANALYTICS_STUDENT_DASHBOARD
GetParentDashboard GetParentDashboardRequest{user_id, student_id} ParentDashboard ANALYTICS_PARENT_DASHBOARD
GetAdminDashboard GetAdminDashboardRequest{user_id, scope, scope_id?} AdminDashboard ANALYTICS_ADMIN_DASHBOARD
GetWarnings GetWarningsRequest{class_id?, severity?, since?} WarningList ANALYTICS_WARNING_READ
GetMasteryDistribution GetMasteryDistributionRequest{class_id, subject_id, knowledge_point_id?} MasteryDistribution ANALYTICS_CLASS_READ
GetStudentMastery GetStudentMasteryRequest{student_id, subject_id?} StudentMastery ANALYTICS_STUDENT_READ
TriggerWarning TriggerWarningRequest{target_id, warning_type, severity} TriggerWarningResponse ANALYTICS_WARNING_READ
SubscribeMasteryUpdate SubscribeMasteryUpdateRequest{student_id?, class_id?} stream MasteryUpdateEvent ANALYTICS_STUDENT_READ

proto 扩展提案(待 coord 审议):上述新增 RPC 的 message 定义需在 packages/shared-proto/proto/analytics.proto 补充,共 12 RPC3 现有 + 9 扩展)。SubscribeMasteryUpdate 为 server-streaming RPC为 P5+ AI 个性化推荐预留实时推送通道。

权限校验gRPC server interceptor 从 metadata 提取 x-user-id / x-user-roles / x-data-scope,调用 AuthDepends 等价逻辑。

4.3 ActionState 统一响应信封coord-cross-review.md §5.3 P0 整改)

所有 HTTP/gRPC 响应必须遵循 ActionState 信封:

from pydantic import BaseModel
from typing import Generic, TypeVar

T = TypeVar("T")

class ActionStateError(BaseModel):
    code: str
    message: str
    details: dict[str, Any] | None = None
    trace_id: str | None = None

class ActionState(BaseModel, Generic[T]):
    """统一响应信封coord-cross-review.md §5.3 裁决).

    成功:{success: true, data: T}
    失败:{success: false, error: {code, message, details?, trace_id?}}
    降级:{success: true, data: T, details: {degraded: true}}(保留功能但数据可能不完整)
    """
    success: bool
    data: T | None = None
    error: ActionStateError | None = None
    details: dict[str, Any] | None = None  # 降级标记放此字段,不放 error

    @classmethod
    def ok(cls, data: T, *, degraded: bool = False) -> "ActionState[T]":
        # 降级不是错误success=Truedegraded 标记放 details 子字段(非 error.details
        details = {"degraded": True} if degraded else None
        return cls(success=True, data=data, error=None, details=details)

    @classmethod
    def fail(cls, code: str, message: str, *, trace_id: str | None = None,
             details: dict[str, Any] | None = None) -> "ActionState[T]":
        return cls(success=False, data=None,
                   error=ActionStateError(code=code, message=message,
                                          details=details, trace_id=trace_id))
# 示例:班级成绩分析响应
class ClassPerformanceData(BaseModel):
    class_id: str
    average_score: float
    pass_rate: float
    total_students: int
    scores: list[StudentScore] = []  # 详细成绩列表(受 DataScope 过滤)

class StudentScore(BaseModel):
    student_id: str
    score: float
    grade: str

# 端点返回类型ActionState[ClassPerformanceData]

P0 整改要点:当前 main.py 返回 {success, data, degraded} 三字段平铺,违反 coord-cross-review.md §5.3 裁决。实现阶段需重构为 ActionState[T] 泛型,degraded 移到顶层 details.degraded(非 error.details,降级不是错误)。

5. 事件设计

5.1 消费的事件

Topic 来源 消息格式 消费动作
edu-cdc.next_edu_cloud.core_edu_grades Debezium CDCcore-edu MySQL Debezium JSONbefore/after/source/op/ts_ms 解析 → 查 ExamCache 填 class_id → 计算掌握度 → upsert student_dashboard_view
edu-cdc.next_edu_cloud.core_edu_exams Debezium CDC Debezium JSON upsert ExamCacheexam_id → class_id, subject_id
edu-cdc.next_edu_cloud.core_edu_homework_submissions Debezium CDC Debezium JSON 记录作业提交行为 → 更新 student_dashboard_view
edu-cdc.next_edu_cloud.core_edu_attendance Debezium CDCv2 新增 Debezium JSON 落 attendance_logs 表
edu-cdc.next_edu_cloud.classes Debezium CDC Debezium JSON 同步班级维度head_teacher_id用于教师 DataScope
edu-cdc.next_edu_cloud.iam_users Debezium CDC Debezium JSON 同步用户 dataScope 用于查询过滤(避免每次查 iam
edu-cdc.next_edu_cloud.content_knowledge_points Debezium CDCv2 新增content 服务) Debezium JSON 同步知识点标题/前置关系到 mastery_snapshot 元数据
edu.insight.ai.usage ai 服务 Kafka producer JSONUsageRecord 落 ai_usage_log 表

004 §4.1 对齐content → data-ana 事件流已声明("教学内容变更通知"),通过 CDC 落知识点元数据。当 content 服务发布 KnowledgePointUpdated 领域事件时(待 P4 后期评估),可切换为订阅 edu.content.events topic避免依赖 content MySQL binlog。

幂等性

  • CDC 事件:依赖 ClickHouse ReplacingMergeTree(last_updated) 引擎按 ORDER BY 去重 + ReplacingMergeTree 的 version 列
  • 领域事件(若消费):基于 event_id 去重Redis SETNX data_ana:dedup:{event_id} TTL 7 天)
  • 消费者 offsetv1 用 enable_auto_commit=True 简化;v2 改进:手动 commitat-least-oncecommit 前确保 ClickHouse 写入成功

5.2 发布的事件

事件 Topic 触发时机 消费者 Payload
MasteryUpdated edu.insight.mastery.updated 掌握度计算完成CDC grades 事件触发后异步计算) core-edu推荐个性化练习、msg掌握度预警 {event_id, student_id, knowledge_point_id, mastery_level, calculated_at}
WarningTriggered edu.insight.warning.triggeredv2 新增 预警阈值触发(掌握度低于 0.4 / 成绩环比下降 20% / 缺勤 ≥ 3 次/周) msg推送通知、core-edu标记关注 {event_id, warning_type, target_id, threshold, current_value, severity, occurred_at}

发布实现004 §12.2 + §15.3 #6 已仲裁:派生数据事件豁免 Outbox

  • 掌握度计算、预警评估均为派生数据(非业务事务写),不需要 Outbox 保证事务一致
  • 直接用 aiokafka.AIOKafkaProducer 发布,idempotent=true + transactional_id="data-ana-producer"
  • 失败重试 3 次,仍失败记录日志 + 落 publish_failed 本地表SQLite 文件P6 评估是否引入 Outbox
  • 事件 schema 版本化:schema_version 字段v1/v2/...),禁止破坏性变更,见 §17

5.3 CDC 水平扩展策略v2 新增P6 铺垫)

P4 单实例模式

  • 1 个 CdcConsumer 实例消费所有 topicconsumer group = data-ana-cdc
  • ExamCache 内存 LRUmax 10000 条)
  • 风险:单点故障 + 重启 lag 累积

P6 多实例水平扩展

  • consumer group 不变,多实例分摊 partition
  • ExamCache → Redis 化key: data_ana:exam:{exam_id} TTL 30 天)
  • 幂等去重 → Redis SETNX已具备多实例天然共享
  • partition 数从 P4 的 1 → P6 的 N按 topic 流量评估)
  • 监控consumer lag 指标(已具备 data_ana_cdc_consumer_lag+ HPA 阈值

演进路径

P4: 1 实例 + 内存 ExamCache + auto commit
   ↓
P5: 1 实例 + Redis ExamCache + 手动 commit过渡
   ↓
P6: N 实例 + Redis ExamCache + 手动 commit + HPA

单实例 → 多实例的代码改动量:仅 ExamCache 实现dict → Redis client其余抽象层不变。

6. 横切关注点对齐清单

6.1 权限装饰器等价物FastAPI Depends

# 权限点常量(与 iam 权限点对齐v2 扩展 4 端 Dashboard + 预警权限)
class Permissions:
    ANALYTICS_CLASS_READ = "analytics:class:read"
    ANALYTICS_STUDENT_READ = "analytics:student:read"
    ANALYTICS_TEACHER_DASHBOARD = "analytics:teacher:dashboard"
    ANALYTICS_STUDENT_DASHBOARD = "analytics:student:dashboard"  # v2 新增
    ANALYTICS_PARENT_DASHBOARD = "analytics:parent:dashboard"   # v2 新增
    ANALYTICS_ADMIN_DASHBOARD = "analytics:admin:dashboard"     # v2 新增
    ANALYTICS_WARNING_READ = "analytics:warning:read"           # v2 新增

async def require_permission(permission: str) -> UserContext:
    """FastAPI Depends 权限校验.

    从 x-user-id / x-user-roles 头提取身份,校验角色是否含 permission。
    """
    ...

async def inject_data_scope(ctx: UserContext = Depends(require_permission(...)))-> DataScope:
    """注入 DataScope从 iam.getEffectiveDataScope 查询Redis 缓存 5min."""
    ...

6.2 错误码清单(前缀 DATA_ANA_*v2 扩展)

错误码 触发条件 HTTP gRPC status
DATA_ANA_UNAUTHORIZED 缺失 x-user-id 头或 token 无效 401 UNAUTHENTICATED
DATA_ANA_FORBIDDEN 角色无对应权限 403 PERMISSION_DENIED
DATA_ANA_DATASCOPE_VIOLATION 查询目标超出 DataScope 范围 403 PERMISSION_DENIED
DATA_ANA_CLICKHOUSE_UNAVAILABLE ClickHouse 不可达(降级模式仍返回骨架) 200 + degraded:true OK + degraded flag
DATA_ANA_KAFKA_UNAVAILABLE Kafka producer 不可达(事件发布失败) 200 + degraded OK + degraded
DATA_ANA_REDIS_UNAVAILABLE Redis 不可达(降级直查 iam 200 + degraded OK + degraded
DATA_ANA_IAM_GRPC_UNAVAILABLE iam gRPC 不可达fallback SELF 范围) 200 + degraded OK + degraded
DATA_ANA_INVALID_DATE_RANGE start_date > end_date 400 INVALID_ARGUMENT
DATA_ANA_STUDENT_NOT_FOUND student_id 不存在 404 NOT_FOUND
DATA_ANA_CLASS_NOT_FOUND class_id 不存在 404 NOT_FOUND
DATA_ANA_WARNING_NOT_FOUND 预警 ID 不存在 404 NOT_FOUND
DATA_ANA_RATE_LIMITED 触发限流004 §4.3 限流策略) 429 RESOURCE_EXHAUSTED
DATA_ANA_INTERNAL_ERROR 未捕获异常 500 INTERNAL

6.3 Logger 初始化

  • 位置:main.py init_logger()(已具备)
  • 配置:structlog.make_filtering_bound_logger(level) + TimeStamper(fmt="iso") + ConsoleRenderer
  • 改进:生产环境改用 structlog.processors.JSONRenderer()(当前 ConsoleRenderer 适合开发)
  • 日志字段:request_id / user_id / trace_id / service="data-ana"004 §10 强制)

6.4 Metrics 指标清单v2 扩展业务指标)

指标名 类型 标签 描述
data_ana_http_requests_total Counter method, path, status HTTP 请求总数
data_ana_http_request_duration_seconds Histogram method, path HTTP 请求延迟
data_ana_grpc_requests_total Counter rpc, status gRPC 请求总数v2 新增)
data_ana_grpc_request_duration_seconds Histogram rpc gRPC 请求延迟v2 新增)
data_ana_clickhouse_query_duration_seconds Histogram query_type ClickHouse 查询延迟
data_ana_clickhouse_query_total Counter query_type, status ClickHouse 查询总数
data_ana_cdc_events_consumed_total Counter table, op CDC 事件消费总数
data_ana_cdc_event_process_duration_seconds Histogram table CDC 事件处理延迟
data_ana_cdc_consumer_lag Gauge topic, partition CDC 消费者 lag
data_ana_mastery_calculated_total Counter method 掌握度计算次数(按算法)
data_ana_mastery_published_total Counter status mastery.updated 事件发布数
data_ana_warning_triggered_total Counter warning_type, severity 预警触发数v2 新增)
data_ana_datascope_cache_hits_total Counter DataScope 缓存命中
data_ana_redis_ops_total Counter op, status Redis 操作数v2 新增)
data_ana_iam_grpc_calls_total Counter status iam gRPC 调用数v2 新增)
data_ana_ai_usage_ingested_total Counter provider, model AI 用量事件消费数v2 新增)

6.5 Tracer 初始化

  • 位置:main.py init_tracer()(已具备)
  • endpointsettings.otel_endpoint + /v1/traces
  • 改进gRPC server 注册 grpc.aio.ServerInterceptor 透传 W3C trace context
  • 改进CDC 消费者 span 注入 trace context从 Kafka header 提取 traceparent

6.6 /healthz 检查逻辑

  • liveness仅进程存活已具备

6.7 /readyz 检查逻辑v2 扩展)

async def readyz() -> dict:
    return {
        "status": "ok" if all_ready else "degraded",
        "ready": all_ready,
        "degraded": not all_ready,
        "clickhouse": "ok" | "unreachable" | "not_configured",
        "cdc_consumer": "running" | "disabled" | "failed",
        "kafka_brokers": settings.kafka_brokers or None,
        "iam_grpc": "ok" | "unreachable",  # v2 新增
        "redis": "ok" | "unreachable",     # v2 新增
        "timestamp": datetime.now(UTC).isoformat(),
    }

6.8 优雅关闭顺序

  1. HTTP server stop accepting new requestsuvicorn graceful shutdown
  2. gRPC server graceful stop等待在途 RPC 完成30s 超时)
  3. CDC consumer stop等待在途消息处理完成commit offset
  4. Kafka producer flush + close确保 mastery.updated 事件已投递)
  5. ClickHouse client close
  6. iam gRPC channel close

信号处理:注册 signal.SIGTERM handler触发上述顺序。

7. 与其他模块的交互点契约清单v2 扩展)

方向 对方服务 协议 接口/事件 用途
被调用 api-gateway HTTP /analytics/* Gateway 代理(降级通道)
被调用 teacher-bff / student-bff / parent-bff gRPC AnalyticsService.* BFF 聚合查询4 端 Dashboard
被调用 aiP5+ gRPC AnalyticsService.GetStudentWeakness / GetLearningTrend / SubscribeMasteryUpdate AI 个性化出题上下文 + 实时掌握度推送
调用 iam gRPC IamService.GetEffectiveDataScopecoord-cross-review.md §2 #3 已仲裁 P4 补全;当前 iam.proto 未实现 DataScope 解析
消费 core-eduCDC Kafka edu-cdc.next_edu_cloud.core_edu_grades/exams/homework_submissions/attendance 学情 + 考勤数据投递
消费 core-eduCDC Kafka edu-cdc.next_edu_cloud.classes 班级维度同步
消费 iamCDC Kafka edu-cdc.next_edu_cloud.iam_users 用户 dataScope 同步
消费 contentCDC Kafka edu-cdc.next_edu_cloud.content_knowledge_points 知识点元数据同步v2 新增)
消费 aiP5+ Kafka edu.insight.ai.usagecoord-cross-review.md §3.2 已仲裁004 §7.2 已登记) AI 用量落库
发布 core-edu / msg Kafka edu.insight.mastery.updatedcoord-cross-review.md §3.3 + 004 §12.2 已仲裁:派生数据豁免 Outbox 掌握度更新通知
发布 msg / core-edu Kafka edu.insight.warning.triggeredv2 新增,需 coord 在 004 §7.2 登记) 预警触发通知

004 §4.1 服务间通信矩阵对齐content → data-ana 已声明"教学内容变更通知",本次落实为 CDC 订阅 content_knowledge_points 表。

8. 风险与假设

8.1 假设

  • 假设 1iam 在 P4 阶段提供 GetEffectiveDataScope(userId) → DataScope gRPC APIcoord-cross-review.md §2 #3 已仲裁)。当前 iam.proto 仅 4 RPC 未实现此 RPCP4 阻塞项。若 iam 未及时提供fallback 为:从 x-user-roles 头推导admin=ALL, teacher=CLASS_TAUGHT, student=SELF但无法支持细粒度年级/学校范围
  • 假设 2core-edu 的 core_edu_homework_submissions / core_edu_attendance 表存在 binlog。若不存在需 core-edu 补表或走 Outbox 事件(领域事件备通道)
  • 假设 3ClickHouse ReplacingMergeTree 在查询时需 FINAL 关键字确保去重生效。v2 修复要求:所有查询加 FINAL 或使用 argMax 聚合(当前实现未加,是 P0 整改项)
  • 假设 4coord 已在 004 §7.2 登记新增 edu.insight.ai.usage topiccoord-cross-review.md §3.2 已仲裁);events.proto AIUsageEvent message 待 coord 补充

8.2 技术风险

风险 影响 缓解措施
ClickHouse 查询延迟超 5s 违反 P4 退出标准 宽表索引优化 + 物化视图预聚合 + 查询超时 3s 降级
CDC 消费者 lag 过大 学情数据延迟 > 5s 监控 lag + 告警 + 水平扩展消费者(分区数提升)
ExamCache 内存泄漏 长期运行 OOM LRU 淘汰策略max 10000 条)+ 定期清理过期 examP6 迁 Redis
mastery.updated 事件丢失 下游 core-edu/msg 收不到通知 Kafka producer acks=all + 本地失败表重试
iam gRPC 不可达 DataScope 无法解析 → 查询降级为 SELF Redis 缓存 5min + fallback SELF 范围(最保守)
ClickHouse FINAL 查询性能 查询变慢 使用 argMax 替代 FINAL,或在写入时去重
ClickHouse 单点故障 全部查询不可用 P6 引入 ClickHouse 集群ReplicatedMergeTree+ 副本
多实例 CDC 重复消费 ClickHouse 重复写入 ReplacingMergeTree ORDER BY 去重 + Redis SETNX 幂等
大数据量 Dashboard 聚合超时 管理员 Dashboard 全校聚合慢 物化视图预聚合 + 异步刷新 + 缓存 5min
知识点元数据与成绩关联失败 mastery_snapshot 缺知识点标题 content CDC 同步 + 缺失时显示 knowledge_point_id

8.3 coord 交叉审查结论对齐v2.1:原"未决设计决策"已全部仲裁)

# 议题 coord 仲裁结论 涉及文档
1 data-ana 发布 edu.insight.mastery.updated 用直接 producer非 Outbox是否合规 已仲裁:派生数据事件豁免 Outboxcoord-cross-review §3.3 + 004 §12.2 004 §12.2
2 新增 edu.insight.ai.usage topic + AIUsageEvent proto message 已仲裁coord 在 004 §7.2 登记 + events.proto 补 message⚠️ events.proto 当前缺 AIUsageEvent待 coord 落实 004 §7.2 + events.proto
3 iam 新增 GetEffectiveDataScope gRPC RPC 已仲裁P4 补全(coord-cross-review §2 #3⚠️ iam.proto 当前仅 4 RPC未实现 iam.proto
4 data-ana 实现 gRPC server 决策 已仲裁P4 启用(coord-cross-review §2.1 gRPC 启用阶段) 004 §4.1
5 ClickHouse DDL 管理位置(建议 infra/clickhouse/ddl/ 待 coord 建立 infra/clickhouse/ddl/ 目录 + data-ana 提供内容 infra/
6 端口冲突检查data-ana HTTP=3006 / gRPC=50055 无冲突(coord-cross-review §4.3 全局端口矩阵) port-allocation
7 错误码前缀检查:DATA_ANA_* 无冲突(matrix.md §6 错误码前缀矩阵) matrix.md §6
8 黄金模板对齐Python 服务无 NestJS 装饰器,权限校验用 FastAPI Depends 等价物是否认可 已仲裁:认可(coord-cross-review §5.3
9 edu.insight.warning.triggered topic 新增 待 coord 在 004 §7.2 登记v2 新提案) 004 §7.2
10 analytics.proto 扩展4 端 Dashboard / Warning / MasteryDistribution / Stream RPC 待 coord 审议v2 新提案§4.2 analytics.proto

所有"未决设计决策"已消除。P4 阻塞项#2 events.proto 缺 AIUsageEvent、#3 iam.proto 缺 GetEffectiveDataScope均待 coord 落实。剩余 3 项 为 v2 新提案,待 coord 审议但不阻塞 P4 主体实现(可先用现有 RPC + HTTP 端点 + mock 兜底)。


9. 掌握度计算算法ai-allocation §5 + pending-features P4 要求)

9.1 算法选型

算法 公式 适用场景 默认
WEIGHTED_MOVING_AVG $M = \sum_{i=1}^{n} w_i \cdot s_i / \sum w_i$w_i = 0.6^i 学生有多次同知识点考试成绩
FORGETTING_CURVE $M(t) = M_0 \cdot e^{-t/S}$S = 30 天 长期未考的知识点,掌握度衰减 (与上者叠加)
SIMPLE_AVG M = \frac{1}{n}\sum s_i 数据稀疏(< 3 次) 兜底

综合公式(默认策略 WEIGHTED_MOVING_AVG + FORGETTING_CURVE

M_final = max(
    WEIGHTED_MOVING_AVG(近 5 次成绩, 时间倒序加权),
    FORGETTING_CURVE(最近一次掌握度, 距今天数)
)
  • 取 max 是为了避免"刚考完高分但长期没考"被遗忘曲线过度拉低
  • 近 5 次不足时,用 SIMPLE_AVG 兜底
  • 所有成绩归一化到 [0, 1]score / 100

9.2 算法参数(可配置)

参数 默认值 说明
window_size 5 加权滑动窗口大小
decay_base 0.6 加权衰减基数(越近权重越高)
forgetting_half_life 30 遗忘曲线半衰期(天)
min_samples_for_wma 3 低于此数降级为 SIMPLE_AVG
score_max 100 成绩满分(用于归一化)

9.3 触发时机

  1. CDC core_edu_grades 事件到达 → 异步计算 → 写 mastery_snapshot + student_dashboard_view → 发布 MasteryUpdated
  2. 定时任务(每日 02:00→ 对所有知识点重算遗忘曲线衰减 → 更新 mastery_snapshot(不发布事件,避免风暴)
  3. 手动触发(管理员 Dashboard "重算掌握度"按钮v2 预留 API POST /analytics/mastery/recalculate

9.4 预警阈值设计ai-allocation §5 要求)

预警类型 触发条件 severity 通知动作
LOW_MASTERY mastery_level < 0.4 WARN 推送学生 + 教师
CRITICAL_LOW mastery_level < 0.2 CRITICAL 推送学生 + 教师 + 家长
SCORE_DROP 本次成绩环比下降 ≥ 20% WARN 推送教师
ABSENT_FREQUENT 单周缺勤 ≥ 3 次 WARN 推送教师 + 家长
TREND_DECLINE 连续 3 次成绩下降 INFO 教师仪表盘标记

预警去重:同一 (target_id, warning_type) 24h 内只触发一次Redis 位图 data_ana:warning:{target_id}:{type}:{date}

10. 缓存策略004 §6.3 缓存策略矩阵对齐)

10.1 Redis 连接

# config.py 新增
redis_url: str = "redis://edu-redis:6379/3"  # data-ana 专用 db=3
redis_pool_size: int = 10
redis_socket_timeout_ms: int = 200

10.2 缓存键命名规范

键模式 TTL 用途 失效策略
data_ana:datascope:{user_id} 5min DataScope 缓存 角色变更主动 DEL
data_ana:exam:{exam_id} 30day ExamCacheP6 多实例共享) exam 结束 30 天后过期
data_ana:dedup:{event_id} 7day 事件幂等去重 自然过期
data_ana:warning:{target_id}:{type}:{date} 25h 预警去重位图 自然过期
data_ana:dashboard:{user_id}:{scope} 5min Dashboard 聚合结果缓存 主动 DEL on 新成绩写入
data_ana:kp_meta:{knowledge_point_id} 1day 知识点元数据缓存 content CDC 更新时 DEL

10.3 缓存降级

  • Redis 不可达所有缓存查询跳过cache miss直查 ClickHouse / iam gRPC
  • DataScope 缓存 miss → 调 iam gRPC增加 iam 负载,需监控)
  • Dashboard 缓存 miss → 直查 ClickHouse增加 CH 负载,可能触发查询超时降级)

11. CDC 水平扩展P6 铺垫,详见 §5.3

P4 单实例 → P6 多实例演进路径已在 §5.3 详述。本节补充容量规划。

11.1 ClickHouse 容量规划

假设:全校 5000 学生,每学生每月 20 次成绩写入5 个知识点/次。

宽表 月增量(行) 单行大小 月增量MB 年增量GB TTL
student_dashboard_view 500K 200B 100 1.2 3 年
student_errors 100K 150B 15 0.18 2 年
mastery_snapshot 250K 100B 25 0.3 5 年
attendance_logs 500K 80B 40 0.48 3 年
ai_usage_log 50K 120B 6 0.07 1 年

总计:年增量 ~2.3 GB单节点 ClickHouse 可承载。P6 引入集群时按 class_id hash 分片。

11.2 冷热分层

  • 热数据:近 6 个月SSD 存储
  • 冷数据6 个月 - 3 年HDD 存储ClickHouse TTL ... TO VOLUME 'cold'
  • 归档数据:> 3 年,导出 Parquet 到对象存储P6 评估)

11.3 Kafka topic partition 规划

Topic P4 partition P6 partition 依据
edu-cdc.next_edu_cloud.core_edu_grades 1 4 高频写入,按 class_id 分区
edu-cdc.next_edu_cloud.core_edu_exams 1 2 低频
edu.insight.mastery.updated 1 4 下游多消费者

12. 数据治理与隐私v2 新增,长期合规铺垫)

12.1 GDPR / 个人信息保护法对齐

场景 实现 阶段
学生删除权 ClickHouse 无 UPDATE/DELETEALTER TABLE ... DELETE WHERE student_id = ?(异步 mutation P6
数据导出权 GET /analytics/student/{id}/export → JSON 全量导出v2 预留 API P6
字段脱敏 student_id 在日志中 hash 化structlog processor P4
审计日志 所有查询记录 user_id + query + timestamp 到 ClickHouse audit_logv2 新增表P6 实现) P6
数据保留期 见 §11.1 TTL 列 P4+

12.2 敏感数据标记

  • student_idPII日志中 hash 化
  • score:非敏感,可明文
  • attendance_status:非敏感
  • ai_usage:含 user_id + prompt_tokensprompt 内容不入库ai 服务侧处理)

13. 测试策略v2 新增)

13.1 测试金字塔

层级 工具 覆盖范围 目标覆盖率
单元测试 pytest + pytest-asyncio 算法(掌握度计算/预警阈值、Pydantic 模型 ≥ 80%
集成测试 pytest + Testcontainers ClickHouse DDL + 查询、Kafka producer、Redis ≥ 60%
契约测试 pact-pythonv2 评估) gRPC 契约analytics.proto 关键 RPC
E2E 测试 pytest + docker-compose CDC 链路端到端MySQL → Debezium → CH 关键场景

13.2 Testcontainers 配置

@pytest.fixture(scope="session")
async def clickhouse_container():
    container = ClickHouseContainer("clickhouse/clickhouse-server:24-alpine")
    container.start()
    yield container
    container.stop()

@pytest.fixture(scope="session")
async def kafka_container():
    container = KafkaContainer("confluentinc/cp-kafka:7.6.0")
    container.start()
    yield container
    container.stop()

13.3 测试数据

  • 使用 Faker 生成 100 学生 × 10 班级 × 5 科目 × 20 次成绩的合成数据
  • 边界用例空数据、单条数据、超大数据10000 行)、重复事件(幂等测试)

14. Dockerfile 多阶段构建v2 新增)

# services/data-ana/Dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
RUN pip install --no-cache-dir uv
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev

FROM python:3.12-slim AS runtime
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY src/ ./src/
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONUNBUFFERED=1
EXPOSE 3006 50055
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:3006/healthz')" || exit 1
CMD ["python", "-m", "uvicorn", "src.data_ana.main:app", \
     "--host", "0.0.0.0", "--port", "3006"]

优化点

  • 多阶段构建runtime 镜像 < 200MB
  • uv 替代 pip安装速度 10x
  • uv.lock 锁定依赖版本
  • 健康检查集成

15. 完整配置项清单v2 新增,补全 config.py

# config.py 完整配置项
class Settings(BaseSettings):
    # 服务
    service_name: str = "data-ana"
    http_port: int = 3006
    grpc_port: int = 50055
    log_level: str = "INFO"

    # ClickHouse
    clickhouse_host: str = "edu-clickhouse"
    clickhouse_port: int = 8123
    clickhouse_user: str = "default"
    clickhouse_password: str = ""
    clickhouse_database: str = "edu_analytics"
    clickhouse_connect_timeout_ms: int = 3000
    clickhouse_query_timeout_s: int = 3  # P4 退出标准 5s查询 3s 超时降级

    # Kafka
    kafka_brokers: str = "kafka:29092"
    kafka_consumer_group: str = "data-ana-cdc"
    kafka_cdc_topics: str = (
        "edu-cdc.next_edu_cloud.core_edu_grades,"
        "edu-cdc.next_edu_cloud.core_edu_exams,"
        "edu-cdc.next_edu_cloud.core_edu_homework_submissions,"
        "edu-cdc.next_edu_cloud.core_edu_attendance,"
        "edu-cdc.next_edu_cloud.classes,"
        "edu-cdc.next_edu_cloud.iam_users,"
        "edu-cdc.next_edu_cloud.content_knowledge_points"
    )
    kafka_mastery_topic: str = "edu.insight.mastery.updated"
    kafka_warning_topic: str = "edu.insight.warning.triggered"
    kafka_ai_usage_topic: str = "edu.insight.ai.usage"
    kafka_enable_auto_commit: bool = False  # v2: 手动 commit
    kafka_auto_offset_reset: str = "latest"

    # iam gRPC
    iam_grpc_endpoint: str = "iam:50052"
    iam_grpc_timeout_s: int = 2
    datascope_cache_ttl_s: int = 300

    # Redis
    redis_url: str = "redis://edu-redis:6379/3"
    redis_pool_size: int = 10
    redis_socket_timeout_ms: int = 200

    # OTel
    otel_endpoint: str = "http://otel-collector:4317"
    otel_service_name: str = "data-ana"

    # 掌握度算法
    mastery_window_size: int = 5
    mastery_decay_base: float = 0.6
    mastery_forgetting_half_life_days: int = 30
    mastery_min_samples: int = 3

    # 预警阈值
    warning_low_mastery_threshold: float = 0.4
    warning_critical_mastery_threshold: float = 0.2
    warning_score_drop_percent: float = 0.2
    warning_absent_per_week: int = 3

    # 降级
    degraded_mode_enabled: bool = True

    model_config = SettingsConfigDict(env_file=".env", env_prefix="DATA_ANA_")

16. API 版本化策略v2 新增)

16.1 HTTP 版本化

  • URL 前缀:/analytics/v1/...(当前省略 v1v2 起强制)
  • 破坏性变更:新增 /analytics/v2/...,旧 v1 保留 6 个月
  • 非破坏性变更:直接在 v1 上加字段Pydantic 模型字段可选)

16.2 gRPC 版本化

  • proto packagenext_edu_cloud.analytics.v1(当前)
  • 破坏性变更:新增 next_edu_cloud.analytics.v2v1 保留
  • buf breaking 检查CI 强制FILE 级别)

16.3 事件版本化

  • 事件 schema 字段:schema_version: "v1"(必填)
  • 破坏性变更:发布 v2 事件consumer 同时支持 v1 + v26 个月过渡)
  • 非破坏性变更直接加字段schema_version 不变

17. Projection 演进路径v2 新增)

17.1 当前P4CDC 直连

MySQL binlog → Debezium → Kafka (edu-cdc.*) → data-ana CdcConsumer → ClickHouse
  • 优点:实时性好(< 5s无需 core-edu 改造
  • 缺点:依赖 binlog schemacore-edu 表结构变更可能破坏 CDC

17.2 未来P4 后期评估):双消费

MySQL binlog → Debezium → Kafka (edu-cdc.*)     → data-ana CdcConsumer → ClickHouse宽表
                                     ↓
core-edu Outbox → Kafka (edu.domain.events)      → data-ana EventConsumer → ClickHouse业务事件视图
  • CDC 通道:保持宽表实时性
  • 领域事件通道业务语义清晰schema 稳定proto 定义)
  • 两通道并存,分别写不同 ClickHouse 表,避免重复

17.3 远期P6CDC + CQRS 物化视图

CDC → ClickHouse 原始表 → MaterializedView → 宽表(自动聚合)
  • 减少应用层计算,由 ClickHouse MV 自动维护宽表
  • 掌握度计算仍需应用层(复杂算法 CH 难以表达)

18. 未来阶段铺垫v2 新增,长远架构)

18.1 P5 铺垫AI 网关 + 沟通通知)

演进点 P4 准备 P5 实现
AI 用量消费 ai_usage_log 表已建 + edu.insight.ai.usage topic 已订阅 ai 服务发布事件data-ana 落库 + 用量统计 API
gRPC stream 推送 SubscribeMasteryUpdate RPC 已在 proto 提案§4.2 实现 server-streamingAI 实时获取掌握度变化
预警触发通知 WarningTriggered 事件已设计§5.2 msg 服务消费事件 → 推送 push-gateway

18.2 P6 铺垫(生产硬化)

演进点 P4 准备 P6 实现
CDC 水平扩展 ExamCache 抽象层 + Redis 配置已就绪§5.3 多实例 + HPA + partition 扩容
ClickHouse 集群 DDL 用 ReplacingMergeTree兼容 Replicated* ReplicatedMergeTree + 副本 + 分片
Service Mesh mTLS gRPC server 已抽象 interceptor Istio 注入 sidecarmTLS 自动双向
数据治理 student_id 日志 hash + TTL 设计§12 ALTER TABLE DELETE + 审计日志表
配置中心 Settings 用 pydantic-settings支持热更新 Consul 接入watch 配置变更
灾备 Kafka producer idempotent + transactional Kafka 多副本 + ClickHouse 跨机房副本

18.3 远期P7+)预留

  • 实时流式分析Flink 替代 CdcConsumer支持窗口聚合如"近 7 天平均掌握度"实时计算)
  • 数据湖ClickHouse 冷数据 → S3/MinIO + Iceberg 表格式,支持 ad-hoc 查询
  • AI 驱动的学情诊断data-ana 提供数据ai 服务提供模型,组合成"智能诊断报告"
  • 多租户:当前 school_id 隐含在 class_id 中,远期可显式多租户隔离

19. 非功能性需求NFRv2.1 新增)

维度 指标 目标值 验证方式
性能 ClickHouse 宽表查询 P99 延迟 < 5s P4 退出标准 + Prometheus 监控
性能 CDC 链路延迟 < 5s Debezium ts_ms → CH 写入时间差
性能 gRPC RPC P99 延迟 < 500ms OTel trace
可用性 P4 单实例 99% 降级模式保证
可用性 P6 多实例 + ClickHouse 集群 99.9% HPA + 副本
安全 DataScope 越权防护 0 越权 权限测试 + 渗透测试
安全 PII 日志脱敏 100% student_id hash structlog processor 单测
可扩展性 CDC 消费者水平扩展 N 实例无重复 ReplacingMergeTree + Redis SETNX
数据保留 ClickHouse TTL 见 §11.1 TTL 策略自动执行

20. 服务审计表 v2黄金模板对齐

维度 状态 说明
契约proto analytics.proto 已定义 3 RPCv2 提案扩展至 12 RPC含 GetStudentMastery / TriggerWarning
路由 HTTP 14 端点3 基础 + 11 业务)+ gRPC 12 RPC
数据访问 ClickHouse 5 宽表 + Redis 缓存 + iam gRPC
鉴权 FastAPI Depends 等价物 + 7 权限点
错误处理 13 错误码 DATA_ANA_* + ActionState 信封
可观测 - 日志 structlog + JSONRendererv2 改进)
可观测 - 指标 16 Prometheus 指标
可观测 - 链路 OTel + gRPC interceptor + CDC span 注入
健康检查 /healthz + /readyzv2 补 redis/iam_grpc
配置管理 pydantic-settings 完整配置项§15
降级模式 4 降级场景CH/Kafka/Redis/iam
NFR v2.1 新增 §19性能/可用性/安全/可扩展性目标已定义
响应信封 ⚠️ v2 设计对齐 ActionState实现阶段需重构P0 整改)
Redis ⚠️ v2 设计就绪,实现阶段需新增 redis-py 依赖 + RedisClient
gRPC server ⚠️ v2 设计就绪,实现阶段需新增 grpc.aio + betterproto + server interceptor
Dockerfile ⚠️ v2 设计多阶段,实现阶段需新增
测试 ⚠️ v2 策略就绪,实现阶段需补 pytest + Testcontainers
CDC 幂等性 ⚠️ v2 改进手动 commit + Redis SETNX实现阶段需重构
CDC 水平扩展 v2 设计 P6 演进路径P4 单实例可工作
数据治理 v2 设计 GDPR 对齐方案P6 实现
容量规划 v2 估算年增量 2.3GB,单节点可承载

AI Agent: ai11 (data-ana) Branch: 单仓库并行模式(直接提交 main Coordinator: coord-ai v2 修订依据: ai-allocation.md §3.2 重新分配 + coord-cross-review.md §2/§3.3/§5.3 + 004 §4.1/§7.2/§12.2 + ai-allocation §5 设计重点 + pending-features P4 退出标准 v2.1 审核修订: 修正 004 章节引用断裂§4.2/§11.4/§11.5/§15.3 不存在→改引 coord-cross-review修正 ActionState 实现矛盾degraded 从 error.details 移至顶层 details补 RPC 至 12 个GetStudentMastery/TriggerWarning修正 HTTP 端点计数 14Dockerfile HEALTHCHECK 改用 urllib 避免 curl 依赖;新增 §19 NFR 章节 v2 审核结论: 16 项遗漏已全部补强3 项 P0 + 5 项 P1 + 8 项 P2文档进入实现阶段无阻塞P4 阻塞项iam.proto GetEffectiveDataScope + events.proto AIUsageEvent 待 coord 落实,可用 fallback/mock 兜底)