Skip to content

Latest commit

 

History

History
169 lines (110 loc) · 8.49 KB

README.md

File metadata and controls

169 lines (110 loc) · 8.49 KB

uncorded - embedded distributed store for small immutable data

Status: Experimental

Uncorded is a use-case specific database for applications needing to add, remove and expire small immutable documents of data across a cluster of nodes. Uncorded is not a general purpose database and it assumes the size and number of active documents is extremely small.

A few uncorded characteristics:

  • intended for very small, short-lived, immutable data
  • expires data at a given point in time
  • prefers availability over cluster consistency
  • handles add/remove centric workloads where master-slave replication doesn't help

Architecture Overview

Uncorded exposes three operations: add, remove and get. Add and remove operations need to be replicated across the cluster. Uncorded takes advantage of the remove-wins semantics of a 2P-set CRDT (conflict-free replicated data type) to achieve strong eventual consistency -- that is, all nodes should eventually arrive at the same state regardless of replication message ordering.

Master-Master Replication Strategy

A state-based CRDT was chosen over an operation-based CRDT so we could forego the requirement of a reliable network ensuring idempotent message delivery. The downside of a state-based CRDT is that we always replicate a node's entire state. Uncorded's small and short-lived state is a great fit here so long as we don't hold remove-set values indefinitely.

Nodes publish changes to listeners via newline delimited JSON representations of their state. Listeners apply the state changes according to the 2P-set merge algorithm.

Fault Model

Adding a document

In order to make adding a document highly-available and fast, uncorded does not wait for any nodes, let alone a quorum, to acknowledge writes. This has two major repercussions:

  • No read-after-write consistency: it is the onus of consumers to implement adequate retries to compensate for replication lag.
  • It is possible to loose a document if the node that accepted the write fails before replicating to other nodes in the cluster.

Removing a document

In order to make document removal highly available and fast, uncorded does not wait for any nodes, let alone a quorum, to acknowledge the removal. It is possible to remove the same document twice while a previous removal is replicated across nodes. Due to the eventually consistent nature of adding a document, it is possible to try and remove a token before it has been replicated to an uncorded node. It is important for consumers to retry token consumption for at least 1 second before declaring a token invalid.

Replicating a document addition/removal

?

Usage

Using uncorded requires firing up the API server and creating sets for use within your application.

const uncorded = require('uncorded');
const server = uncorded.createServer({
  discovery: uncorded.discovery.elb({
    region: 'us-east-1',
    elbName: 'uncorded-elb'
  })
});
const tokens = server.createSet('tokens');

// ... now start using your replicated set
const token = tokens.add({ user: 'bob' });
const found = tokens.get(token.id);
console.log(found.doc);
tokens.remove(token.id);

Options

log

An instance of a bunyan logger or an API compatible alternative.

discovery

Uncorded discovery is pluggable. If the discovery options is not specified, uncorded will warn and disable cluster discovery.

You can supply your own implementation or use a built-in option. The built-in options are accessible programmatically as properties of the uncorded.discovery object. Alternatively, you can declare the use of a built-in option by passing an object with the following format:

{
  type: 'elb',
  options: {
    region: 'us-east-1',
    elbName: 'uncorded-elb'
  }
}

Cluster Discovery

Uncorded supports pluggable cluster discovery. Discovery modules must be EventEmitters that appropriately produce peer-added and peer-removed events containing peer URLs.

Cluster Discovery: ELB

Uncorded comes built-in with a module for discovering peers based on healthy instances registered with an AWS Elastic Load Balancer. Cluster discovery is accomplished by periodically querying AWS ELBs for healthy nodes and resolving their IP addresses. We'll start with polling every 5 seconds and see where we go from there. Option fields include:

  • region: the AWS region where the ELB and EC2 instances reside. (i.e. us-east-1)
  • elbName: the name of the Elastic Load Balancers the instances are registered with. (i.e. uncorded-elb)
  • interval: the period of time to delay between polling for healthy nodes.
  • port: the port to connect to peers on - must match port passed into createServer.

The instances your running need to have the correct IAM policies to describe load balancer instances. Review the Terraform example files to see how to provision instances with the correct policy.

In the future, this discovery module may automatically detect the region and elbName based on the instance uncorded is running from.

Configuration

Status: experimental

The current configuration model is based on rc. Configure the port that uncorded listens on by setting the uncorded_port environment variable.

Uncorded uses bunyan for logging and you can configure most elements of logging via environment variables too. Change the log level by setting the uncorded_log__level environment variable to info, warn or error.

Security Considerations

Uncorded assumes the network is secure.

API

Each uncorded node exposes an HTTP server with the following routes.

2P-Set State Stream

Each set maintained by an uncorded node is exposed via an HTTP endpoint. Siblings in a cluster can listen for changes to a set's state by establishing a connection and waiting for newline-delimited (CRLF) JSON objects to appear.

GET /sets/{set_names}

Request

Clients are able to request the state stream for an individual set or multiple sets. Provide a comma delimited list of set names to receive changes for multiple sets:

GET /sets/tokens,keys,foos

Response

Responses are JSON objects with the root keys corresponding to set names. The initial response will contain the state of all requested sets. Streaming updates will only include state for the sets that have changed.

// formatted for your viewing pleasure, normally it is unformatted on one line
{
  "tokens": {
    "adds": {
      "d8b0a6fd-d0f5-4390-be87-37d8c91d62ea": {
        "id": "d8b0a6fd-d0f5-4390-be87-37d8c91d62ea",
        "doc": { "foo": "bar" }
      }
    },
    "removals": {}
  },
  "keys": {
    "adds": {},
    "removals": {}
  },
  "foos": {
    "adds": {},
    "removals": {}
  }
}

What's with the name?

If you were to guess how to pronounce the CRDT abbreviation, you might land at the word "corded". Given the master-master decentralized nature of this beast, the word uncorded seemed to make sense.

License

The MIT License (MIT) Copyright (c) 2016 Mario Pareja

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.