CouchDB复制时删除本地修改:主库文档缺失时本地文档如何自动删除?
嘿,这个需求我之前帮朋友处理过,CouchDB默认的复制机制确实不会主动删除那些主库已经移除但本地还存在的文档,特别是当本地文档有过修改的时候。下面给你分情况讲清楚怎么实现:
一、先搞懂默认复制的行为
首先得明确:如果本地文档和主库完全同步过,之后主库删除了这个文档,CouchDB会生成一个「墓碑文档」(带有_deleted: true标记),默认的一次性复制会把这个墓碑同步到本地,自动标记本地文档为删除——这种情况不用额外操作。
但如果本地文档被用户修改过,主库又删除了它,这时候就会产生冲突:本地有一个活跃的修订版,主库是删除的修订版,默认复制不会直接删除本地的修改版,这时候就需要我们自定义处理逻辑了。
二、分场景实现自动删除
场景1:不管本地有没有修改,只要主库没了就删本地文档
这种需求CouchDB没有内置功能,得靠复制后加个清理脚本实现,步骤很清晰:
先执行主库到本地的一次性复制
你可以通过CouchDB的_replicator文档来触发复制,比如创建一个临时的复制任务(用完就删):{ "_id": "temp-main-to-local-sync", "source": "http://你的主库地址:5984/目标库名", "target": "http://localhost:5984/本地库名", "continuous": false, "create_target": false }把这个文档插入到本地的
_replicator库,就会启动一次性复制。复制完成后执行清理脚本
复制结束后,我们需要对比本地和主库的文档ID,删除本地独有的那些(排除设计文档,因为设计文档一般是本地维护的)。这里给你一个Node.js的脚本示例,用nano库操作CouchDB:const nano = require('nano')('http://localhost:5984'); const mainDbClient = require('nano')('http://你的主库地址:5984'); const localDb = nano.db.use('本地库名'); const mainDb = mainDbClient.db.use('目标库名'); async function cleanStaleLocalDocs() { // 1. 获取主库所有文档ID(排除设计文档) const mainDocList = await mainDb.list({ include_docs: false }); const mainDocIds = new Set( mainDocList.rows.filter(row => !row.id.startsWith('_design/')).map(row => row.id) ); // 2. 获取本地库所有文档ID(排除设计文档) const localDocList = await localDb.list({ include_docs: false }); const localDocIds = localDocList.rows .filter(row => !row.id.startsWith('_design/')) .map(row => row.id); // 3. 找出本地有但主库没有的文档ID const staleDocIds = localDocIds.filter(id => !mainDocIds.has(id)); // 4. 批量删除这些文档 if (staleDocIds.length > 0) { const deleteBatch = staleDocIds.map(id => ({ _id: id, _deleted: true })); await localDb.bulk({ docs: deleteBatch }); console.log(`清理完成:删除了${staleDocIds.length}个本地失效文档`); } else { console.log('没有需要清理的本地文档'); } } // 先等待复制完成,再执行清理 async function syncThenClean() { // 插入复制任务 const replicatorDb = nano.db.use('_replicator'); const replicationTask = await replicatorDb.insert({ "_id": "temp-main-to-local-sync", "source": "http://你的主库地址:5984/目标库名", "target": "http://localhost:5984/本地库名", "continuous": false }); // 轮询等待复制结束 let taskStatus; do { await new Promise(resolve => setTimeout(resolve, 1000)); taskStatus = await replicatorDb.get('temp-main-to-local-sync'); } while (taskStatus._replication_state === 'running'); // 删除临时复制任务 await replicatorDb.destroy('temp-main-to-local-sync', taskStatus._rev); // 执行清理 await cleanStaleLocalDocs(); } // 启动流程 syncThenClean().catch(err => console.error('执行出错:', err));把脚本集成到应用启动流程
你可以在本地应用启动时,先调用这个脚本,完成复制+清理的操作,再启动应用的业务逻辑。
场景2:只删除主库已标记删除的本地文档(不处理本地新增的文档)
如果你的需求是:本地新增的文档可以保留,只删除那些主库原本有现在删了的文档,那可以用CouchDB的冲突解决策略:
在_replicator文档里加上"conflict": "winner_takes_all",这个策略会选择修订版最新的那个——如果主库的删除操作比本地修改的时间晚,就会自动删除本地文档;如果本地修改更晚,会保留本地版本。
{ "_id": "sync-main-to-local", "source": "http://你的主库地址:5984/目标库名", "target": "http://localhost:5984/本地库名", "continuous": false, "conflict": "winner_takes_all" }
三、注意事项
- 设计文档保护:脚本里排除了
_design/开头的文档,因为设计文档(比如视图、过滤器)一般是本地开发维护的,不需要和主库同步删除。 - 性能优化:如果文档数量很大,
_all_docs可以用分页参数(limit和skip)来分批获取,避免内存占用过高。 - 权限控制:确保脚本使用的CouchDB账号有主库的读取权限和本地库的写入权限。
内容的提问来源于stack exchange,提问作者georges rolland

