DEV Community

Cover image for CouchDB: Offline-first multi-master synchronization using Docker and Docker-compose
Aniello Musella
Aniello Musella

Posted on

CouchDB: Offline-first multi-master synchronization using Docker and Docker-compose

In this post, I'll show how to simulate a multi-master synchronization with Apache CouchDB considering an off-line scenario. To reach this goal, I'll use Docker and Docker compose.

What should you know?

  • Apache CouchDB (basic)
  • Docker
  • Docker Compose

What do you need?

Docker and Docker compose.

What's CouchDB

CouchDB is a NoSQL database document oriented using JSON format to store data.

The CouchDB's main features are:

  • Single node database.
  • Possibility to be configured as a cluster to increase the data availability.
  • Intuitive HTTP/JSON API to access to data (e.g. http://<yourserver>:<yourport>/<dbname>/<document id>/)
  • The use of multiversion concurrency control (MVCC) so the database file is not locked during writes (this helps performances). Conflicts are reported and managed at application level.
  • ACID properties at document level.
  • Multi master replication thanks to CouchDB Replication Protocol able to synchronize JSON documents between 2 peers over HTTP/1.1 by using the public CouchDB REST API based on the Apache CouchDB MVCC Data model.
  • Offline First Data Sync thanks to its replication protocol, data are always available even in scenario of bad connectivity.
  • Good platform scalability from server to mobile (PouchDB)

What's a multi-master synchronization

Quoting Wikipedia:

Multi-master replication is a method of database replication which allows data to be stored by a group of computers, and updated by any member of the group. All members are responsive to client data queries. The multi-master replication system is responsible for propagating the data modifications made by each member to the rest of the group and resolving any conflicts that might arise between concurrent changes made by different members.

CouchDB replication protocol lets to synchronize data between peers even in scenario of bad connectivity.

Multi-master synchronization: the scenario

In my scenario, I want to use multi-master synchronization to replicate data between three installations:

  • Alpha
  • Beta
  • Gamma

In this scenario, Alpha could be a database on main server (preferibly a cluster) and Beta and Gamma are edge installations with connectivity problems.

The requirement is that all data must be replicated through all database istallations. Below it's shown the synchronization schema schema.

Image description

It's worth note, in this schema, that the synchronization is bidirectional:

  • Alpha and Beta synchronize data bidirectionally
  • Alpha and Gamma synchronize data bidirectionally
  • Beta and Gamma don't need to be synchronized because they're already synchronized with Alpha, in this way I save network bandwidth.
  • Nothing prevents to implement data replication between Beta and Gamma, but in my case I prefer to save network bandwidth (so it's a precise architectural choice).

POC

Following I'll show all steps to simulate a multi-master synchronization. After tested the data replication, I'll consider an off-line scenario putting a database off-line.

Multi-master synchronization: Running all instances of CouchDB

To implement the scenario described above, I'll use Docker Compose to run a multi container application where each container represents respectively our Alpha, Beta and Gamma database installations.

I'll use the official CouchDb docker image published on DockerHub.

Following my docker-compose.yaml file:

version: '3'
services:
  couchserveralfa:
    image: couchdb
    restart: always
    ports:
      - "5984:5984"
    environment:
      - COUCHDB_USER=admin
      - COUCHDB_PASSWORD=A24cvmri
    volumes:
        - ./db/alpha:/opt/couchdb/data
        - ./config/alpha/local.ini:/opt/couchdb/etc/local.ini

  couchserverbeta:
    image: couchdb
    restart: always
    ports:
      - "5985:5984"
    environment:
      - COUCHDB_USER=admin
      - COUCHDB_PASSWORD=9VQhWrfW
    volumes:
        - ./db/beta:/opt/couchdb/data
        - ./config/beta/local.ini:/opt/couchdb/etc/local.ini

  couchservergamma:
    image: couchdb
    restart: always
    ports:
      - "5986:5984"
    environment:
      - COUCHDB_USER=admin
      - COUCHDB_PASSWORD=ouPFQ6mj
    volumes:
        - ./db/gamma:/opt/couchdb/data
        - ./config/gamma/local.ini:/opt/couchdb/etc/local.ini
Enter fullscreen mode Exit fullscreen mode

I map the default port 5984 of CouchDB to host port 5984 for Alpha, 5985 for Beta and 5986 for Gamma.

In this compose I use an'external configuration file local.ini for each installation to set this configuration (source stack over flow):

[chttpd]
port = 5984
bind_address = 0.0.0.0    
Enter fullscreen mode Exit fullscreen mode

Finally, I've mapped on the host file system (in the folder ./db/[alpha|beta|gamma]/) each database.

Now we can start the databases:

am@animus:~$docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

Multi-master synchronization: Creating databases to be synchronized

Using Flauxton login (as admin) in each node, accessing to:

For each installation, Select Databases from side menu and then Create Database, type the name of the database (in my case mydb) and click on create button like shown below:

Database creation

Multi-master synchronization: Creating replication

Now it's the moment to create replication.
Select Replication side menu and then click on New Replication button.

Image description

In this page we can create the replications.

Image description

In CouchDB replications are documents belonging to _replicator DB, so when you click Start Replication you create a new document.

Multi-master synchronization: Creating replications on Database Alpha

On DB Alpha (port 5984) we create two continuos replication:

One toward Beta:

{
  "_id": "69b5d15c85f1e8e5352136a2ce00457f",
  "_rev": "1-87b5f963a45b2d8df14eb10a3cdbd1f0",
  "user_ctx": {
    "name": "admin",
    "roles": [
      "_admin",
      "_reader",
      "_writer"
    ]
  },
  "source": {
    "url": "http://localhost:5984/mydb",
    "headers": {
      "Authorization": "Basic YWRtaW46MTIz"
    }
  },
  "target": {
    "url": "http://<YOUR IP ADDRESS>:5986/mydb",
    "headers": {
      "Authorization": "Basic YWRtaW46MTIz"
    }
  },
  "create_target": false,
  "continuous": true,
  "owner": "admin"
}
Enter fullscreen mode Exit fullscreen mode

One toward Gamma:

{
  "_id": "b15782ac94b2277c570b8689ab000d67",
  "_rev": "1-4912109dc6748874144aeef24ec23f20",
  "user_ctx": {
    "name": "admin",
    "roles": [
      "_admin",
      "_reader",
      "_writer"
    ]
  },
  "source": {
    "url": "http://localhost:5984/mydb",
    "headers": {
      "Authorization": "Basic YWRtaW46MTIz"
    }
  },
  "target": {
    "url": "http://<YOUR IP ADDRESS>:5985/mydb",
    "headers": {
      "Authorization": "Basic YWRtaW46MTIz"
    }
  },
  "create_target": false,
  "continuous": true,
  "owner": "admin"
}
Enter fullscreen mode Exit fullscreen mode

Multi-master synchronization: Creating replications on Database Beta

On DB Beta (port 5985) we create one continuos replication toward Alpha:

{
  "_id": "b45d1f6f186500eaa926b6d84d000507",
  "_rev": "4-6884dcb72b7132562e10d9a83c59936c",
  "user_ctx": {
    "name": "admin",
    "roles": [
      "_admin",
      "_reader",
      "_writer"
    ]
  },
  "source": {
    "url": "http://localhost:5984/mydb",
    "headers": {
      "Authorization": "Basic YWRtaW46MTIz"
    }
  },
  "target": {
    "url": "http://<YOUR IP ADDRESS>:5984/mydb",
    "headers": {
      "Authorization": "Basic YWRtaW46MTIz"
    }
  },
  "create_target": false,
  "continuous": true,
  "owner": "admin"
}
Enter fullscreen mode Exit fullscreen mode

Please note, even if we have mapped database Beta on host port 5895, the native port of the service remains 5894.

Multi-master synchronization: Creating replications on Database Gamma

On DB Gamma (port 5986) we create one continuous replication toward Alpha:

{
  "_id": "b45d1f6f186500eaa926b6d84d000507",
  "_rev": "4-6884dcb72b7132562e10d9a83c59936c",
  "user_ctx": {
    "name": "admin",
    "roles": [
      "_admin",
      "_reader",
      "_writer"
    ]
  },
  "source": {
    "url": "http://localhost:5984/mydb",
    "headers": {
      "Authorization": "Basic YWRtaW46MTIz"
    }
  },
  "target": {
    "url": "http://<YOUR IP ADDRESS>:5984/mydb",
    "headers": {
      "Authorization": "Basic YWRtaW46MTIz"
    }
  },
  "create_target": false,
  "continuous": true,
  "owner": "admin"
}
Enter fullscreen mode Exit fullscreen mode

Please note, even if we have mapped database Gamma on host port 5896, the native port of the service remains 5894.

Multi-master synchronization: Creating documents

Now we're ready to create new documents.

Choose a database (alpha, beta or gamma) and go to side menu bar, select Databases, then select Create Document. Compose your document and then confirm.

Image description

Wait few seconds and the document you've just created is sent to other databases (here we go!).

Multi-master synchronization: Creating documents off-line

I turn off database Alpha and Gamma running the following commands:

am@animus:~$docker kill couchdb_couchserveralfa_1 && docker kill couchdb_couchserverbeta_1
Enter fullscreen mode Exit fullscreen mode

On database gamma I create the document while the other databases are off-line.

After the creation of the new document I turn on the other databases running:

am@animus:~$docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

Wait few seconds and the document you've just created on Gamma database is sent while the other databases were off-line. So we've just proved that the synchonization works when they return on-line (here we go!).

Conclusions

In this article, I've shown how to simulate the multi-master synchronization of CouchDB and how to simulate an off-line scenario where I show that data keep on to be available for local operations and then be synchronized when the database returns on-line.
In these scenarios, CouchDB turned out a good solution.
Suggestions and corrections are welcome.

Top comments (0)