文档 11 / 28

数据库设计文档

PostgreSQL · Redis · COS · ClickHouse · 数据模型 · 索引策略

册别:第三册 · 技术与架构 版本:V2.0 日期:2026-08-17 状态:国内版

一、数据库总览

存储用途数据保留部署
PostgreSQL(云数据库)业务核心数据(用户/通话/支付/社交)持久(90 天归档)腾讯云广州(双可用区)
Redis Cluster热数据/缓存/匹配池/计数器TTL 自动过期腾讯云 Redis 主备
COS静态资源/头像/录音归档180 天(合规)腾讯云 COS + CDN
ClickHouse数据分析/BI/模型训练无限(分区归档)腾讯云数仓
CKafka消息队列/事件流1-7 天腾讯云 CKafka

1.1 数据分类与合规

数据类别示例存储位置保留期加密
个人身份信息 (PII)手机号/邮箱/真实姓名PostgreSQL(云数据库)账号存续期AES-256(腾讯云 KMS)
语音数据通话音频不存储(仅实时流)0—
文本数据字幕/消息/翻译PG + COS 归档90 天AES-256
支付数据订单/交易/订阅PG (PCI-DSS)7 年(税务)AES-256 + Tokenization
行为数据点击/浏览/事件ClickHouse2 年脱敏后
审核数据举报/标记/裁决PG + COS3 年AES-256

二、PostgreSQL 核心表设计

2.1 users — 用户主表

CREATE TABLE users ( user_id VARCHAR(32) PRIMARY KEY, -- usr_xxx phone VARCHAR(20) UNIQUE, email VARCHAR(100), nickname VARCHAR(30) NOT NULL, avatar_url TEXT, mother_tongue VARCHAR(10) NOT NULL, -- zh-CN / en-US target_language VARCHAR(10) NOT NULL, gender VARCHAR(10), -- male/female/other/prefer_not_say birth_year SMALLINT, country_code VARCHAR(5) DEFAULT 'CN', vip_status VARCHAR(20) DEFAULT 'none', -- none/active/expired vip_expire_at BIGINT, coins_balance INTEGER DEFAULT 0, free_calls_left SMALLINT DEFAULT 10, free_calls_reset_at BIGINT, rating_avg DECIMAL(3,1) DEFAULT 5.0, total_calls INTEGER DEFAULT 0, trust_score SMALLINT DEFAULT 100, is_active BOOLEAN DEFAULT TRUE, is_banned BOOLEAN DEFAULT FALSE, ban_reason VARCHAR(100), device_ids JSONB DEFAULT '[]', created_at BIGINT NOT NULL, updated_at BIGINT NOT NULL, deleted_at BIGINT -- 软删除时间戳 ); CREATE INDEX idx_users_lang ON users(mother_tongue, target_language); CREATE INDEX idx_users_online ON users(is_active) WHERE is_active = TRUE; CREATE INDEX idx_users_vip ON users(vip_status, vip_expire_at);

2.2 calls — 通话记录

CREATE TABLE calls ( call_id VARCHAR(32) PRIMARY KEY, -- call_xxx caller_id VARCHAR(32) NOT NULL REFERENCES users(user_id), callee_id VARCHAR(32) NOT NULL REFERENCES users(user_id), start_time BIGINT NOT NULL, end_time BIGINT, duration_seconds INTEGER DEFAULT 0, caller_language VARCHAR(10), callee_language VARCHAR(10), cost_coins INTEGER DEFAULT 0, earner_coins INTEGER DEFAULT 0, caller_rating SMALLINT, -- 1-5 callee_rating SMALLINT, end_reason VARCHAR(20), -- normal_hangup/timeout/error/report has_translation BOOLEAN DEFAULT TRUE, avg_latency_ms INTEGER, quality_score DECIMAL(3,1), created_at BIGINT NOT NULL ) PARTITION BY RANGE (start_time); -- 按月分区(自动创建) CREATE INDEX idx_calls_user_time ON calls(caller_id, start_time DESC); CREATE INDEX idx_calls_callee_time ON calls(callee_id, start_time DESC);

2.3 translations — 翻译记录

CREATE TABLE translations ( translation_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, call_id VARCHAR(32) NOT NULL REFERENCES calls(call_id), seq INTEGER NOT NULL, speaker_id VARCHAR(32) NOT NULL, source_lang VARCHAR(10) NOT NULL, target_lang VARCHAR(10) NOT NULL, source_text TEXT NOT NULL, translated_text TEXT, asr_confidence DECIMAL(4,3), translate_confidence DECIMAL(4,3), latency_ms INTEGER, engine VARCHAR(30), -- baidu_translate/iflytek/self_hosted was_cached BOOLEAN DEFAULT FALSE, is_flagged BOOLEAN DEFAULT FALSE, created_at BIGINT NOT NULL, UNIQUE(call_id, seq, speaker_id) ) PARTITION BY RANGE (created_at); CREATE INDEX idx_translations_call ON translations(call_id, seq); CREATE INDEX idx_translations_flagged ON translations(is_flagged) WHERE is_flagged = TRUE;

2.4 payments — 支付订单

CREATE TABLE payments ( payment_id VARCHAR(32) PRIMARY KEY, user_id VARCHAR(32) NOT NULL REFERENCES users(user_id), type VARCHAR(20) NOT NULL, -- coin_purchase/vip_subscription/gift amount_cny DECIMAL(10,2) NOT NULL, coins_amount INTEGER, channel VARCHAR(20) NOT NULL, -- wechat/alipay/apple_iap/huawei channel_tx_id VARCHAR(100), status VARCHAR(20) NOT NULL, -- pending/completed/failed/refunded failure_reason VARCHAR(200), receipt_data TEXT, -- 苹果/厂商商店回执(加密) metadata JSONB, created_at BIGINT NOT NULL, completed_at BIGINT, CHECK(amount_cny >= 0) ); CREATE INDEX idx_payments_user ON payments(user_id, created_at DESC); CREATE INDEX idx_payments_status ON payments(status, created_at);

2.5 subscriptions — VIP 订阅

CREATE TABLE subscriptions ( subscription_id VARCHAR(32) PRIMARY KEY, user_id VARCHAR(32) NOT NULL REFERENCES users(user_id), plan VARCHAR(20) NOT NULL, -- vip_week/vip_month/vip_year status VARCHAR(20) NOT NULL, -- active/cancelled/expired/past_due start_date BIGINT NOT NULL, end_date BIGINT NOT NULL, auto_renew BOOLEAN DEFAULT TRUE, channel VARCHAR(20), original_transaction_id VARCHAR(100), created_at BIGINT NOT NULL, updated_at BIGINT NOT NULL ); CREATE INDEX idx_subs_user ON subscriptions(user_id, status); CREATE INDEX idx_subs_expire ON subscriptions(end_date) WHERE status = 'active';

2.6 buddies — 语伴关系

CREATE TABLE buddies ( buddy_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, user_a_id VARCHAR(32) NOT NULL REFERENCES users(user_id), user_b_id VARCHAR(32) NOT NULL REFERENCES users(user_id), status VARCHAR(20) DEFAULT 'active', -- active/blocked/muted is_favorite BOOLEAN DEFAULT FALSE, total_calls INTEGER DEFAULT 0, last_call_at BIGINT, created_at BIGINT NOT NULL, CHECK(user_a_id < user_b_id), -- 保证唯一对 UNIQUE(user_a_id, user_b_id) ); CREATE INDEX idx_buddies_user ON buddies(user_a_id, status);

2.7 messages — 私聊消息

CREATE TABLE messages ( message_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, sender_id VARCHAR(32) NOT NULL REFERENCES users(user_id), receiver_id VARCHAR(32) NOT NULL REFERENCES users(user_id), type VARCHAR(10) DEFAULT 'text', -- text/gift/system content TEXT, translated_content TEXT, is_read BOOLEAN DEFAULT FALSE, is_flagged BOOLEAN DEFAULT FALSE, created_at BIGINT NOT NULL ) PARTITION BY RANGE (created_at); CREATE INDEX idx_messages_conv ON messages(sender_id, receiver_id, created_at DESC); CREATE INDEX idx_messages_unread ON messages(receiver_id, is_read) WHERE is_read = FALSE;

2.8 reports — 举报记录

CREATE TABLE reports ( report_id VARCHAR(32) PRIMARY KEY, reporter_id VARCHAR(32) NOT NULL REFERENCES users(user_id), reported_user_id VARCHAR(32) NOT NULL REFERENCES users(user_id), call_id VARCHAR(32) REFERENCES calls(call_id), reason VARCHAR(30) NOT NULL, description TEXT, severity VARCHAR(10) DEFAULT 'medium', -- low/medium/high/critical status VARCHAR(20) DEFAULT 'pending', -- pending/reviewing/resolved/dismissed resolution VARCHAR(50), -- user_warned/user_banned/false_positive moderator_id VARCHAR(32), evidence JSONB, created_at BIGINT NOT NULL, resolved_at BIGINT ); CREATE INDEX idx_reports_pending ON reports(status, severity, created_at) WHERE status = 'pending'; CREATE INDEX idx_reports_user ON reports(reported_user_id, status);

2.9 gifts — 礼物记录

CREATE TABLE gifts ( gift_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, from_user_id VARCHAR(32) NOT NULL REFERENCES users(user_id), to_user_id VARCHAR(32) NOT NULL REFERENCES users(user_id), gift_type VARCHAR(20) NOT NULL, -- flower/applause/melody/crown/heart coins_cost INTEGER NOT NULL, call_id VARCHAR(32) REFERENCES calls(call_id), is_anonymous BOOLEAN DEFAULT FALSE, created_at BIGINT NOT NULL ); CREATE INDEX idx_gifts_receiver ON gifts(to_user_id, created_at DESC);

2.10 ad_events — 广告事件

CREATE TABLE ad_events ( event_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, user_id VARCHAR(32) NOT NULL REFERENCES users(user_id), ad_type VARCHAR(20) NOT NULL, -- rewarded_video/banner event_type VARCHAR(20) NOT NULL, -- impression/click/reward/dismiss/error provider VARCHAR(20), -- csj(穿山甲)/ylh(优量汇) ad_unit_id VARCHAR(50), reward_granted BOOLEAN DEFAULT FALSE, reward_coins INTEGER DEFAULT 0, error_code VARCHAR(20), created_at BIGINT NOT NULL ) PARTITION BY RANGE (created_at); CREATE INDEX idx_ads_user_daily ON ad_events(user_id, event_type, created_at DESC);

三、Redis 数据结构

Key 模式类型用途TTL
user:<uid>:sessionHash用户会话/Token7 天
match:pool:<lang>Sorted Set匹配池(score=rating)无(实时维护)
match:searching:<uid>String用户搜索状态60 秒
call:<cid>:participantsSet通话参与者通话结束清除
call:<cid>:qualityHash实时质量指标通话结束清除
translate:cache:<hash>String翻译结果缓存24 小时
user:<uid>:ads:todayString (counter)今日广告次数到次日 0 点
user:<uid>:rate_limitString (counter)API 限流计数60 秒
config:feature_flagsHashFeature Flag 全局配置无(手动刷新)
stats:online_countString全站在线人数无(实时更新)

3.1 匹配池 Sorted Set 设计

# 加入匹配池 ZADD match:pool:zh-CN <rating_score> <user_id> ZADD match:pool:en-US <rating_score> <user_id> # 查找匹配(按评分邻近) ZRANGEBYSCORE match:pool:en-US <min_score> <max_score> LIMIT 0 10 # 移除(匹配成功/取消/离线) ZREM match:pool:zh-CN <user_id> # 过期清理(定时任务每 30s) ZREMRANGEBYSCORE match:pool:zh-CN -inf <current_time - 60s>

四、分区与归档策略

4.1 表分区规则

表名分区键分区粒度保留期归档目标
callsstart_time按月12 个月热 + 无限冷COS Parquet
translationscreated_at按月3 个月热 + 6 个月温COS Parquet
messagescreated_at按月6 个月COS (加密)
ad_eventscreated_at按月6 个月ClickHouse
reportscreated_at按月3 年COS (WORM)

4.2 自动归档脚本

-- 每月 1 号 03:00 执行(pg_cron) SELECT create_archive_partition( p_table_name := 'calls', p_month := DATE_TRUNC('month', NOW() - INTERVAL '3 months') ); -- 流程: -- 1. 将 3 个月前的分区数据导出为 Parquet → COS -- 2. 验证 COS 数据完整性(行数 + checksum) -- 3. DROP 原分区 -- 4. 创建 FOREIGN TABLE 指向 COS(仍可查询)

4.3 数据生命周期

数据类型热存储温存储冷存储删除
通话记录PG 分区(0-3 月)COS Parquet(3-12 月)归档存储(1-3 年)3 年后
翻译文本PG 分区(0-1 月)COS(1-6 月)归档存储(6-12 月)1 年后
私聊消息PG 分区(0-6 月)COS(6-12 月)—1 年后
支付记录PG(永久)—归档存储(7 年税务)7 年后
审核日志PG(0-3 月)COS WORM(3-36 月)归档存储(3-10 年)10 年后

五、性能与容量规划(M1)

5.1 容量估算

表日增量月增量年增量存储/年
users~500~1.5 万~18 万~50 MB
calls~3 万~90 万~1080 万~5 GB
translations~150 万~4500 万~5.4 亿~250 GB
messages~2 万~60 万~720 万~3 GB
ad_events~60 万~1800 万~2.16 亿~80 GB
reports~50~1500~1.8 万~10 MB
合计———~340 GB/年

5.2 数据库实例规格(腾讯云 PostgreSQL 云数据库,人民币)

环境实例规格存储CPU/RAM月费(¥)
DevPG 基础版50 GB SSD2 vCPU / 4 GB~¥500
StagingPG 高可用版100 GB SSD2 vCPU / 8 GB~¥1,000
ProductionPG 高可用版(主)+ 只读副本500 GB SSD4 vCPU / 16 GB + 2/4~¥3,500
Production (P2)PG 高可用版(主)+ 只读 ×21000 GB8 vCPU / 32 GB~¥8,000

注:以上为腾讯云 PostgreSQL 云数据库参考月费(含备份与高可用)。M1-M2 用 Dev/Staging 档即可,Production 规格随 DAU 阶梯在 M3-M4 逐步升级(与文档 08 技术架构一致)。

5.3 关键查询优化

查询场景索引策略预期耗时
用户通话历史idx_calls_user_time (caller_id, start_time DESC)< 50ms
匹配池查找Redis Sorted Set(非 PG)< 5ms
未读消息数Partial Index (is_read=false)< 10ms
待处理举报Partial Index (status=pending)< 20ms
翻译质量分析PG + ClickHouse 外部表异步批处理
在线用户统计Redis HyperLogLog< 1ms

六、安全与合规

6.1 加密策略

数据加密方式密钥管理
手机号AES-256(列级加密)腾讯云 KMS
邮箱AES-256(列级加密)腾讯云 KMS
支付回执AES-256 + Tokenization腾讯云 KMS + 加密机 HSM
翻译文本TLS 传输 + AES-256 存储腾讯云 KMS
备份AES-256(备份文件加密)腾讯云 KMS

6.2 备份策略

类型频率保留恢复测试
自动快照每 6 小时7 天每周恢复演练
每日全量每天 03:0030 天每两周
每周归档每周日1 年每月
PITR持续(WAL)35 天—

6.3 个人信息保护法合规映射

个保法条款数据库实现
第 13/14 条(同意)users 表 consent_version + consent_date 字段
第 6 条(最小化)仅收集必要字段;address/birthdate 可选
第 45 条(查阅复制)GET /auth/data-export 接口 → 导出全部个人数据
第 47 条(删除)DELETE /auth/account → 30 天后匿名化处理
第 19 条(保存期限)分区 + 自动归档 + 生命周期策略
第 38 条(跨境提供)数据本地化:全部存储于境内数据中心;不向境外提供个人信息

七、数据库迁移管理

7.1 迁移工具

工具用途
pg_dump / pg_restore环境间数据迁移(dev→staging→prod)
Flyway版本化 SQL 迁移脚本(V1__init.sql, V2__add_index.sql)
pg_cron定时任务(归档/统计/清理)
pg_partman自动分区管理(按月创建/清理)

7.2 初始化脚本清单

migrations/ ├── V1__init_schema.sql -- 所有 CREATE TABLE ├── V2__create_indexes.sql -- 所有索引 ├── V3__seed_countries.sql -- 国家/语言种子数据 ├── V4__create_partitions.sql -- 分区表初始化 ├── V5__setup_pgcron.sql -- 定时任务 ├── V6__encrypt_existing.sql -- 列加密迁移 └── V7__add_feature_flags.sql -- 功能开关种子

7.3 数据脱敏规则

字段脱敏方式示例
手机号中间 4 位 → ***+86 1***4567
邮箱@ 前部分脱敏a***@gmail.com
昵称不脱敏(公开信息)Alex
翻译文本非 PII,不脱敏—
IP 地址末段清零192.168.1.0

附录

附录 A:ER 图核心关系

users 1──┐ │ ├──< calls >──┐ │ │ users 2──┘ ├──< translations │ └──< gifts users 1──< buddies >── users 2 (self-referencing) users 1──< messages >── users 2 users ──< payments users ──< subscriptions users ──< reports (as reporter) users ──< reports (as reported) users ──< ad_events

附录 B:相关文档

文档关联内容
08 系统架构 HLD数据层在整体架构中的位置
09 实时翻译子系统 LLDtranslations 表的写入频率/格式
13 支付中台接入方案payments/subscriptions 详细设计

附录 C:修订记录

版本日期修改内容作者
V1.02026-08-15初始版本(新加坡版)DBA 团队
V2.02026-08-17重写为国内版:存储底座对齐腾讯云(广州双可用区);脱敏示例改为 +86 手机号DBA 团队