本程序 (ex_insert_delete_history.c) 演示了在 WiredTiger 中通过持续的插入-删除操作,如何产生历史版本数据并存储在 WiredTigerHS.wt (History Store) 中。
这模拟了 MongoDB 启用 minSnapshotHistoryWindowInSeconds 功能后的行为。
-
生成复合Key:
- 格式:
aaaaaaaaaaaaaaaa_X_bbbbbbbbbbbbbbbb_Y_cccccccccccccccc_Z X: 随机取值 1-1000Y: 随机取值 1-2000Z: 单调递增(从1开始)
- 格式:
-
插入-删除循环:
- 每次循环:先INSERT(空值),然后立即DELETE
- 每个操作都使用独立的时间戳
- 使用事务确保一致性
-
历史窗口管理(模拟 minSnapshotHistoryWindowInSeconds):
- 维持约60秒的历史快照窗口
- 定期推进
oldest_timestamp - 保留最近60秒内的所有历史版本
-
监控和统计:
- 实时显示文件大小变化
- 显示 History Store 统计信息
- 演示历史清理过程
wiredtiger-11.3.1/examples/c/
├── ex_insert_delete_history.c # 主程序源码
├── build_and_run_insert_delete.sh # 编译和运行脚本
└── wt_insert_delete_db/ # 程序生成的数据库目录
├── WiredTigerHS.wt # History Store 文件
├── insert_delete_test.wt # 主表文件
└── ... # 其他 WiredTiger 文件
cd /data/reading-and-annotate-wiredtiger-11.3.1/wiredtiger-11.3.1/examples/c
# 赋予执行权限
chmod +x build_and_run_insert_delete.sh
# 编译并运行
./build_and_run_insert_delete.shcd /data/reading-and-annotate-wiredtiger-11.3.1/wiredtiger-11.3.1/examples/c
# 编译
gcc -o ex_insert_delete_history \
ex_insert_delete_history.c \
-I../../ \
-L../../.libs \
-lwiredtiger \
-lpthread \
-Wall \
-O2
# 设置库路径
export LD_LIBRARY_PATH=../../.libs:$LD_LIBRARY_PATH
# 运行
./ex_insert_delete_history========================================
WiredTiger Insert-Delete History Demo
========================================
Configuration:
- Operation count: 10000
- Snapshot window: 60 seconds
- Checkpoint interval: every 100 operations
- Database directory: wt_insert_delete_db
- Key format: aaaaaaaaaaaaaaaa_X_bbbbbbbbbbbbbbbb_Y_cccccccccccccccc_Z
Step 3: Starting insert-delete operations (10000 iterations)...
Completed 100 insert-delete cycles (z_counter: 100, timestamp: 201)
[Progress Update]
WiredTigerHS.wt (History Store): 24.5 KB
insert_delete_test.wt (Table): 16.0 KB
Total: 40.5 KB
[Timestamp Management] Advanced oldest_timestamp to 201
(keeping ~60 second history window)
程序会在不同阶段显示文件大小:
- Initial State: 初始状态(几乎为空)
- Progress Update: 每100次操作后的中间状态
- After All Operations: 所有操作完成后
- After History Cleanup: 清理历史后
- After Compact: 压缩后
- Final State: 最终状态
History Store Statistics:
history store operation successful: 1234
history store table insert calls: 5678
history store table size in bytes: 524288
...
/* 插入操作的时间戳 */
current_ts++;
commit_timestamp = current_ts // 例如: 101
stable_timestamp = current_ts // 推进到 101
/* 删除操作的时间戳 */
current_ts++;
commit_timestamp = current_ts // 例如: 102
stable_timestamp = current_ts // 推进到 102
/* oldest_timestamp 管理 */
if (current_ts > 6000) {
oldest_timestamp = current_ts - 6000 // 保持60秒窗口
}时间戳关系:
oldest_timestamp≤stable_timestamp≤commit_timestamp- 历史窗口 =
stable_timestamp - oldest_timestamp
每个INSERT-DELETE循环会产生以下历史:
时间轴:
T1: INSERT key=xxx, value="" [timestamp=101]
T2: DELETE key=xxx [timestamp=102]
在 History Store 中:
- 保存了 key=xxx 在时间戳101的INSERT版本
- 保存了 key=xxx 在时间戳102的DELETE标记
MongoDB 的 minSnapshotHistoryWindowInSeconds 确保至少保留N秒的历史:
// MongoDB 配置
db.adminCommand({
setParameter: 1,
minSnapshotHistoryWindowInSeconds: 60
})我们的程序模拟方式:
#define SNAPSHOT_WINDOW_SECONDS 60
/*
* 假设: 100 个 timestamp = 1 秒逻辑时间
* 则 60 秒 = 6000 个 timestamp
*/
if (current_ts > 6000) {
oldest_ts = current_ts - 6000; // 保持60秒窗口
}void generate_key(char *buffer, size_t buffer_size, uint64_t z_counter) {
int x = (rand() % 1000) + 1; // X: 1-1000
int y = (rand() % 2000) + 1; // Y: 1-2000
snprintf(buffer, buffer_size,
"aaaaaaaaaaaaaaaa_%d_bbbbbbbbbbbbbbbb_%d_cccccccccccccccc_%lu",
x, y, z_counter);
}示例Key:
aaaaaaaaaaaaaaaa_523_bbbbbbbbbbbbbbbb_1847_cccccccccccccccc_1
aaaaaaaaaaaaaaaa_89_bbbbbbbbbbbbbbbb_356_cccccccccccccccc_2
aaaaaaaaaaaaaaaa_742_bbbbbbbbbbbbbbbb_1203_cccccccccccccccc_3
...
修改 OPERATION_COUNT 常量:
#define OPERATION_COUNT 10000 // 改为你想要的次数修改 SNAPSHOT_WINDOW_SECONDS:
#define SNAPSHOT_WINDOW_SECONDS 60 // 改为你想要的秒数#define CHECKPOINT_INTERVAL 100 // 每N次操作做checkpoint在 generate_key() 函数中自定义:
/* 示例:更短的key */
snprintf(buffer, buffer_size, "key_%d_%d_%lu", x, y, z_counter);
/* 示例:更长的key */
snprintf(buffer, buffer_size,
"prefix_very_long_aaaaaaaaaaa_%d_middle_bbbbbbbbbb_%d_suffix_cccccc_%lu",
x, y, z_counter);# 实时监控文件大小
watch -n 1 'ls -lh wt_insert_delete_db/WiredTigerHS.wt'
# 或使用程序内置的监控(每100次操作报告一次)cd wt_insert_delete_db
# 列出所有表
wt list
# 验证数据完整性
wt verify table:insert_delete_test
# 查看统计信息
wt stat
# 查看 History Store 的详细信息
wt stat file:WiredTigerHS.wt# 使用 wt dump 命令
wt dump -x table:insert_delete_test
# 查看 History Store 内容(需要特殊权限)
wt dump file:WiredTigerHS.wt| 特性 | 本程序 | MongoDB |
|---|---|---|
| 历史存储 | WiredTigerHS.wt | WiredTigerHS.wt |
| 快照窗口 | SNAPSHOT_WINDOW_SECONDS | minSnapshotHistoryWindowInSeconds |
| 时间戳管理 | oldest/stable/commit | 相同 |
| 插入-删除 | 显式循环 | 应用层操作 |
| 方面 | 本程序 | MongoDB |
|---|---|---|
| 时间映射 | 简化的时间戳(100ts=1秒) | 实际系统时间 |
| 并发控制 | 单线程 | 多线程/多会话 |
| Value大小 | 空字符串 | 实际文档数据 |
| 复杂度 | 演示级别 | 生产级别 |
/* 生成复合key */
void generate_key(char *buffer, size_t buffer_size, uint64_t z_counter);
/* 管理 oldest_timestamp */
void manage_oldest_timestamp(WT_CONNECTION *conn, ...);
/* 获取文件大小 */
long get_file_size(const char *filename);
/* 打印文件大小信息 */
void print_file_sizes(const char *phase);
/* 主函数 */
int main(int argc, char *argv[]);1. 初始化
├─ 打开数据库连接
├─ 创建表
└─ 打开游标
2. 插入-删除循环 (N次)
├─ 生成Key
├─ INSERT 操作 (timestamp T)
├─ DELETE 操作 (timestamp T+1)
├─ 管理 oldest_timestamp
└─ 定期 checkpoint
3. 清理演示
├─ 推进 oldest_timestamp
├─ 执行 checkpoint
└─ 压缩 History Store
4. 统计和报告
├─ 显示文件大小
└─ 显示 History Store 统计
解决:
# 确保在正确的目录
cd /data/reading-and-annotate-wiredtiger-11.3.1/wiredtiger-11.3.1/examples/c
# 确保 WiredTiger 已编译
cd ../../
make解决:
export LD_LIBRARY_PATH=/data/reading-and-annotate-wiredtiger-11.3.1/wiredtiger-11.3.1/.libs:$LD_LIBRARY_PATH可能原因:
oldest_timestamp没有正确设置- Checkpoint 频率太低
- 操作次数太少
解决:
- 增加
OPERATION_COUNT - 减小
CHECKPOINT_INTERVAL - 检查
manage_oldest_timestamp()逻辑
优化建议:
- 减少
OPERATION_COUNT - 增大
CHECKPOINT_INTERVAL - 调整 cache_size 配置
创建多个线程同时执行插入-删除:
pthread_t threads[4];
for (int i = 0; i < 4; i++) {
pthread_create(&threads[i], NULL, insert_delete_worker, ...);
}char large_value[1024];
memset(large_value, 'x', sizeof(large_value) - 1);
large_value[sizeof(large_value) - 1] = '\0';
cursor->set_value(cursor, large_value);/* 使用实际系统时间 */
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
uint64_t timestamp = ts.tv_sec * 1000000000 + ts.tv_nsec;/* 在指定时间戳读取数据 */
snprintf(config, sizeof(config), "read_timestamp=%lx", old_ts);
session->begin_transaction(session, config);
cursor->search(cursor);
/* ... 读取旧版本数据 ... */
session->commit_transaction(session, NULL);通过本程序,您应该理解:
- ✅ WiredTiger 如何存储历史版本
- ✅ History Store (WiredTigerHS.wt) 的作用
- ✅ 时间戳 (oldest/stable/commit) 的管理
- ✅ INSERT-DELETE 操作如何产生历史
- ✅ minSnapshotHistoryWindowInSeconds 的工作原理
- ✅ 历史数据的清理机制
如有问题或建议,请参考:
- WiredTiger 官方文档: https://source.wiredtiger.com/
- MongoDB 文档: https://docs.mongodb.com/
- WiredTiger History Store 深度分析
- ex_lijie_history.c - UPDATE 操作的历史存储演示
- WiredTiger Architecture Guide