// 2026年3月26日
VS Code 使用统计系统:WebSocket + ClickHouse 全链路
需求
Qoder 是一款基于 VS Code 的 AI 代码编辑器,需要统计使用情况:谁在用、用了多久、AI 功能使用频率。现有方案没有,从零搭。
架构
三个服务 + 一个 VS Code 扩展,monorepo 管理:
VS Code 扩展(数据采集)
↓ WebSocket
收集器服务(实时接收 + 批量写入)
↓
ClickHouse(时序存储) + PostgreSQL(元数据)
↓
MCP 服务器(查询接口,给 AI 编辑器用)
VS Code 扩展:增量数据同步
扩展从 Qoder 本地 SQLite 数据库读取会话数据,增量同步到服务端:
// 记录上次同步位置,只发增量数据
const lastSyncId = context.globalState.get('lastSyncId', 0);
const newSessions = await queryLocalDb(
'SELECT * FROM sessions WHERE id > ? ORDER BY id',
[lastSyncId]
);
if (newSessions.length > 0) {
ws.send(JSON.stringify({ type: 'sync', data: newSessions }));
await context.globalState.update('lastSyncId', newSessions[newSessions.length - 1].id);
}
关键点:增量同步避免重复传输,断线重连后从上次位置继续。
WebSocket 收集器:批量写入优化
直接逐条写 ClickHouse 性能很差,用 batch-writer 攒一批再写:
class BatchWriter {
private buffer: Record[] = [];
private timer: NodeJS.Timeout | null = null;
add(record: Record) {
this.buffer.push(record);
// 攒够 1000 条或 5 秒超时,批量写入
if (this.buffer.length >= 1000) {
this.flush();
} else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), 5000);
}
}
async flush() {
const batch = this.buffer.splice(0);
if (this.timer) clearTimeout(this.timer);
this.timer = null;
if (batch.length > 0) {
await clickhouse.insert(batch);
}
}
}
两个触发条件取其先:数量满 1000 条或时间满 5 秒。保证大流量时高吞吐,低流量时不丢数据。
消息去重与校验
WebSocket 不保证 exactly-once,需要在收集器端去重:
// 用 sessionId + timestamp 作为去重键
const dedupeKey = `${msg.sessionId}:${msg.timestamp}`;
if (seen.has(dedupeKey)) return; // 跳过重复
seen.add(dedupeKey);
同时用 Zod 做消息格式校验,畸形数据直接丢弃不入库。
MCP 查询接口
给 AI 编辑器提供查询能力,通过 API Key 鉴权:
// API Key 用 SHA-256 哈希存储,不存明文
const hashedKey = crypto.createHash('sha256')
.update(apiKey)
.digest('hex');
const record = await db.query(
'SELECT * FROM api_keys WHERE key_hash = $1',
[hashedKey]
);
通过 MCP 协议暴露 ClickHouse 和 PostgreSQL 的查询接口,AI 可以直接分析使用数据。
部署
Docker Compose 一键拉起全部服务:
services:
collector:
build: ./packages/collector
ports: ["8080:8080"]
clickhouse:
image: clickhouse/clickhouse-server
volumes: ["./data/clickhouse:/var/lib/clickhouse"]
postgres:
image: postgres:16
volumes: ["./data/postgres:/var/lib/postgresql/data"]
mcp-server:
build: ./packages/mcp-server
ports: ["3001:3001"]
总结
这个项目最有价值的经验:
- WebSocket + 批量写入是时序数据采集的经典模式,简单有效
- 增量同步比全量同步节省 90%+ 的带宽
- API Key 哈希存储是基本安全素养,不要存明文
- **Monorepo(npm workspace)**管理多个关联服务很方便,共享类型定义和工具函数