Offline-First Database Comparison
github.com
github.com
Biggest bummer of CouchDB? If you’re not hosting it yourself, there’s only one major player in the market that I know of: IBM Cloudant. They contribute much to Apache CouchDB though, and hosting it yourself doesn’t seem too difficult, especially for small, simple use cases.
Anyone else using CouchDB?
They offer VMs, containers, managed DB instance offerings, block storage, multiple regions and datacenters, load balancing, etc. They have an API to control all those things, and modules available in many popular languages. But that's basically what many people consider table stakes for a service like that, and indeed there are competitors like Vultr and Upcloud that offer the same.
Will any of them have quite the same level of offerings that AWS or GCE or Azure offer? Probably not. But for a great many people what they have is all the enterpise level stuff they'll need or use, and it is decidedly easier to just start up a cheap linux VM and take care of it on one of these services compared to AWS or GCE or Azure, so if what you want are somewhat manually managed cloud VMs, I highly recommend one of these services over one of the big names.
I've used all the services named, and I still prefer DO for just throwing up a cheap $5-$10 VM for personal stuff, or to spin up a temporary VM for testing something out. On DO that's a couple second process when you do it manually by clicking around.
Can one run a professional, mid-pack to top-tier SaaS on it, is it consistent, stable, etc
I wouldn't hesitate to run significant sites on any of these providers. Problems and outages happen, I don't think anyone is really providing five 9s whatever they say.
The "DO offering" is a basic VPS and you host/manage the couchdb process yourself. DO does not offer a managed couchdb service. I've found couchdb to be reasonably stable and worry free.
The nature of pouchdb/couchdb and the design philosophy behind it makes it relatively easy to scale to additional servers. Couchdb is master-master, which is a good eventually consistent model that should help in any horizontal scaling. The base process is erlang/elixir which should scale vertically well.
I've been using it a couple of years, but not on any sites that have significant traffic.
(with regard to systems from Oracle and SAP)
But DO doesn't have any pre-configured droplets to start off with. Be nice if they did.
Have you come across any simple examples that show offline mode and syncing between clients with replication?
I love RxDB with Hasura behind it. It's incredible and you get a great postgres front end to boot.
That's really great to hear about native JWT auth support, that is a huge gap for me to not have a good story of how to do auth. I totally understand the reasons for moving it outside of the database, but it was too much work to create and manage my own proxy on top of couchdb.
Thanks a lot for the clarification, and I'll be watching.
Some of the things that trouble me. The ubuntu upstream package manager dropped off the radar for a while, not sure if it's currently running or not. Also, the last release was a looong time ago. I understand it's pretty stable, but there are rough spots that could use some shoring up as noted.
These issues aren't enough for me drop its active use in production, but I'm eyeing reworking how I use if these kinds of issues continue. This kind of dropping the ball doesn't instill confidence. I don't want to have to maintain my own installer so I can predictably perform new installations.
Also, no Linux ARM package. I gave a go at compiling it myself for ARM, but that failed due to being unable to find/use a compatible SpiderMonkey.
It's great tech, and I'd love to carry it with me into bigger and better projects. Here hoping :)
CouchDB doesn't move extremely fast. It's kinda boring and reliable once everything is set up - which I consider a good thing.
But I agree, it's annoying to not be able to analyse data across multiple user DBs with a single query or to build hacky solutions when listening to changes across databases.
[1] https://github.com/apache/couchdb/issues/1524
E: typo
+--+ +-------+ +-------+ +--+
|PG+<-+->+CouchDB+<-->+PouchDB+<-->+UI|
+--+ | +-------+ +-------+ +--+
|
+->...
|
+->...
[0]: https://kroki.io/ditaa/svg/eNpTUNDW1dVWAAEgAwy0sXG0uRQUagLct...I don't get that. If they're working on improving something I didn't like that's something I'd appreciate.
CouchDB/PouchDB works great for how I'm using them but over the years I've observed that those coming from using SQL DBs can have a hard time with it.
I get that. But CouchDB is not designed to compete or replace SQL.
To me, it feels like CouchDB was not the right tool for the job you were doing. That's not a reason to dismiss it though.
Because it was inspired by one of the first document-based, nosql, replicated databases - Lotus Notes.
I’ve copied airtable data to it in the past.
Recently I implemented an event store CQRS system designed to be usable offline. I considered syncing events to the client via sockets but I needed to implement diffs. So I use couch and pouch as read only side of CQRS with an append only event CouchDB. The actual data is in Postgres.
Authorization is tricky. I do not recommend trying to do document level access control. I simply added an express endpoint that allows only reads and checks the session user for which table they can access. Then pass the request to couch.
Overall works really well. I recently turned off live sync for web and react native and loop over all of my databases on a set timeout interval. I had trouble with many connections at once.
If you're trying to do that, true. But if you simply let the user sync his data as he pleases, auth is quite easy with the library I maintain (link in my profile).
> I had trouble with many connections at once.
Can you elaborate on how many? And Did you increase max_db_open and the necessary OS limits? I'm currently planning to go the other way, but also a bit worried that too many open connections can cause trouble.
> Can you elaborate on how many?
For web, I can have as many as I want syncing. I haven’t stress tested it yet tho. I have a CouchDB in prod that throws an error message about connection limit a few times a day. This one writes and reads. It restarts the docker container to recover since I haven’t had time to investigate. You may have given me the answer :)
On React Native, I’ve had odd behavior around 5 connections. That’s where I need to periodically poll for syncing. It works out best since the user is offline most of the time and downloads infrequently.
A shared databases where everyone can read but only some can write is very much possible with design docs. And when being flexible with assigning/revoking roles + creating/replicating data, a lot can be modelled without a proxy.
But yeah, at some point it's probably easier to use a proxy than to do weird stuff with databases and roles.
If you're hitting the same issues, this might be it. A non-throwing limitation in the browser runtime.
That's the biggest problem my projects using Pouch/Couch are facing. The tech choice was made when Cloudant still had Azure datacenter support and IBM's multiple confusing changes to their Cloud brands has put it in a situation we aren't entirely happy with and I keep getting asked/pressure if I can move things back to Azure datacenters.
I don't know what I'm going to replace it with and I still wish Azure CosmosDB was more friendly to Couch replication. (It's so close, especially its Changes feed, I feel that the proxy I need probably doesn't need to do all that much I just don't think I have the budget/time to build and test such a proxy.)
> I just don't think I have the budget/time to build and test such a proxy
Same here, including with the authentication shortcomings we hope get addressed, like per-document security or other improvements.
So I'm glad to hear something else apart from Pouch actually handles it. Anyone familiar with rxdb and can chime in on how they do it?
Personally I usually don't have multiple instances of the same app open on multiple machines, but other people might open the same or different files on their desktop and laptop.
- Not all settings should be synchronized (don't include machine-specific "recent files" paths).
- How should settings be stored locally (if I may have multiple instances of my app open on a single machine)? Registry (Windows-only)? INI with atomic saving (requires care and locking to prevent multiple instances from trampling or racing with each other)? SQLite?
IMO Stylus is a pretty good implementation of offline-first cloud settings sync over Dropbox/etc. It's currently based around one JSON file per CSS file (Dropbox/Apps/Stylus - Userstyles Manager/docs/uuid.json), and what appears to be a transaction log (Dropbox/Apps/Stylus - Userstyles Manager/changes/number.json). Cloud sync has been 100% reliable in my experience, though I do notice temporary file lock errors when switching between different machines in my dual-boot setup (but sync seems to be eventually consistent nonetheless).
uBlock Origin is worse. Instead of merging settings, it expects the user to upload and download the entire settings blob at once (and pulling an old blob can erase changes you've made locally). And in the past it's entirely failed to sync because the blob was too big to upload to Mozilla's servers. (Right now it "works" but takes several minutes for one computer to see a config uploaded from another computer.)
- A directory is synced with the server with no conflict resolution - The application creates its config file in that directory, named by a random UUID, which is stored outside the synced folder - The config file stores the setting overrides (defaults were compiled-in) in any format (I used YAML) - Each setting override includes a "locked" and "lastModifed" - On startup (sync was external) all files in the directory are read and merged starting with the local one, then skipping any settings that are locked (locally or remotely), last modified wins
Some deployments used a daily rsync cronjob, some had a mounted network share (with hilarious broken file locking) and of course it worked with direct bind mounts as well.
I also briefly experimented turning the "locked" field into a "group" field to enable multiple "sync groups" with some keys shared globally and some only with other group members (even different groups for different settings), but it ended up not being useful for my use case, although it did work.
What does the locked field do?
Do you have a link to your implementation, or is this proprietary?
As an aside… I would truly love to explore a collection of interesting ways to use SQLite. It’s such an impressive piece of technology that I’d like to use more often. Please share if you have something similar!
Pros: querying complex data hierarchies was easy, and was able to skip the pain typically associated with managing a SQL schema.
> EAV is often an anti-pattern when a schema could be defined
Super interesting, I wasn't aware that EAV is an anti-pattern in that case. Is it an efficiency thing?
For clarity, my design wasn't schemaless, values (can) have defined datatypes and relationships are first-class. I meant that I found adding to or modifying the schema was less cumbersome and error prone than traditional SQL schema additions or changes. I feel like SQL schema management is more suited to server-based dbs where you have tight control over the db lifecycle, which you don't when it lives on a bunch of mobile devices.
Totally agree with the ease of sync and conflict resolution, another strong pro.
Love to hear more about your approach! Also feel free to reach out (email in bio) if you'd like to compare notes some time.
It was built on a single table that held the entity-attribute-value tuple along with some additional metadata like type information, whether or not the attribute was a pointer to another entity, and the cardinality of that relationship (one or many).
Relationships were walked via self joins and the eav columns were all indexed.
The difficulty with having all attributes be EAV becomes apparent when having to do multiple joins to fetch a single record type (what would be a “table” traditionally). Although this is manageable, the bigger difficulty I’ve found is synchronizing deletions of records, especially if deletions/insertions are done in bulk. Rather than just 1 transaction you have to do multiple delete/insert queries to also delete/insert the attributes and the values and they should be done in a way that doesn’t break key constraints.
The project is both interesting and amusing, thanks!
The closest thing is this:
https://munin.uit.no/bitstream/handle/10037/22344/thesis.pdf
There it split each record in a stream of CRDTs values. I found (quickly!) that it could cause serious violations of business logics if done as-is. Now, I trying to threat the record as whole. Still could have issues for multi-record/table logical integrity, so I have tough in build a "transaction markers" so your stream of changes are:
Start
ADD: T1.Row1...
ADD: T2.Row1...
End
So you don't partially apply a change.P.D: If interested and know Rust we can talk!
i'm pretty sure the term offline-first wouldn't have met the cutoff point, as its _really_ well known from my experience.
I now leave things out that can be googled and are already known by "most" readers.
naive question: arguably git and mercurial and subversion could be thought of as offline first databases -- albeit targeted at a domain-specific use case. does it make any sense to compare them too?
academia has many things to learn from the world of software development, particularly around testing to ensure quality and reproducibility of work, but perhaps software development could benefit from a few ideas from academia: giving a brief introduction to contextualise the work -- not an extensive glosarry, but at least a few links to relevant work others have already done - ideally with at least one link to something that introduced the idea or is an extensive survey of the subject.
I'm reading the book "designing data-intensive applications" at the moment and looked up offline-first applications in the index, which references http://blog.hood.ie/2013/11/say-hello-to-offline-first/ , which no longer exists, but is still mirrored by https://web.archive.org/web/20200222150347/http://hood.ie/bl...
Over the last 8 months, I've been working on a react/capacitor based android app (potentially ios later on) and was originally using idb-keyval, which uses indexeddb for key val storage, and things were great since it's a local storage solution compatible with react/capacitor, and one of my goals is to not rely on a remote data storage solution.
As said, things were going great, but then a couple weeks ago things went to shit when all the data that was stored in the prototype on my android device was wiped. Apparently, both android and ios tend to wipe browser/web-view local storage at random/when space is needed(?).
Dug around since looking for an alternative solution (would love a capacitor compatible mongodb solution), came across pouchdb via rxdb, but could've sworn there was mention that it relies on indexeddb. So just to be safe switched to sqlite and been rewriting components since.
Lesson of the story, even if it isn't dependent on indexeddb, if you're looking for a local storage option for a mobile app and happen to be using a js framework with capacitor or anything that utilizes a web-view, stay away from anything that uses indexeddb. If a wipe like this were to happen post release, the chance that your app succeeds afterwards would be near 0%
Edit: so yeah, just double checked/was reading through the readme of this project, and pouchdb via rxdb is reliant on indexeddb
I am using RxDB with Capacitor (iOS and Android app). You can use the SQLite based pouchdb adapter with capacitor. It keeps your data and is (sometimes) faster.
Here [1] I have documented a whole section about how to use RxDB+SQLite in Capacitor.
Now would I use the adapter for react native or for cordova?
If react native, I've been writing with react js and have been under the impression that react native specific plugins aren't compatible with react js. Is that wrong?
If cordova, it's totally compatible with capacitor?
Sorry just want to make sure before jumping in
Also wasn't sure whether it was being suggested to use the react (native) plugin or the cordova plugin, as I didn't clarify (I should have) in the parent that I'm using react js and not native.
One time, users could not load their data if their android device had less than 1 GB of free disk space, because Chrome had a bug in calculation the quota for IndexedDb.
SQLite is fast and it gives me peace at night, knowing that user data is safe.
"Apparently"? Mozilla docs say so much in the introduction[0], even directing you to a dedicated page[1].
[0]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_A... [1]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_A...
Yeah, that's going to be hard goal to meet in this case. A benefit to PouchDB over raw IndexedDB is the great replication support and replicating everything back down in the case of one of those worst case wipes is an alright solution in some cases (but that does require managing remote data storage solutions, unfortunately).
Android and iOS are supposed to manage IndexedDB as Application Storage when installed as an app via something like Capacitor, but it's still not great. Supposedly Android is getting a lot better about it for apps installed as PWAs instead and Capacitor should give you a good PWA path for Android at least. (iOS is unfortunately still lagging far behind on PWA support.)
its great if you're writing a traditional app though, but really unfortunate wrt to the PWAs
This includes what options you have available for querying (just accessing a key range, upper/lower bound, forward or backwards (backwards can be much slower), rather than a full query language like SQL etc..
So you need to design your app / data format and pre-plan any queries so you'll have the indexes you need, or do some glue logic to combine indexes as needed.
I've used indexedDB on a couple of projects at work, while there are definitely downsides with its indexing design, limited querying options and the menagerie of fuckups by Team Fruit(TM), it works well as a local cache when our clients are out on site with their customers and all they've got is a crappy intermittent 3/4g signal.
It's true that the indexeddb access is faster then a 3g signal with timeout, but that wasn't happening often enough to warrant slowing down all other requests measuribly just to speed up the rare case when this occurs.
Loading from indexeddb generally took about 100-200ms, loading data over WLAN/4g from a remote server (~500km real life distance) over socket took < 50ms overall for multiple json payloads with about 200 serialized entities altogether.
And doing both at the same to serve whatever finished first wasn't worth the trade for me either, as the indexeddb access isn't cheap from a energy drain perspective either.
Other people might come to different conclusions depending on their challenges.
This approach gets you pretty close to native app speed.
Our major issue: we write many small documents, and we write them over every user’s database fairly frequently. And Cloudant’s default settings don’t like that with a one-user-per-database approach. In fact, they discourage anyone from the one-db-per-user approach these days: https://www.ibm.com/cloud/blog/cloudant-best-and-worst-pract...
That blog post calls it an anti-pattern, but I would respectfully disagree. It is an absolutely great pattern to keep a native app and a web app in sync across multiple devices with intelligent conflict resolution.
A solution was to reduce the number of shards that a database was split out over, since our database’s data is pretty small overall and we didn’t need each database split out so much across our cluster.
I was thinking read uncommitted, but they might allow dirty writes. Maybe there’s some CRDTs under the hood… I can’t find any documentation on consistency though, anyone here know?
There's consistency, write first, latency, replication, and then acronyms.
I just implement hardware and OS stuff, but I like the DB people to be happy. What am I missing?
Can you show where you force the browser offline?
But it does has everything required for streaming and syncing data in a modern way. You can open streaming queries and even with a backlog of data, so syncing should be pretty easy. Maybe would be feasible to fork PouchDB to use RethinkDB as a backend?
https://firebase.google.com/docs/firestore/security/get-star...
[0] https://docs.google.com/spreadsheets/d/12ReO-4_bZ2BaLj9P6oJT...
Syncing that local CouchDB to your web based CouchDB is very fast and it's done in the background so it doesn't affect the performance of actually using the app. You can click "Save" and move on with no waiting at all.
So in this scenerio the speed of the DB is not necessarily a reflection of the speed of the app for the user.
This is not true. When you compare the CouchDB replication with other replication protocols, it is slow. The reason is that CouchDB supports replication with many instances at the same time. This creates big overhead in handling revision trees. Many requests have to be made all the time. You can observe that by starting the PouchDB subproject in the comparison repo. Watch the network tab in dev tools. Another problem is that CouchDB does not support Websocket replication, everything is long polling and plain http requests.
Other replications that only support many-clients-to-one-server are way faster. Both, on the initial load and on ongoing changes. This was the main reason why I build GraphQL replication for RxDB.
That may be true but when a single user is working with an offline-first app connected to a CouchDB installed on their desktop pc that happens entirely in the background so they don't experience any lag.
And while I've not done benchmark studies with CouchDB I have monitored the logs to watch those syncs and we're not talking painfully "slow" in real world use. It is reliable though. I've been using it for about 5 years now and it's been solid. And so has the work they've done to improve it.
And I was not aware of RxDB, which is certainly interesting, so thank you for sharing that!
I looked into using this as well but eventually decided against it due to the lack of active development.
I deployed CouchDB in a Kubernetes cluster (not with HA as I didn't have high availability requirements), and it was working great.
The real world demands web apps in the enterprise even in 2021.
Edit: and as I said at the beginning - "I often feel I'm on another planet" - having this discussion with web developers trying to justify something that is not just bad but dumb in so many ways always gives me a chuckle, and I try to educate on why there are other things beside browsers, and its not a very good way to write software
The problem is that Watermelon assumes a consistent view of the entire database, so you can't have multiple writers - at least not without synchronous notifications from IndexedDB (not a thing), leader election (cannot be made reliable), or some design sacrifices. To be reconsidered in the future...
If you're considering GunJS, take a look at the code and verify it's something you'd want to debug, before going all-in.
Don't get me wrong. The world still uses the technology you mentioned at large, but the industry has in-fact built upon and grown alongside older technology.
In general, the whole rich web app space reminds me of the "You're not making Christianity better, you're making Rock'n Roll worse" meme.
But seriously, I'm okay with working with SB when I have to, but quite often in those situations Java wouldn't be my first choice, and it's still all the putrescence of proper Spring underneath. A framework for a framework, with a bit too much magic for me. Explicit is better than implicit.
Allowing users to work when their mobile signal is spotty is pretty good UX for those that don't deal in a office cubical or wfh.
Yes you could argue that native mobile apps have that covered...
But should it be that you have two proprietary "standards", and only those two? For "trivial" apps that are just data entry/query?
Why do I need 124mb for the app code alone of Twitter, when the web version is closer to ~7mb (ang don't start the argument about 7mb being obscene for a website, please. we're discussing a web APP, not grandma's cooking blog).
Plenty of sites are still on the "Linux, MySQL, SpringBoot, and HTML/CSS/JS" stack or equiv. We just have tools now that let us add "works while you're on a train where the internewt is unreliable" to the featureset.