pouchdb Conflicts
Conflicts are an unavoidable reality when dealing with distributed systems. And make no mistake: client-server is a distributed system.
CouchDB and PouchDB differ from many other sync solutions, because they bring the issue of conflicts front-and-center. With PouchDB, conflict resolution is entirely under your control.
In CouchDB, conflicts can occur in two places: immediately, when you try to commit a new revision, or later, when two peers have committed changes to the same document. Let's call these immediate conflicts and eventual conflicts.
Immediate conflicts
Immediate conflicts can occur with any API that takes a rev or a document with _rev as input –put(), post(), remove(), bulkDocs(), and putAttachment(). They manifest as a 409 (conflict) error:
var myDoc = {
_id: 'someid',
_rev: '1-somerev'
};
db.put(myDoc).then(function () {
// success
}).catch(function (err) {
if (err.name === 'conflict') {
// conflict!
} else {
// some other error
}
});
In your code, you should always be handling conflicts. No matter how unlikely it may seem, 409s can and do occur.
For instance, if you are doing live replication, a document may be modified by somebody else while the user is working on it. If the remote changes are replicated to the local database before the user tries to commit their changes, then they will receive the above 409 error.
Upsert
In many cases, the most practical solution to the 409 problem is to retry the put() until it succeeds. If the user's intended change can be expressed as a delta (i.e. a change that doesn't depend on the current revision), then this is very easy to achieve.
Borrowing a phrase from MongoDB, let's call this an upsert ("update or insert"), and use the pouchdb-upsert plugin to implement it:
function myDeltaFunction(doc) {
doc.counter = doc.counter || 0;
doc.counter++;
return doc;
}
db.upsert('my_id', myDeltaFunction).then(function () {
// success!
}).catch(function (err) {
// error (not a 404 or 409)
});
This upsert() function takes a docId and deltaFunction, where the deltaFunction is just a function that takes a document and outputs a new document. (If the document does not exist, then an empty document is provided.)
pouchdb-upsert also offers a putIfNotExists() function, which will create a document if it doesn't exist already. For more details, see the plugin's documentation.
Eventual conflicts
Now, let's move on to the second type: eventual conflicts.
Imagine two PouchDB databases have both gone offline. The two separate users each make modifications to the same document, and then they come back online at a later time.
Both users committed changes to the same version of the document, and their local databases did not throw 409 errors. What happens then?
This is the classic "conflict" scenario, and CouchDB handles it very elegantly. By default, CouchDB will choose an arbitrary winner based on a deterministic algorithm, which means both users will see the same winner once they're back online. However, since the replication history is stored, you can always go back in time to resolve the conflict.
To detect if a document is in conflict, you use the {conflicts: true} option when you get() it.
db.get('docid', {conflicts: true}).then(function (doc) {
// do something with the doc
}).catch(function (err) {
// handle any errors
});
If the document has conflicts, then the doc will be returned with a _conflicts attribute, which may contain the IDs of conflicting revisions.
For instance, imagine the doc returned is the following:
{
"_id": "docid",
"_rev": "2-x",
"_conflicts": ["2-y"]
}
Here we have two separate revisions (2-x and 2-y) written by two separate databases, and one database's revision (2-x) has arbitrarily won.
Normally, _revs look more like 2-c1592ce7b31cc26e91d2f2029c57e621, i.e. a digit followed by a very long hash. In these examples, x and y are used in place of the hash, for simplicity’s sake.
Notice that the document's current revision starts with 2-, and the conflicting version also starts with2-, indicating that they're both at the same level of the revision tree. (Revision hashes start with 1-,2-, 3-, etc., which indicates their distance from the first, "root" revision. The root always starts with1-.)
Both databases will see the same conflict, assuming replication has completed. In fact, all databases in the network will see the exact same revision history – much like Git.
To fetch the losing revision, you simply get() it using the rev option:
db.get('docid', {rev: '2-y'}).then(function (doc) {
// do something with the doc
}).catch(function (err) {
// handle any errors
});
At this point, you can present both versions to the user, or resolve the conflict automatically using your preferred conflict resolution strategy: last write wins, first write wins, RCS, etc.
To mark a conflict as resolved, all you need to do is remove() the unwanted revisions. So for instance, to remove '2-y', you would do:
db.remove('docid', '2-y').then(function (doc) {
// yay, we're done
}).catch(function (err) {
// handle any errors
});
If you want to resolve the conflict by creating a new revision, you simply put() a new document on top of the current winner, and make sure that the losing revision is deleted.
PouchDB deviates from CouchDB’s replication algorithm in one small way: revision hashes aren’t deterministic. PouchDB is forced to do this, because CouchDB calculates its revision hashes in an Erlang-specific way.
In practice, this just means that PouchDB’s replication algorithm is slightly less efficient than CouchDB’s, for some very unlikely edge cases. For details, seethis comment.
Another conflict resolution strategy is to design your database so that conflicts are impossible. In practice, this means that you never update or remove existing documents – you only create new documents.
This strategy has been called the "every doc is a delta" strategy. A classic use-case for this would be a checkbook app, where every document is simply an operation that increases or decreases the account balance:
{_id: new Date().toJSON(), change: 100} // balance increased by $100
{_id: new Date().toJSON(), change: -50} // balance decreased by $50
{_id: new Date().toJSON(), change: 200} // balance increased by $200
In this system, it is impossible for two documents to conflict, because the document _ids are just timestamps. Ledger transactions are recorded in the order they were made, and at the end of the day, you only need to do an allDocs() or query() operation to sum the result.
The wisdom of this strategy can be expressed by the maxim: "Accountants don't use erasers". Like a diligent accountant, your app can just add new documents when you want to make a change, rather than going back and scrubbing out previous changes.
There is also a PouchDB plugin that implements this strategy: delta-pouch.
pouchdb Conflicts的更多相关文章
- pouchdb sync
PouchDB and CouchDB were designed for one main purpose: sync. Jason Smith has a great quote about th ...
- file xxx from install of xxx conflicts with file from xxx
执行安装 rpm -ivh lib64stdc++6-4.6.1-2-mdv2011.0.x86_64.rpm 时提示以下错误: warning: lib64stdc++6-4.6.1-2-mdv20 ...
- ionic 通过PouchDB + SQLite来实现app的本地存储(Local Storage)
首先声明,本教程参考国外网站(http://gonehybrid.com/how-to-use-pouchdb-sqlite-for-local-storage-in-your-ionic-app/) ...
- 使用PouchDB来实现React离线应用
最近听到有同学在讨论关于数据上传遇到离线的问题,因此在这里介绍一下PouchDB. PouchDB 是一个开源的javascript数据库,他的设计借鉴于Apache CouchDB,我们可以使用他来 ...
- file /usr/share/mysql/... conflicts with file from package mysql-libs-5.1.73-3.el6_5.x86_ 64 MySQL安装
在CentOS 6.5安装MySQL 5.6.17,安装到最后一个rpm文件MySQL-server时 安装命令是:rpm -ivh MySQL-server-5.6.17-1.el6.x86_64. ...
- 【Junit】The import org.junit.Test conflicts with a type defined in the same file报错
引入Junit后,进行单元测试,莫名其妙报了个这样的错误 The import org.junit.Test conflicts with a type defined in the same fil ...
- docker 报Error: docker-engine-selinux conflicts with docker-selinux-1.9.1-25.el7.centos.x86_64
root@ecshop Deploy]# yum -y install docker-engine-selinux.noarchLoaded plugins: fastestmirrorhttp:// ...
- ionic 运用pouchdb/sqlite 数据库做本地存储
配置数据库环境需要3步: 1.安装slqite插件 在ionic 工程目录对应终端执行一下命令: npm install cordova-plugin-sqlite 2.安装pouchdb 在ioni ...
- [转]ionic 通过PouchDB + SQLite来实现app的本地存储(Local Storage)
本文转自:http://www.cnblogs.com/ailen226/p/ionic.html 首先声明,本教程参考国外网站(http://gonehybrid.com/how-to-use-po ...
随机推荐
- ERP仓库管理系统查询(十)
需求: 1.根据仓库编号,获取仓库信息绑定至页面相关控件. 2.根据仓库编号,获取管理员信息绑定到页面相关控件 修改的界面: <%@ Page Language="C#" ...
- ERP权限系统(七)
添加链接权限的字段: //权限管理 n.Target = "MainFrame"; //折叠 TreeView1.Nodes.Add(n); n.Expanded = false;
- 2、C#基础整理(运算符、数据类型与转换、var关键字)
·运算符 数学运算符:+ - * / % 比较运算符:< > = <= >= != 返回bool值 逻辑运算符:&&并且.||或者,两者运行 ...
- Team Foundation API - 编程访问 WorkItem
Team Foundation Server (TFS)工具的亮点之一是管理日常工作项, 工作项如Bug, Task,Task Case等. 使用TFS API编程访问TFS服务器中的工作项, 步骤如 ...
- C语言----变量及作用域 、 指针 、 指针和数组 、 进程空间 、 字符串
1 使用程序来模拟放球.取球的问题 1.1 问题 栈是一种特殊的线性表,它的逻辑结构和线性表相同,只是其运算规则较线性表有更多的限制,故又称为运算受限的线性表. 栈的定义是限制仅在表的一端进行插入和删 ...
- 本地安装git
在ubuntu上安装git特别简单 首先用命令查看是否安装git 在终端输入 git 如果没有安装 sudo apt-get install git 安装完之后,测试是否安装成功: git --ver ...
- mysql explain用法和结果的含义(转)
重点是第二种用法,需要深入的了解. 先看一个例子: mysql> explain select * from t_order; +----+-------------+---------+--- ...
- 1password密码文件重装后恢复
因为重装系统,加上Time machine硬盘损坏. 只能从之前零散Time machine的恢复数据中找到最近的一个备份. 1password是会自己执行备份的,起备份文件在 ~/Library/C ...
- C# 接受邮件 两种方式
有些累了,不想写太多,直接把代码贴上 EWS 源码 POP协议 源码 PS:如果我们发现引入的一个dll,能够添加引用,但是一编译又找不到,那么很有可能是.net framework 版本不同. 不如 ...
- Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现(转)
Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现 zouxy09@qq.com http://blog.csdn.net/zouxy09 自己平时看了一些论文, ...