DynamoDB 表关系图
Audit 历史
- 2026-04-26: 9 个 CDK stack deployed(StorageStack / SqsStack / IamStack / LambdaStack / CognitoStack / ContactsStack / MonitoringStack / EventBridgeStack / MessageSystemStack + 条件性 GitHubOidcStack)
- 2026-04-23: deep audit(AWS CLI 实查 + CDK 代码交叉验证)
- 2026-03-02: 上次全量 audit
call-analysis/call-events/LeadTracking-v2仍是 DDB-primary;calls/messages/leads已切换到 Neon-primary read(DDB 表保留作 backward-compat)
Production (us-east-1) — 26 张表
全部 PAY_PER_REQUEST(按需计费)。16 个 CDK-managed + 6 个孤儿表 + 4 个 Legacy 表。
MessageStore 和 Conversations 已部署到 test (us-west-2),尚未部署到 prod。部署后 prod 将有 28 张。
::: CDK Stack 所有权(5 stacks · 4 repos)
Call Analytics — 通话分析 (3) CDK: CallAnalytics-Prod-Storage
::: info Neon-primary read 已切换 (2026-04-26)
call-analysis 表仍接收 ai-analysis-processor 的 dual-write(legacy 兼容),但 Neon calls 已是 primary read source。studio-api 查询通话数据走 Neon SQL(含 store_id filter)。call-events 仍 DDB-primary(无 Neon 镜像)。call-analysis-config 仍 DDB-only(per-client AI 配置,operational table,不迁 Neon)。
call-analysis 11 个 GSI(全部
ProjectionType: ALL)
call-events 有 2 个 GSI:transcriptionJobName-index 和 sessionId-index(RC session 关联)。
⚠ 所有 11 个 GSI 均 ALL projection,写入成本 12 倍。其中 3 个 GSI 记录数为 0,建议排查后删除或改为 KEYS_ONLY。TODO:2026-03-02 审计未记录具体是哪 3 个 GSI。候选(按 feature status 推断,未 verify):call_outcome.outcome_category-callStartTime-index / call_categorization.follow_up_type-callStartTime-index / staff_performance...name-callStartTime-index。下次 audit 需 per-GSI scan --projection-expression 确认。
ConnectionService — 孤儿表 (6) ⚠ 全部 KMS + Deletion Protection
原 RingCentralOAuthStack-prod CDK stack 于 2025-12-29 删除,表因 RETAIN + DP 保留。功能已被 orangetheory-* 的 user+store-centric 模型替代。主应用无运行时依赖(USE_V2_CONFIG_LOADER=false,code path short-circuit),但 lib/stacks/lambda-stack.ts:440-447, 471-479 仍 hardcode CONFIG_TABLE_V2 / ORG_TABLE_V2 / CONN_TABLE_V2 3 个 env vars 到 AnalyticsGenerator + ForceRefresh 2 个 Lambda 的 env(值含 -test 硬后缀,指向在 us-west-2 不存在的 phantom 表)——未启用但未移除。追踪见 infra#658。
ConnectionService → orangetheory-* 新旧表映射
Phone Management — 电话管理 (3) CDK: OrangeTheoryStack-prod
Store & Operations — 门店运营 (7) CDK: OrangeTheoryStack-prod
Lead Tracking — 线索追踪 (1) CDK: LeadTrackingStack
LeadTracking-v2 仍接收 lead-tracking Lambda 的 dual-write(legacy 兼容),但 Neon leads 已是 primary read source,并已带 store_id(lead-tracking #88)。studio-api 查询线索数据走 Neon SQL。
LeadTracking 4 个 GSI
RC Subscription — Webhook 订阅 (2) CDK: RCSubscription-prod-Storage
Legacy 遗留表 (4) Pre-CDK,仅 prod
Test (us-west-2) — 19 张表
Prod 的镜像子集。无 ConnectionService 孤儿表、无 Legacy 表、无 LeadTracking。另有 2 张 prod 尚未部署的新表。
Test 环境差异
- 无 Deletion Protection、无 PITR、全部
removalPolicy: DESTROY— test 环境允许随时重建 - BlackoutPeriods / OperatingHours 同时存在
orangetheory-*和studio-*两个版本。Prod 已清理仅保留studio-*,test 的orangetheory-*版本应在下次部署时清理。⚠️ 2026-04-23 live 发现:orangetheory-BlackoutPeriods-test有 29 items(2026-01-10 创建),studio-BlackoutPeriods-test只有 1 item——legacy 表的数据量反而更多。下次部署清理前必须先把 29 items 迁移到studio-*,否则丢数据。 - 缺少: LeadTracking(CDK 未部署到 test)、ConnectionService(孤儿表,从未创建)、Legacy 4 张(Pre-CDK 手动创建)
:::
实体关系
全局关系图
完整的 DynamoDB 全量表关系图(21 张表 · 36 GSIs · 色彩分组 + 关系箭头),源文件:
diagrams/dynamodb.drawio
数据隔离模型
storeId 是数据围墙 — 每个门店只能看到自己电话号码关联的数据:
Phone Management 数据流
核心问题:哪个电话号码属于哪个门店?
注意事项
::: danger siteId ≠ storeId — 两个完全不同的字段!
call-analysis.siteId=providerAccountId(numeric,RingCentral 账号 ID,如277275028)StoresV2.storeId= UUID(如18a16bde-8f20-4442-9b95-35f9d144b0e7)- 两者不可互换! 要从通话记录关联到门店:
phoneNumber → PhoneStoreAssignments → storeId call-analysis.clientId=franchise-providerAccountId(如orangeTheory-277275028)call-analysis.franchise= 品牌名(如orangeTheory)—— GSIfranchise-callStartTime-index/franchise-siteId-index用此字段
:::
::: info ⚠️ siteId / providerAccountId 即将 rename → accountId
siteId 已统一 rename 为 account_id(callytics-common 0.22.0,migration 0016,2026-04-25 完成)。GSI 索引名 franchise-siteId-index 等不会变(DDB 索引名不能改名),但 item 内字段名已切换。详见 identity-fields.md §1.3 + docs/archive/identity-naming-history.md 演变史。
::: 表名前缀
PhoneNumbers 表的 extensionId 字段类型不一致:有分机的号码为 Number 类型,无分机的为空 String。代码需兼容两种类型。
Archive 候选清单 { #archive }
审计日期: 2026-04-23 · AWS CLI 实查 + CDK 代码引用 + 全仓库 grep 交叉验证 2026-03-02 首次 audit;2026-04-23 deep re-audit 发现 RCU 数据与前次结论的 drift(见各节 live 提示)。
🔴 DEAD — archive 前必须 CloudTrail 追踪读源
::: warning 2026-04-23 live 发现:DEAD 表并非完全零读
aws cloudwatch get-metric-statistics --metric-name ConsumedReadCapacityUnits --period 604800 显示所有 4 张 DEAD 表在过去 7 天都有 2-16 RCU 的读取。读量极低,但 "零业务读写" 的旧结论已被证伪。archive 前必须用 CloudTrail 定位读源,否则冒 "删掉仍在 low-volume 被读的表" 风险。
另:client-configurations 经 aws lambda get-function-configuration RingCentralTokenRefresher-Multi-Tenant 验证,运行时 env var CLIENT_CONFIG_TABLE_V1=client-configurations 被设置(而非 doc 之前推断的空串)——这是 legacy Python Lambda,属于 ConnectionService-CallAnalysisConfigurations-prod 的同批退役范围。
🟡 LEGACY — 需完成前置步骤后 archive
ConnectionService 低活跃表(3 张) — 功能已被 orangetheory-* 替代。callytics-infrastructure 中有 V2 config loader 引用,但 USE_V2_CONFIG_LOADER=false(未启用)。
ConnectionService-CallAnalysisConfigurations-prod(🟢 仍活跃)
Legacy 通话表(2 张) — callytics-infrastructure/lib/config/production-resources.ts 中硬编码了 ARN(fromTableArn 引用)。
这两张表不是 quiet legacy —— 7 天 RCU Sum 分别为 call-analysis=3,482 和 call-events=8,819。"前置步骤 ②(确认零业务读写)" 目前不满足,archive 前必须 CloudTrail 追源并迁移 caller。
Test 环境重复表(2 张)
Archive 优先级
删除操作步骤
- 确认 7 天 CloudWatch RCU/WCU = 0(或已确认来源并切换)
- grep 所有活跃代码仓库确认 0 引用
- 确认数据不需要归档(如需要,先导出到 S3)
- 在 test 环境先执行删除,观察 7 天无报错
- 最后在 prod 执行
:::
安全现状
保护状态总览
每张 prod 持久表都有三层保护:DP(Deletion Protection,防 API/Console 误删)、RETAIN(removalPolicy,防 cdk destroy 误删)、PITR(Point-in-Time Recovery,防数据损坏,可恢复 35 天内任意时间点)。
::: danger ⚠️ 2026-04-23 live 审计发现:Call Analytics 3 张 prod 表 DP + PITR 未启用
CDK code 中 lib/stacks/storage-stack.ts:131-138, 161-168, 279-286 intent 正确(deletionProtection: envConfig.environmentName === 'prod' + pointInTimeRecoverySpecification),但 CallAnalytics-Prod-Storage stack LastUpdatedTime = 2026-02-04,CDK 保护代码 2026-03-02 才 commit(SHA c129c1d)——从未 deploy 到 prod。52 天 risk window。
同样问题影响 call-analytics-prod-call-events-us-east-1 + call-analytics-prod-call-analysis-config-us-east-1。另:rc-subscription-prod-webhook-subscriptions DP 也是 False(PITR ✅,单项漂移)。
Ops 追踪见 infra#656 — 需要 out-of-band aws dynamodb update-table --deletion-protection-enabled + update-continuous-backups,然后 deploy-prod.yml 让 CF 模板对齐。Deploy cadence meta 追踪见 infra#659。
Prod: ⚠️ 4 张应 ✅✅✅ 的持久表实际 DP=False(3 张 Call Analytics + 1 张 webhook-subscriptions),pending ops 修复。Studio 持久表 + LeadTracking 验证 ✅✅✅。瞬态表(TTL 自动过期)无需保护。
Test: 全部 ❌❌❌,允许随时重建。— = 该环境无此表。
ConnectionService 孤儿表和 Legacy 表的保护状态见 Archive 候选。