数据对账系统 Runbook
last-verified: 2026-07-14(对照 infra PR #1459;发现和代码不符以代码为准)
⚠️ 两处以代码为准的关键更正(见对应 section 内联说明):
- Metric 已改名(#775):
ReconciliationCase{1..4,D}→CallsMissingInDB/CallsRecordingNotDownloaded/CallsTranscriptionNotStarted/CallsTranscriptionNotCompleted/CallsOrphaned。下文旧名保留作历史对照,查 CloudWatch 用新名。- 对账不用 AWS Transcribe:所有修复都是「重置 flag + 往 transcribe-queue 重投 synthetic webhook」,走正常 Deepgram 路径(
reconciliation-worker无任何transcribe:*SDK 调用)。旧文档里aws transcribe get/delete-transcription-job命令已废弃。- Schedule 当前不是自动运行:live CDK 在所有 environments 都配置
state: 'DISABLED'。本页下方的 “Every 3 hours / auto-enabled” 是历史设计说明,不得当作当前 safety net。另见 RingCentral Call Log、录音就绪与 Rate Limit 调查。
Quick Links
- Troubleshooting Guide: reconciliation/troubleshooting.md
- Dashboard: CloudWatch Dashboard
- Monitoring Guide: ../01-MONITORING/README.md
- Alerts Guide: ../02-ALERTING/README.md
System Overview
Purpose
The reconciliation system detects and fixes data inconsistencies between RingCentral (source of truth) and DynamoDB (local state) by periodically scanning for missing or incomplete call records.
What It Does:
- Identifies calls that exist in RingCentral but are missing from DynamoDB
- Detects calls stuck at various processing stages (recording download, transcription)
- Fixes issues automatically by replaying the processing pipeline
- Prevents data loss from webhook delivery failures or transient errors
When It Runs:
- Automated: Every 3 hours via EventBridge schedule
- Manual: On-demand via Lambda test event or CLI
- Emergency: After major incidents or data integrity concerns
Architecture Evolution
V2.5.0 (November 2025): SQS Fan-Out Architecture
Before (V2.4): Orchestrator processed all actions in single invocation
- Problem: 37+ RingCentral API calls per client in single Lambda
- Impact: Rate limit storms, 429 errors, failed reconciliations
After (V2.5.0): SQS fan-out with wide-spread retry
Performance Impact:
- 97% API Call Reduction: 37+ calls → 1 call per client
- Rate Limit Prevention: Wide-spread retry (2-15 min random delays)
- Independent Retry: Each action can retry independently via SQS
- Concurrency Control: Reserved concurrency = 2 prevents API storms
V2.5.3 (November 2025): Auto-Enable in Dev/Test
Schedule State:
- Production: Disabled by default (manual enable required for safety)
- Dev/Test: Auto-enabled (no manual CLI step needed)
- Benefit: Eliminates "enable schedule after deploy" operational step
How to Override:
Reconciliation States (V2.4.1 Migration)
The system uses a 4-state model to classify call record issues:
Legacy Model (Deprecated in V2.4.1):
- Case A → State 1 (missing in DB)
- Case B + C → State 4 (transcription stuck)
- Case D → Unchanged (orphaned records)
Migration Timeline:
- Code Updated: V2.4.1 (November 28, 2025)
- Metrics Changed:
CallAnalytics→CallAnalytics/RingCentralnamespace - Dashboard Updated: Row 9 added for per-client case breakdown
Key Characteristics
Orchestrator Lambda
- Runtime: Node.js 20
- Memory: 256 MB
- Timeout: 1 minute
- Concurrency: Unreserved (auto-scales with schedule)
- Trigger: EventBridge schedule (cron:
0 */3 * * ? *) - Schedule State: Auto-enabled (dev/test), Disabled (prod)
- Metrics Namespace:
CallAnalytics/RingCentral
Worker Lambda
- Runtime: Node.js 20
- Memory: 512 MB
- Timeout: 15 minutes
- Concurrency: Reserved 2 (prevents RingCentral 429 errors)
- Trigger: SQS (reconciliation-worker-queue)
- Batch Size: 1 message at a time (sequential processing)
- DLQ: reconciliation-worker-dlq (3 retries before DLQ)
- Metrics Namespace:
CallAnalytics/RingCentral
Reconciliation Window
- Default: 6 hours (configurable via
RECONCILIATION_WINDOW_HOURSenv var) - Rationale:
- 2x processing SLA headroom (normal processing: ~3 hours)
- Minimizes duplicate API calls (50% reduction vs 7-day window)
- Balances freshness vs API cost
- Frequency: Every 3 hours (2 overlapping scans per window)
- Maximum: 90 days (RingCentral retention period)
Dashboard Integration
Primary Monitoring (Row 9: Per-Client Reconciliation)
Dashboard Row 9 provides per-client visibility into reconciliation case breakdown:
Dashboard Features:
- Stacked Area Charts: Visualize trends over time (last 3 hours by default)
- ClientId Dimension: Drill down to specific clients (e.g.,
orangeTheory-2c9fc00886d14b9a9a24a12d337c438c) - Color Coding: GREEN (State 1-3), BROWN (State 4, Case D)
- Auto-Refresh: Updates every minute
Supporting Metrics (Other Dashboard Rows)
Health Check Guide
✅ Healthy System
⚠️ Warning (Investigate)
Actions:
- Check CloudWatch Logs Insights for error patterns
- Review per-client breakdown (click widget → "View in metrics")
- Monitor for trend continuation (is it getting worse?)
- Prepare for manual intervention if critical threshold reached
🚨 Critical (Immediate Action Required)
Actions:
- Disable schedule immediately (prevent more failures)
- Check RingCentral service status (outage?)
- Review last deployment (recent change?)
- Follow runbook for specific error pattern
- Escalate to engineering if root cause unclear
Per-Client Drill-Down
Dashboard Row 9 widgets include ClientId dimension for client-specific investigation:
Step 1: Identify Affected Clients
Example Output:
Step 2: Check Time-Series Trend
Step 3: Cross-Reference with Logs
告警语义(谁 page 人)
日志级别 ≠ 告警级别。PROD 页人靠 CloudWatch alarm → SNS → alarm-dispatcher Lambda → Lark,不靠 Lambda logger.error(logger.error 只到 CloudWatch + Sentry,不发 Lark;只有 logger.alert 直接发 Lark)。以 lib/stacks/monitoring-stack.ts 为准。
Reconciliation 相关 alarm:
收到告警怎么办 → DLQ / queue-age 的响应 step 统一在 infra repo 的 Pipeline Monitoring Runbook + Queue age 维护,这里不重复。
Common Issues
Issue 1: ReconciliationCase1 Increasing (Missing Calls)
Symptoms
- Dashboard: Row 9 Widget 1 showing ReconciliationCase1 >10
- Dashboard: Row 9 Widget 6 total issues trending up
- Metrics:
ReconciliationCase1dimensionClientIdidentifies specific clients - Logs: Messages like
State 1: Missing in DBin worker logs
Investigation
Step 1: Identify Affected Clients
Expected Output:
Step 2: Check RingCentral API Health
Step 3: Check Webhook Delivery
Root Cause Analysis
Scenario A: Webhook Delivery Failure
- Symptoms: Calls made but webhooks not received → Reconciliation detects and backfills
- Dashboard: Row 1 Left (TranscribeProcessor) shows low throughput
- Investigation: Check API Gateway metrics for 4xx/5xx errors on webhook endpoint
- Expected Behavior: Reconciliation self-heals by creating missing records
- Fix: Usually self-heals within 3 hours. If persistent, contact RingCentral support for webhook delivery investigation.
Scenario B: TranscribeProcessor Errors
- Symptoms: Webhooks received but processing failed → DLQ accumulation
- Dashboard: Row 1 Left shows DLQ depth >0
- Investigation:
- Fix: Follow transcribe-processor.md#dlq-recovery
Scenario C: Calls Without Recordings
- Symptoms: RingCentral calls exist without recordings (e.g., voicemail, abandoned calls)
- Dashboard: ReconciliationCase1 count matches calls without recordings
- Investigation:
- Expected Behavior: V2.5.1+ creates audit records with
hasRecording: false(not an error) - Fix: No action needed - these are processed correctly
Scenario D: EventBridge Schedule Disabled
- Symptoms: ReconciliationCase1 increasing over multiple days (no automatic cleanup)
- Investigation: Check schedule state
- Fix: Enable schedule (see Operational Procedures)
Resolution
Automatic (Recommended):
- Monitor Row 9 Widget 1 for trend reversal (should decrease within 3-6 hours)
- Reconciliation runs every 3 hours and auto-fixes State 1 issues
- If trending down, no action needed
Manual (Emergency):
Preventive:
- Ensure EventBridge schedule is enabled (auto-enabled in dev/test as of V2.5.3)
- Monitor webhook delivery success rate
- Set up alerts for ReconciliationCase1 >10 per client
Issue 2: ReconciliationCase4 Persistent (Transcription Stuck)
Symptoms
- Dashboard: Row 9 Widget 4 showing ReconciliationCase4 >5
- Metrics:
ReconciliationCase4indicates transcriptions not completing - Duration: Issues persisting >24 hours
- Logs: Messages like
State 4: Transcription not completed
Investigation
Step 1: Identify Stuck Transcriptions
Expected Output:
Step 2: 确认转录卡在哪一步(不是查 AWS Transcribe job)
⚠️ 转录早已不是 AWS Transcribe,没有
aws transcribe get-transcription-job可查。State 4 的修复方式是 worker 重置 flag + 往 transcribe-queue 重投 synthetic webhook,重新走 Deepgram(handleCaseB,reconciliation-worker/src/handler.ts:752)。State 4 的transcriptionJobName只是内部标识符。
Step 3: Check DynamoDB State
Root Cause Analysis
Scenario A: 转录反复失败(Deepgram)
- Symptoms: transcribe-processor 侧持续
Deepgram failed/DEEPGRAM_SUSTAINED_FAILURE - Dashboard:
CallsTranscriptionNotCompleted(旧名 ReconciliationCase4)与失败量对应 - Common Failure Reasons:
- Deepgram API key 失效 / secret 缺失
- 录音无法访问(S3 IAM)或音频损坏
- Deepgram 服务 outage
- Fix:
- 排查见 transcribe-processor.md Issue 5
- 修好根因后,reconciliation 每 3 小时会自动重投 webhook 重试;无需手动碰 AWS Transcribe
Scenario B: DynamoDB State Desync(transcript 已产出但 DB 没更新)
- Symptoms: S3 里已有 transcript,但
transcriptionCompleted= false in DB - Investigation: Check ai-analysis-processor (result processor) logs
- Fix: Manual DB update or re-trigger downstream processing
Scenario C: Deepgram 服务 outage
- Symptoms: 多个 session 同时卡住,transcribe-processor 侧
DEEPGRAM_SUSTAINED_FAILURE - Dashboard:
CallsTranscriptionNotCompleted跨多 client spike - Investigation: 查 Deepgram 服务状态(https://status.deepgram.com)
- Fix: 等服务恢复;reconciliation 会自动重投重试
Resolution
Automatic (Recommended):
- Reconciliation Worker 每 3 小时重试 State 4(重置 flag + 重投 webhook → 重新 Deepgram 转录)
- Monitor Row 9 Widget 4(
CallsTranscriptionNotCompleted)for trend (should decrease within 6-9 hours)
Manual (If persistent >12 hours):
Issue 3: CaseD Spike (Orphaned Records)
Symptoms
- Dashboard: Row 9 Widget 5 showing ReconciliationCaseD >20
- Metrics:
ReconciliationCaseDindicating orphaned records - Logs: Messages like
Case D: Orphaned Record
Investigation
Step 1: Identify Orphaned Records
Step 2: Verify RingCentral Deletion
Step 3: Check Record Age
Root Cause Analysis
Scenario A: Normal RingCentral Retention (Expected)
- Symptoms: Records >90 days old being marked as orphaned
- Dashboard: CaseD count stable, not increasing rapidly
- Explanation: RingCentral deletes calls after 90 days (retention policy)
- Fix: No action needed - this is expected behavior
- Note: DynamoDB records are marked as orphaned but NOT deleted (audit trail)
Scenario B: Bulk RingCentral Account Deletion
- Symptoms: Sudden spike in CaseD across all clients or specific client
- Dashboard: CaseD increased by >50 in single reconciliation run
- Investigation: Contact RingCentral support to verify account status
- Fix: If account was deleted intentionally, mark all records as orphaned (permanent)
Scenario C: DynamoDB Test Data
- Symptoms: CaseD spike after dev/test environment migration
- Dashboard: CaseD only in dev/test, not prod
- Explanation: Test data created manually without corresponding RingCentral calls
- Fix: Purge test data from DynamoDB
Resolution
Normal Behavior:
- CaseD count <20 is normal (gradual accumulation from 90-day retention)
- No action needed - records are marked as orphaned for audit trail
Abnormal Spike:
Issue 4: ReconciliationWorker DLQ Messages
Symptoms
- Dashboard: Row 1 (SQS) shows DLQ depth >0 for reconciliation-worker-dlq
- Alarms:
reconciliation-dlq-alarmtriggered (>5 messages) - Logs: Messages failing after 3 retries
Investigation
See detailed troubleshooting guide: reconciliation/troubleshooting.md#scenario-1-messages-stuck-in-dlq
Quick Diagnosis:
Common Error Patterns:
Resolution
Option A: Fix Root Cause and Re-drive
Option B: Manual Reprocessing
Option C: Delete Invalid Messages
Issue 5: Rate Limit Errors During Reconciliation
Symptoms
- Dashboard: Row 12 Left shows
RateLimitCapacity<3 - Metrics:
RateLimitErrorsincreasing - Logs:
429 Too Many Requestserrors in worker logs - Impact: Reconciliation jobs failing, retrying after 2-10 minutes
Investigation
Step 1: Check Rate Limit Capacity
Step 2: Identify High-Volume Clients
Step 3: Check Concurrency
Root Cause Analysis
Scenario A: Reserved Concurrency Too High
- Symptoms: Multiple workers processing simultaneously → API storm
- Dashboard: ApiCallsPerMinute >10
- Investigation: Check Lambda metrics for concurrent executions
- Fix: Reduce reserved concurrency from 10 → 2
Scenario B: SQS Batch Size Too Large
- Symptoms: Worker processing multiple messages → multiple API calls
- Dashboard: RateLimitCapacity drops sharply during reconciliation runs
- Investigation: Check SQS event source configuration
- Fix: Ensure batch size = 1 (sequential processing)
Scenario C: Normal High Volume (Not an Error)
- Symptoms: RateLimitCapacity fluctuates but >3 most of the time
- Dashboard: Occasional dips to 2-3, then recovers
- Explanation: V2.5.0 wide-spread retry (2-10 min delays) prevents storms
- Fix: No action needed - system is self-regulating
Resolution
Automatic (V2.5.0 Feature):
- Worker extends SQS visibility timeout by 2-10 minutes on 429
- Lambda throws error → SQS retries after random delay
- Prevents thundering herd retry storms
Manual (If persistent):
Preventive:
- Monitor Row 12 Left (RateLimitCapacity) in dashboard
- Set up alarm for RateLimitCapacity <3 for >5 minutes
- Review reserved concurrency settings after client onboarding
Metrics Reference
Reconciliation Case Metrics
ReconciliationCase1
- Namespace:
CallAnalytics/RingCentral - Dashboard: Row 9 Widget 1
- Dimensions:
ClientId - Unit: Count
- Meaning: Calls exist in RingCentral but missing in DynamoDB
- Healthy: 0 or <5 per client
- Warning: >10 per client (backlog forming)
- Critical: >50 per client (major sync gap)
- Troubleshoot: Webhook delivery failure, TranscribeProcessor errors, EventBridge schedule disabled
ReconciliationCase2
- Namespace:
CallAnalytics/RingCentral - Dashboard: Row 9 Widget 2
- Dimensions:
ClientId - Unit: Count
- Meaning: Call in DB but recording not downloaded (>15 minutes)
- Healthy: 0
- Warning: >5 per client
- Critical: >20 per client
- Troubleshoot: RingCentral API errors, network issues, IAM permissions for S3 upload
ReconciliationCase3
- Namespace:
CallAnalytics/RingCentral - Dashboard: Row 9 Widget 3
- Dimensions:
ClientId - Unit: Count
- Meaning: Recording downloaded but transcription not started (>30 minutes)
- Healthy: 0
- Warning: >5 per client
- Critical: >20 per client
- Troubleshoot: AWS Transcribe quota limits, IAM permissions, S3 bucket access
ReconciliationCase4
- Namespace:
CallAnalytics/RingCentral - Dashboard: Row 9 Widget 4
- Dimensions:
ClientId - Unit: Count
- Meaning: Transcription started but not completed (>24 hours)
- Healthy: 0 or <3 per client
- Warning: >5 per client (transcription failures)
- Critical: >15 per client (systemic issue)
- Troubleshoot: AWS Transcribe job failures, audio format issues, service backlog
ReconciliationCaseD
- Namespace:
CallAnalytics/RingCentral - Dashboard: Row 9 Widget 5
- Dimensions:
ClientId - Unit: Count
- Meaning: Record in DynamoDB but deleted from RingCentral (>90 days)
- Healthy: <20 total (gradual accumulation from retention policy)
- Warning: >20 total or sudden spike
- Critical: >50 total (potential account deletion)
- Troubleshoot: RingCentral account status, test data cleanup, retention policy changes
Operational Metrics
ProcessingDuration
- Namespace:
CallAnalytics/RingCentral - Dashboard: Not visualized (use CloudWatch Metrics Explorer)
- Dimensions:
ClientId - Unit: Milliseconds
- Meaning: Time taken to process single reconciliation action
- Healthy: <30,000 ms (30 seconds)
- Warning: >60,000 ms (1 minute)
- Critical: >180,000 ms (3 minutes, approaching timeout)
- Troubleshoot: Slow RingCentral API, large recording downloads, network latency
RateLimitErrors
- Namespace:
CallAnalytics/RingCentral - Dashboard: Not visualized (use CloudWatch Metrics Explorer)
- Dimensions:
ClientId,Operation - Unit: Count
- Meaning: Number of RingCentral 429 rate limit errors
- Healthy: 0
- Warning: >1 per hour (occasional limits)
- Critical: >5 per hour (sustained rate limiting)
- Troubleshoot: High concurrency, large reconciliation windows, insufficient delays
RateLimitCapacity
- Namespace:
CallAnalytics/RingCentral - Dashboard: Row 12 Left
- Dimensions:
Source(webhook vs reconciliation) - Unit: Count (raw remaining requests)
- Meaning: RingCentral API requests remaining in current window
- Healthy: >5
- Warning: <3 (approaching rate limit)
- Critical: <1 (rate limit imminent)
- Troubleshoot: Reduce concurrency, increase delays, disable schedule temporarily
ApiCallsPerMinute
- Namespace:
CallAnalytics/RingCentral - Dashboard: Row 12 Right
- Dimensions:
Source(webhook vs reconciliation) - Unit: Count
- Meaning: RingCentral API calls per minute (CloudWatch sums automatically)
- Healthy: <10 per minute
- Warning: >10 per minute (high load)
- Critical: >20 per minute (rate limit storm risk)
- Troubleshoot: Check reserved concurrency, SQS batch size, reconciliation frequency
CloudWatch Logs Insights Queries
Reconciliation Actions Taken (Last Run)
Purpose: See what actions reconciliation took in last run
Expected Output:
Per-Client Reconciliation Summary
Purpose: Aggregate view of issues per client
Expected Output:
Track State 1 Trend (Missing Calls Over Time)
Purpose: See if State 1 (missing calls) is increasing or decreasing
Expected Output:
Find Rate Limit Errors by Client
Purpose: Identify which clients are hitting rate limits
Expected Output:
Reconciliation Processing Duration by Client
Purpose: Identify slow reconciliation runs
Expected Output:
Find Calls Without Recordings (V2.5.1)
Purpose: Distinguish calls without recordings (not an error)
Expected Output:
Identify DLQ Failure Patterns
Purpose: See why messages are ending up in DLQ
Expected Output:
Compare RingCentral Calls vs DynamoDB Records
Purpose: Identify data sync gaps
Expected Output:
Track Reconciliation Success Rate
Purpose: Overall health check
Expected Output:
Find Stuck Transcription Jobs
Purpose: Identify transcriptions stuck in State 4
Expected Output:
Monitor SQS Visibility Timeout Extensions (429 Handling)
Purpose: See how often reconciliation is hitting rate limits and retrying
Expected Output:
Find Orphaned Records (Case D)
Purpose: Investigate orphaned records for data integrity
Expected Output:
Reconciliation Window Coverage
Purpose: Verify reconciliation window is appropriate
Expected Output:
Action Execution Success/Failure
Purpose: Track action execution outcomes
Expected Output:
SQS Message Processing Metrics
Purpose: Understand message retry patterns
Expected Output:
Operational Procedures
Enable/Disable Reconciliation Schedule
Check Current State
Enable Schedule
When to Enable:
- After successful deployment and testing in dev/test
- After incident resolution when root cause is fixed
- When manual reconciliation is no longer needed
Note: V2.5.3 change - dev/test environments auto-enabled, prod disabled by default
Disable Schedule
When to Disable:
- During major incidents (prevent more failures)
- Before risky deployments (pause reconciliation during change)
- When investigating data integrity issues (prevent auto-fixes)
- During RingCentral API outages (prevent rate limit storms)
Manual Reconciliation Run
Trigger Orchestrator (Scan-Only Mode)
Purpose: See what issues exist without fixing them
Expected Output:
Trigger Orchestrator (Execute Mode)
Purpose: Actually fix detected issues
When to Use:
- After fixing root cause of reconciliation failures
- During off-peak hours (minimize RingCentral API load)
- For emergency data recovery
- When schedule is disabled but issues need fixing
Caution:
- Execute mode creates SQS messages with random delays (2-15 min)
- Actions process asynchronously (check logs for completion)
- Monitor dashboard Row 9 for case count decreases
Monitor Progress
Step 1: Check Orchestrator Completion
Step 2: Check SQS Queue Depth
Step 3: Monitor Dashboard
Adjust Reconciliation Window
Temporary Override (via Environment Variable)
Purpose: Change reconciliation window for single run without code change
When to Use:
- After long outages (need wider window to catch up)
- For one-time historical data recovery
- Testing reconciliation with different windows
Caution:
- Larger windows increase RingCentral API calls (stay under rate limits)
- Maximum window: 90 days (RingCentral retention period)
- Revert to default (6 hours) after temporary use
Permanent Change (via CDK)
Purpose: Change default window for all future runs
-
Edit
lambda/reconciliation-worker/src/core/reconciliation-cases.ts -
Deploy change
-
Verify in logs
When to Use:
- After changing reconciliation frequency (e.g., every 6 hours instead of 3)
- Based on observed data patterns (e.g., calls delayed >6 hours)
- To balance freshness vs API cost
Best Practices:
- Window should be 2x reconciliation frequency (safety margin)
- Don't exceed 24 hours (diminishing returns, higher API cost)
- Test in dev/test before production
View Reconciliation History
Last 10 Runs
Purpose: Quick health check - are reconciliation runs succeeding?
Expected Output:
Trend Analysis (Last 7 Days)
Purpose: Long-term health - is backlog growing or shrinking?
Expected Output:
Success Rate Over Time
Purpose: Reliability check - what percentage of runs succeed?
Expected Output:
Code Reference
Key Files
Orchestrator:
- Entry:
/lambda/reconciliation-orchestrator/src/handler.ts - Config scanner:
/lambda/reconciliation-orchestrator/src/infrastructure/config-scanner.ts - SQS publisher:
/lambda/reconciliation-orchestrator/src/infrastructure/sqs-publisher.ts
Worker:
- Entry:
/lambda/reconciliation-worker/src/handler.ts - State detection:
/lambda/reconciliation-worker/src/core/reconciliation-cases.ts - Reconciliation engine:
/lambda/reconciliation-worker/src/core/reconciliation-engine.ts - Metrics:
/lambda/reconciliation-worker/src/utils/metrics.ts - RingCentral API:
/lambda/reconciliation-worker/src/infrastructure/ringcentral-client.ts
Shared:
- Constants:
/lambda/shared/constants/reconciliation.ts - Models:
/lambda/reconciliation-worker/src/core/models.ts
Critical Code Sections
SQS Fan-Out (Orchestrator handler.ts:64-84)
Purpose: Scan DynamoDB for active clients, publish to SQS
Why It Matters: V2.5.0 architecture - 1 SQS message per client (enables independent retry)
4-State Detection (reconciliation-cases.ts:48-131)
Purpose: Classify call records into States 1-4 or Case D
Why It Matters: Granular state detection enables targeted fixes (e.g., State 2 = download recording, State 3 = start transcription)
Per-Client Metrics (metrics.ts:120-156)
Purpose: Emit metrics with ClientId dimension for per-client visibility
Usage in handler.ts:
Why It Matters: Enables Dashboard Row 9 per-client drill-down
RingCentral 429 Handling (ringcentral-client.ts:99-130)
Purpose: Extend SQS visibility timeout on rate limit, retry with random delay
Why It Matters: V2.5.0 wide-spread retry prevents thundering herd retry storms
Testing & Validation
Test Reconciliation Locally
Purpose: Validate reconciliation logic without deploying
Key Test Files:
/lambda/reconciliation-worker/__tests__/unit/reconciliation-cases.test.ts- State detection logic/lambda/reconciliation-worker/__tests__/unit/reconciliation-engine.test.ts- Action generation/lambda/reconciliation-worker/__tests__/unit/webhook-builder.test.ts- Synthetic webhook creation
Validate State Detection Logic
Purpose: Ensure 4-state model correctly classifies records
Load Testing
Purpose: Validate reconciliation at scale (production-like volume)
Caution: Only run in dev/test environments to avoid production impact
Success Criteria:
- All 5000 calls processed without DLQ messages
- Rate limit capacity stays >3 throughout
- No Lambda errors or throttles
- Completion time <1 hour
Architecture Decisions
Why SQS Fan-Out? (V2.5.0)
Problem (V2.4):
- Orchestrator made 37+ RingCentral API calls per client in single invocation
- Each reconciliation action (State 1-4) triggered immediate API call
- 10 clients × 37 calls = 370 API calls in <60 seconds
- RingCentral rate limit: ~10 calls/minute → 429 errors
Solution (V2.5.0):
- Orchestrator makes 1 API call per client (fetch call logs)
- Generates 1 SQS message per action (e.g., State 1 = inject_webhook)
- Worker processes 1 message at a time (concurrency=2)
- Random delays (2-15 min) spread load over time
Result:
- 97% API call reduction (370 → 10 calls for 10 clients)
- Wide-spread retry prevents thundering herd
- Independent retry (each action can retry without reprocessing all)
- Better observability (per-action metrics, logs)
Trade-off:
- Longer completion time (10-30 min vs <5 min)
- More complex architecture (orchestrator + worker + SQS)
- Acceptable: Reconciliation is background job, not time-critical
Why 4-State Model? (V2.4.1)
Problem (Legacy A/B/C):
- Case B: "Stuck in progress" - didn't distinguish recording vs transcription
- Case C: "Failed transcription" - overlapped with Case B
- Hard to troubleshoot: Is recording missing? Or transcription stuck?
Solution (State 1-4):
- State 1: Missing in DB (was Case A) - unchanged
- State 2: Recording not downloaded (new) - network/S3 issue
- State 3: Transcription not started (new) - Transcribe quota/permissions
- State 4: Transcription stuck (merged B+C) - job failure/timeout
- Case D: Orphaned (unchanged) - RingCentral deleted
Result:
- Granular troubleshooting (State 2 = download issue, State 3 = Transcribe issue)
- Better dashboard visibility (5 widgets instead of 4)
- Targeted fixes (State 2 = retry download, State 3 = start transcription)
- Easier metrics (one metric per state)
Migration:
- Code updated: V2.4.1 (November 28, 2025)
- Backward compatible: Old metrics still work, new metrics added
- Dashboard updated: Row 9 shows 5 states (1-4 + D)
Why 6-Hour Window? (V2.4)
Problem (7-Day Window):
- Reconciliation runs every 3 hours
- 7-day window = 56 overlapping scans (7 days ÷ 3 hours)
- Each scan fetches same calls 56 times (99.98% duplication!)
- RingCentral API cost: 56× necessary calls
Solution (6-Hour Window):
- 6-hour window = 2 overlapping scans (6 hours ÷ 3 hours)
- Each call fetched 2 times (50% duplication for safety margin)
- 2× processing SLA headroom (normal: 3 hours, window: 6 hours)
Result:
- 50% API call reduction vs 7-day window
- Faster issue detection (catches problems within 6 hours, not 7 days)
- Lower RingCentral API cost
- Still safe: 2× headroom for delayed processing
Trade-off:
- Misses calls >6 hours delayed (rare: <0.1% based on metrics)
- Can increase window to 12/24 hours for historical recovery
- Acceptable: Focus on recent calls, not historical data
Why Concurrency = 2? (V2.5.0)
Problem (No Reserved Concurrency):
- Lambda auto-scales based on SQS queue depth
- 100 messages → 100 concurrent Lambdas
- Each Lambda makes RingCentral API calls
- 100 Lambdas × 5 calls = 500 calls/minute → 429 storm
Solution (Reserved Concurrency = 2):
- Maximum 2 Lambdas processing simultaneously
- Each Lambda processes 1 message at a time (SQS batch size = 1)
- 2 Lambdas × 5 calls = 10 calls/minute (well under rate limit)
- SQS queue builds up, but processes slowly and safely
Result:
- Prevents rate limit storms
- Stable API call rate (~10 calls/minute)
- Queue eventually drains (2 workers × 15 min timeout = 8 messages/hour)
- Better than concurrency=1 (too slow) or concurrency=10 (rate limit risk)
Trade-off:
- Slower processing (100 messages = 50 hours vs 1 hour)
- Acceptable: Reconciliation is background job, correctness > speed
Related Documentation
- Troubleshooting: reconciliation/troubleshooting.md - Detailed DLQ recovery, IAM issues, secrets management
- Dashboard: ../01-MONITORING/README.md - Complete dashboard reference (all 13 rows)
- Alerts: ../02-ALERTING/README.md - Reconciliation-related alarms (DLQ, errors, rate limits)
- TranscribeProcessor: ./lambda/transcribe-processor.md - Primary data path, webhook processing
- On-Call Guide: ../04-INCIDENT-RESPONSE/on-call-guide.md - Incident #4: Reconciliation failures
Changelog
V2.5.3 (2025-11-30): Auto-enable reconciliation schedule in dev/test
- Schedule state: Auto-enabled (dev/test), Disabled (prod)
- Eliminates manual enable step after deployment
- Updated operational procedures for schedule management
V2.5.0 (2025-11-28): SQS fan-out architecture
- 97% API call reduction (37+ calls → 1 per client)
- Wide-spread retry (2-15 min random delays)
- Independent action retry via SQS
- Reserved concurrency = 2 (prevents rate limit storms)
V2.4.1 (2025-11-28): 4-state reconciliation model
- New states: State 2 (recording not downloaded), State 3 (transcription not started)
- Merged Case B + C → State 4 (transcription stuck)
- Per-client metrics with ClientId dimension
- Dashboard Row 9 added (5 widgets for states 1-4 + D)
V2.4 (2025-11-28): Schedule optimization
- Frequency: Daily → Every 3 hours
- Window: 7 days → 6 hours
- RingCentral API pagination support
V2.3 (2025-11-27): Metrics namespace change
- Namespace:
CallAnalytics→CallAnalytics/RingCentral - Added
ApiCallsPerMinutemetric
V2.2.5 (2025-11-24): Secrets Manager IAM fix
- IAM policy supports 3 secret naming patterns
- Uses
secret_namefrom DynamoDB config
Document Version: 1.0 Last Updated: 2025-11-30 Author: Engineering Team Review Frequency: Quarterly