跳转至

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)
状态 含义 行动
🟢 ACTIVE 有活跃代码引用,正在生产使用 正常维护
🟡 LEGACY 功能已被新表替代,但仍有残留代码引用 排查引用后迁移
🔴 DEAD 零活跃代码引用,业务读写接近零(≤16 RCU/7d) 列入 Archive 候选。archive 前必须 CloudTrail 追踪 2-16 RCU/7d 的实际来源

Production (us-east-1) — 26 张表

全部 PAY_PER_REQUEST(按需计费)。16 个 CDK-managed + 6 个孤儿表 + 4 个 Legacy 表。

28 vs 26 — 为什么少了 2 张?

MessageStore 和 Conversations 已部署到 test (us-west-2),尚未部署到 prod。部署后 prod 将有 28 张。

CDK Stack 所有权(5 stacks · 4 repos)

CDK Stack Repo 表数 表名前缀
CallAnalytics-Prod-Storage callytics-infrastructure 3 call-analytics-prod-*
OrangeTheoryStack-prod studio-website-monorepo 10 orangetheory-* / studio-*
RCSubscription-prod-Storage ringcentralSubscriptionService 2 rc-subscription-prod-*
LeadTrackingStack lead-tracking 1 LeadTracking-v2-*
(已删除) RingCentralOAuthStack-prod (已归档) ringcentralService 6 ConnectionService-*-prod

Call Analytics — 通话分析 (3) CDK: CallAnalytics-Prod-Storage

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)。

表名 状态 PK GSI 说明
call-analytics-prod-call-analysis-us-east-1 🟢 telephonySessionId 11 AI 分析结果(最大表,Neon calls 是 primary read,DDB 仅 dual-write 兼容
call-analytics-prod-call-events-us-east-1 🟢 telephonySessionId 2 原始通话事件(DDB-only,无 Neon 镜像)
call-analytics-prod-call-analysis-config-us-east-1 🟢 client_id 0 每客户分析配置(operational,不迁 Neon
call-analysis 11 个 GSI(全部 ProjectionType: ALL
GSI 名称 Partition Key Sort Key 用途
franchise-callStartTime-index franchise callStartTime 按品牌查通话
franchise-siteId-index franchise siteId 按品牌+门店查
siteId-callStartTime-index siteId callStartTime 按门店查通话
fromPhoneNumber-callStartTime-index fromPhoneNumber callStartTime 按来电号码查
toPhoneNumber-callStartTime-index toPhoneNumber callStartTime 按被叫号码查
callDirection-callStartTime-index callDirection callStartTime 按方向查(呼入/呼出)
staff_performance...name-callStartTime-index staff...name callStartTime 按员工姓名查
call_categorization...classification-callStartTime-index classification callStartTime 按主分类查
call_categorization.follow_up_type-callStartTime-index followuptype callStartTime 按跟进类型查
call_outcome.outcome_category-callStartTime-index outcome_category callStartTime 按结果类别查
transcriptionJobName-index transcriptionJobName 转录任务关联

call-events 有 2 个 GSI:transcriptionJobName-indexsessionId-index(RC session 关联)。

⚠ 所有 11 个 GSI 均 ALL projection,写入成本 12 倍。其中 3 个 GSI 记录数为 0,建议排查后删除或改为 KEYS_ONLYTODO: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-* 新旧表映射
ConnectionService (老) orangetheory-* (新) 说明
Organizations (orgId) StoresV2 (storeId) 数据围墙从 org → store
Connections (orgId+provider) UserConnections (userId+connectionId) RC 连接改为按用户
UserOrganizations (userId+orgId) UserStore (userId+storeId) 成员权限改为按门店
OrgConfigurations (orgId) StoresV2.businessConfig 配置合并到门店表
SubAccountLinks SubAccounts 同样功能,不同 schema
CallAnalysisConfigurations call-analysis-config 直接替代(CDK 管理)
表名 状态 PK SK GSI Streams 说明
ConnectionService-CallAnalysisConfigurations-prod 🟢 client_id 0 Legacy Lambda 仍在读(RCU ~450/7d)
ConnectionService-Organizations-prod 🟡 orgId 0 ✅ NEWANDOLD V2 loader 存在但 USE_V2_CONFIG_LOADER=false
ConnectionService-Connections-prod 🟡 orgId provider 1 ✅ NEWANDOLD V2 loader 存在但未启用
ConnectionService-OrgConfigurations-prod 🟡 orgId 0 ✅ NEWANDOLD V2 loader 存在但未启用
ConnectionService-UserOrganizations-prod 🔴 userId orgId 1 ✅ NEWANDOLD 零代码引用
ConnectionService-SubAccountLinks-prod 🔴 parentUserId childUserId 1 ✅ NEWANDOLD 零代码引用

Phone Management — 电话管理 (3) CDK: OrangeTheoryStack-prod

表名 状态 PK SK GSI 说明
orangetheory-UserConnections-prod 🟢 userId connectionId 4 RC OAuth 连接(KMS 加密)
orangetheory-PhoneNumbers-prod 🟢 providerAccountId phoneNumber 2 RC 电话线路与分机
orangetheory-PhoneStoreAssignments-prod 🟢 storeId phoneNumber 1 电话→门店映射

Store & Operations — 门店运营 (7) CDK: OrangeTheoryStack-prod

表名 状态 PK SK GSI 说明
orangetheory-StoresV2-prod 🟢 storeId 1 门店信息(GSI: userId-index)
orangetheory-UserStore-prod 🟢 userId storeId 1 用户-门店权限 OWNER/EDITOR/VIEWER
orangetheory-SubAccounts-prod 🟢 childUserId 1 子账号关系
orangetheory-OAuthStates-prod 🟢 stateId 0 OAuth 瞬态(TTL: ttl
studio-Cache-prod 🟢 cacheKey 0 通用缓存(TTL 30d: ttl
studio-BlackoutPeriods-prod 🟢 storeId blackoutId 1 禁呼时段
studio-OperatingHours-prod 🟢 storeId sk 0 营业时间(SK: SCHEDULE#date#ts / OVERRIDE#date

Lead Tracking — 线索追踪 (1) CDK: LeadTrackingStack

Neon-primary read 已切换 (2026-04-26)

LeadTracking-v2 仍接收 lead-tracking Lambda 的 dual-write(legacy 兼容),但 Neon leads 已是 primary read source,并已带 store_id(lead-tracking #88)。studio-api 查询线索数据走 Neon SQL。

表名 状态 PK GSI 说明
LeadTracking-v2-us-east-1 🟢 id 4 线索记录(Neon leads 是 primary read,DDB 仅 dual-write 兼容
LeadTracking 4 个 GSI
GSI 名称 Partition Key Sort Key 用途
email-receivedAt-index leadEmail receivedAt 按客户邮箱查
emailFrom-receivedAt-index emailFrom receivedAt 按来源邮箱查
phone-receivedAt-index phone receivedAt 按电话号码查(E.164)
forwardedOriginalFrom-receivedAt-index forwardedOriginalFrom receivedAt 按转发源查

RC Subscription — Webhook 订阅 (2) CDK: RCSubscription-prod-Storage

表名 状态 PK GSI 说明
rc-subscription-prod-webhook-subscriptions 🟢 accountId 1 Webhook 订阅状态
rc-subscription-prod-idempotency 🟢 id 0 Lambda Powertools 幂等性去重(TTL: expiration

Legacy 遗留表 (4) Pre-CDK,仅 prod

表名 状态 说明
call-analysis 🟡 CDK 前旧表,production-resources.ts 仍引用 ARN
call-events 🟡 CDK 前旧表,production-resources.ts 仍引用 ARN
CallAnalysisMetadata 🔴 更早期格式,零代码引用
client-configurations 🔴 旧客户配置,零代码引用

Test (us-west-2) — 19 张表

Prod 的镜像子集。无 ConnectionService 孤儿表、无 Legacy 表、无 LeadTracking。另有 2 张 prod 尚未部署的新表。

# 表名 CDK Stack 备注
1 call-analytics-test-call-analysis-us-west-2 CallAnalytics-Test-Storage
2 call-analytics-test-call-events-us-west-2 CallAnalytics-Test-Storage
3 call-analytics-test-call-analysis-config-us-west-2 CallAnalytics-Test-Storage
4 call-analytics-test-message-store-us-west-2 MessageSystemStack Prod 未部署
5 call-analytics-test-conversations-us-west-2 MessageSystemStack Prod 未部署
6 orangetheory-UserConnections-test OrangeTheoryStack-test
7 orangetheory-PhoneNumbers-test OrangeTheoryStack-test
8 orangetheory-PhoneStoreAssignments-test OrangeTheoryStack-test
9 orangetheory-StoresV2-test OrangeTheoryStack-test
10 orangetheory-UserStore-test OrangeTheoryStack-test
11 orangetheory-SubAccounts-test OrangeTheoryStack-test
12 orangetheory-OAuthStates-test OrangeTheoryStack-test
13 orangetheory-BlackoutPeriods-test OrangeTheoryStack-test ⚠ 与 studio- 重复
14 orangetheory-OperatingHours-test OrangeTheoryStack-test ⚠ 与 studio- 重复
15 studio-Cache-test OrangeTheoryStack-test
16 studio-BlackoutPeriods-test OrangeTheoryStack-test
17 studio-OperatingHours-test OrangeTheoryStack-test
18 rc-subscription-test-webhook-subscriptions RCSubscription-test-Storage
19 rc-subscription-test-idempotency RCSubscription-test-Storage
Test 环境差异
  • 无 Deletion Protection、无 PITR、全部 removalPolicy: DESTROY — test 环境允许随时重建
  • BlackoutPeriods / OperatingHours 同时存在 orangetheory-*studio-* 两个版本。Prod 已清理仅保留 studio-*,test 的 orangetheory-* 版本应在下次部署时清理。⚠️ 2026-04-23 live 发现orangetheory-BlackoutPeriods-test29 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

DynamoDB 全量表关系图

数据隔离模型

storeId 是数据围墙 — 每个门店只能看到自己电话号码关联的数据:

老板 (userId)
  ├── 门店 A (storeId-A)
  │     ├── +1914265xxxx ──→ 门店 A 的 SMS / 通话 / 线索
  │     └── +1914265xxxx ──→ 门店 A 的 SMS / 通话 / 线索
  └── 门店 B (storeId-B)
        ├── +1978489xxxx ──→ 门店 B 的 SMS / 通话 / 线索
        └── +1978489xxxx ──→ 门店 B 的 SMS / 通话 / 线索

❌ 门店 A 看不到门店 B 的数据
❌ 同一个客户在两家店 = 两条独立记录
✅ 老板可以切换门店分别查看

Phone Management 数据流

核心问题:哪个电话号码属于哪个门店?

graph LR
    UC[UserConnections] -->|providerAccountId| PN[PhoneNumbers]
    PN -->|phoneNumber| PSA[PhoneStoreAssignments]
    PSA -->|storeId| ST[StoresV2]

注意事项

siteId ≠ storeId — 两个完全不同的字段!

  • call-analysis.siteId = providerAccountIdnumeric,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)—— GSI franchise-callStartTime-index / franchise-siteId-index 用此字段

⚠️ 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 演变史。

表名前缀

前缀 Repo CDK Stack
call-analytics-prod-* callytics-infrastructure CallAnalytics-Prod-Storage
ConnectionService-*-prod (已归档) ringcentralService (已删除) ⚠ 孤儿表
orangetheory-*-prod studio-website-monorepo OrangeTheoryStack-prod(旧品牌前缀)
studio-*-prod studio-website-monorepo OrangeTheoryStack-prod(新前缀,勿用 orangetheory-)
LeadTracking-v2-* lead-tracking LeadTrackingStack
rc-subscription-prod-* ringcentralSubscriptionService RCSubscription-prod-Storage

PhoneNumbers 表的 extensionId 字段类型不一致:有分机的号码为 Number 类型,无分机的为空 String。代码需兼容两种类型。


Archive 候选清单

审计日期: 2026-04-23 · AWS CLI 实查 + CDK 代码引用 + 全仓库 grep 交叉验证 2026-03-02 首次 audit;2026-04-23 deep re-audit 发现 RCU 数据与前次结论的 drift(见各节 live 提示)。

🔴 DEAD — archive 前必须 CloudTrail 追踪读源

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-configurationsaws lambda get-function-configuration RingCentralTokenRefresher-Multi-Tenant 验证,运行时 env var CLIENT_CONFIG_TABLE_V1=client-configurations 被设置(而非 doc 之前推断的空串)——这是 legacy Python Lambda,属于 ConnectionService-CallAnalysisConfigurations-prod 的同批退役范围。

表名 数据量 DP 7d RCU 前置步骤 说明
CallAnalysisMetadata 401 条 / 117KB 16 CloudTrail 追源 → 确认后删除 更早期格式,零活跃引用
client-configurations 2 条 / 1.5KB 2 退役 RingCentralTokenRefresher-Multi-Tenant(Python,非 CDK)后删除 运行时被 CLIENT_CONFIG_TABLE_V1 env var 引用
ConnectionService-UserOrganizations-prod 62 条 2.5 CloudTrail 追源 → 关闭 DP → 删除 零活跃代码引用,新模型用 UserStore
ConnectionService-SubAccountLinks-prod 13 条 2 CloudTrail 追源 → 关闭 DP → 删除 零活跃代码引用,新模型用 SubAccounts

🟡 LEGACY — 需完成前置步骤后 archive

ConnectionService 低活跃表(3 张) — 功能已被 orangetheory-* 替代。callytics-infrastructure 中有 V2 config loader 引用,但 USE_V2_CONFIG_LOADER=false(未启用)。

表名 DP 替代表 前置步骤
ConnectionService-Organizations-prod orangetheory-StoresV2-prod ① 确认 V2 loader 不再需要 ② 关闭 DP
ConnectionService-Connections-prod orangetheory-UserConnections-prod ① 同上 ② 关闭 DP
ConnectionService-OrgConfigurations-prod StoresV2.businessConfig ① 同上 ② 关闭 DP

ConnectionService-CallAnalysisConfigurations-prod(🟢 仍活跃)

属性
DP
7d RCU ~450 ← 仍有大量读取(2026-04-23 确认)
替代表 call-analytics-prod-call-analysis-config-us-east-1(8 条,CDK 管理)
仍在读取的 Lambda RingCentralTokenRefresher-Multi-Tenant(Legacy Python,非 CDK 管理),env var CONFIG_TABLE_NAME=ConnectionService-CallAnalysisConfigurations-prod
关联 DEAD 读源 同一 Legacy Lambda env var CLIENT_CONFIG_TABLE_V1=client-configurations —— 同批退役
前置步骤 ① 退役 Legacy Lambda(CDK 替代: orangetheory-token-refresh-prod,已在运行) ② 切换 USE_V2_CONFIG_LOADER 到新表 ③ 关闭 DP

Legacy 通话表(2 张)callytics-infrastructure/lib/config/production-resources.ts 中硬编码了 ARN(fromTableArn 引用)。

2026-04-23 live 发现:Legacy 表仍有显著读取流量

这两张表不是 quiet legacy —— 7 天 RCU Sum 分别为 call-analysis=3,482call-events=8,819。"前置步骤 ②(确认零业务读写)" 目前不满足,archive 前必须 CloudTrail 追源并迁移 caller。

表名 数据量 DP 7d RCU 前置步骤
call-analysis 20,487 条 / 27MB 3,482 ① 移除 production-resources.ts 中的 ARN 引用 ② CloudTrail 追源,迁移 caller 到 -prod- 新表 ③ 确认零业务读写 ④ 关闭 DP
call-events 24,550 条 / 69MB 8,819 ① 同上

Test 环境重复表(2 张)

表名 环境 说明
orangetheory-BlackoutPeriods-test us-west-2 studio-BlackoutPeriods-test 重复
orangetheory-OperatingHours-test us-west-2 studio-OperatingHours-test 重复

Archive 优先级

Phase 1 (立即): CallAnalysisMetadata, client-configurations,
                ConnectionService-UserOrganizations, ConnectionService-SubAccountLinks
              → 零引用零读写,风险最低

Phase 2 (退役 Legacy Lambda 后): ConnectionService-CallAnalysisConfigurations-prod
              → 需先退役 RingCentralTokenRefresher-Multi-Tenant

Phase 3 (清理 V2 loader 引用后): ConnectionService-Organizations/Connections/OrgConfigurations
              → 需确认 USE_V2_CONFIG_LOADER 代码路径可移除

Phase 4 (清理 production-resources.ts 后): call-analysis(legacy), call-events(legacy)
              → 需移除 callytics-infrastructure 中的 hardcoded ARN 引用

Phase 5 (Test 清理): orangetheory-BlackoutPeriods-test, orangetheory-OperatingHours-test
              → 下次 CDK deploy 时清理

删除操作步骤

# Step 1: 关闭 Deletion Protection(需要的表)
aws dynamodb update-table \
  --table-name <TABLE_NAME> \
  --no-deletion-protection \
  --region us-east-1

# Step 2: 删除表
aws dynamodb delete-table \
  --table-name <TABLE_NAME> \
  --region us-east-1

删除前 checklist

  • 确认 7 天 CloudWatch RCU/WCU = 0(或已确认来源并切换)
  • grep 所有活跃代码仓库确认 0 引用
  • 确认数据不需要归档(如需要,先导出到 S3)
  • 在 test 环境先执行删除,观察 7 天无报错
  • 最后在 prod 执行

安全现状

保护状态总览

每张 prod 持久表都有三层保护:DP(Deletion Protection,防 API/Console 误删)、RETAINremovalPolicy,防 cdk destroy 误删)、PITR(Point-in-Time Recovery,防数据损坏,可恢复 35 天内任意时间点)。

⚠️ 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。

$ aws dynamodb describe-table --table-name call-analytics-prod-call-analysis-us-east-1 \
    --region us-east-1 --query 'Table.DeletionProtectionEnabled'
false

$ aws dynamodb describe-continuous-backups --table-name call-analytics-prod-call-analysis-us-east-1 \
    --region us-east-1 \
    --query 'ContinuousBackupsDescription.PointInTimeRecoveryDescription.PointInTimeRecoveryStatus'
"DISABLED"

同样问题影响 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 DP Prod RETAIN Prod PITR Test DP Test RETAIN Test PITR
Call Analytics (3) ⚠️ intent=✅ / live=❌ ⚠️ intent=✅ / live=❌
Studio 持久表 (8)
LeadTracking (1)
RC webhook-subscriptions (1) ⚠️ intent=✅ / live=❌
Message System (2)
瞬态表 (3: OAuthStates, Cache, idempotency)

Prod: ⚠️ 4 张应 ✅✅✅ 的持久表实际 DP=False(3 张 Call Analytics + 1 张 webhook-subscriptions),pending ops 修复。Studio 持久表 + LeadTracking 验证 ✅✅✅。瞬态表(TTL 自动过期)无需保护。 Test: 全部 ❌❌❌,允许随时重建。 = 该环境无此表。 ConnectionService 孤儿表和 Legacy 表的保护状态见 Archive 候选

其他配置

配置项 当前状态 备注
计费模式 全部 PAYPERREQUEST 当前规模合适
KMS 加密 仅 ConnectionService(6 张,共用 1 个 KMS key) 含 PII 的表应启用
DynamoDB Streams ConnectionService 5 张表(NEW_AND_OLD_IMAGES call-analysis 表待启用 NEW_IMAGE
TTL OAuthStates (ttl)、Cache (ttl)、idempotency (expiration) = 3 张 瞬态数据表
CDK Tags callytics 3 + ConnectionService 6 + RC Subscription 2 = 11 张 orangetheory-、studio-、LeadTracking 缺少 tags