{"id":27641214,"url":"https://github.com/olric-data/olric","last_synced_at":"2025-05-14T04:09:02.100Z","repository":{"id":37733160,"uuid":"128195485","full_name":"olric-data/olric","owner":"olric-data","description":"Distributed, in-memory key/value store and cache. It can be used as an embedded Go library and a language-independent service.","archived":false,"fork":false,"pushed_at":"2025-04-25T16:23:00.000Z","size":6839,"stargazers_count":3220,"open_issues_count":15,"forks_count":121,"subscribers_count":51,"default_branch":"master","last_synced_at":"2025-05-12T04:59:37.976Z","etag":null,"topics":["cache","database","distributed-cache","distributed-database","distributed-hash-table","distributed-systems","in-memory-database","key-value","key-value-store","nosql","redis"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/olric-data.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"buraksezer","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2018-04-05T11:15:51.000Z","updated_at":"2025-05-10T22:49:24.000Z","dependencies_parsed_at":"2023-10-27T17:37:23.516Z","dependency_job_id":"fc2c5fda-b58f-4a68-a52e-7f91ac7ec39c","html_url":"https://github.com/olric-data/olric","commit_stats":{"total_commits":331,"total_committers":11,"mean_commits":30.09090909090909,"dds":"0.048338368580060465","last_synced_commit":"ac8ec420672fd5b80f6fe2ac3af434d735b81cc5"},"previous_names":["buraksezer/olricdb","olric-data/olric","buraksezer/olric"],"tags_count":73,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olric-data%2Folric","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olric-data%2Folric/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olric-data%2Folric/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olric-data%2Folric/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/olric-data","download_url":"https://codeload.github.com/olric-data/olric/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253948416,"owners_count":21988953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","database","distributed-cache","distributed-database","distributed-hash-table","distributed-systems","in-memory-database","key-value","key-value-store","nosql","redis"],"created_at":"2025-04-23T23:19:11.432Z","updated_at":"2025-05-14T04:09:02.078Z","avatar_url":"https://github.com/olric-data.png","language":"Go","funding_links":["https://github.com/sponsors/buraksezer"],"categories":["Go","database"],"sub_categories":[],"readme":"# Olric [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Olric%3A+Distributed+and+in-memory+key%2Fvalue+database.+It+can+be+used+both+as+an+embedded+Go+library+and+as+a+language-independent+service.+\u0026url=https://github.com/olric-data/olric/\u0026hashtags=golang,distributed,database)\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/olric-data/olric/.svg)](https://pkg.go.dev/github.com/olric-data/olric/) [![Go Report Card](https://goreportcard.com/badge/github.com/olric-data/olric/)](https://goreportcard.com/report/github.com/olric-data/olric/) [![Discord](https://img.shields.io/discord/721708998021087273.svg?label=\u0026logo=discord\u0026logoColor=ffffff\u0026color=7389D8\u0026labelColor=6A7EC2)](https://discord.gg/ahK7Vjr8We) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\nDistributed In-Memory Cache \u0026 Key/Value Store\n\nOlric provides a simple way to create a **fast, scalable, and shared pool of RAM** across a cluster of machines. \nIt's a distributed, in-memory key/value store and cache, written entirely in Go and designed specifically for distributed environments.\n\n**Flexible Deployment:**\n\n* **Embedded Go Library:** Integrate Olric directly into your Go applications.\n* **Standalone Service:** Run Olric as a language-independent service.\n\n**Key Features:**\n\n* **Effortless Scalability:** Designed to handle hundreds of members and thousands of clients. New nodes auto-discover the cluster and linearly increase capacity.\n* **Automatic Distribution:** Provides partitioning (sharding) and data re-balancing out-of-the-box, requiring no external coordination services. Data and backups are automatically balanced when capacity is added.\n* **Wide Client Support:** Uses the standard **Redis Serialization Protocol (RESP)**, ensuring client libraries are available in nearly all major programming languages.\n* **Common Use Cases:** Ideal for distributed caching, managing application cluster state, and implementing publish-subscribe messaging.\n\nSee [Docker](#docker) and [Samples](#samples) sections to get started! \n\nJoin our [Discord server!](https://discord.gg/ahK7Vjr8We)\n\nThe current production version is [v0.6.1](https://github.com/olric-data/olric/tree/release/v0.6)\n\n### About renaming the module\n\n`github.com/buraksezer/olric` module has been renamed to `github.com/olric-data/olric`. This change has been effective since **v0.6.0**.\nImporting previous versions should redirect you to the new repository, but you should change the import paths in your codebase as soon as possible.\n\nThere is no other difference between v0.5.7 and v0.6.0.\n\n## At a glance\n\n* Designed to share some transient, approximate, fast-changing data between servers,\n* Uses Redis serialization protocol,\n* Implements a distributed hash table,\n* Provides a drop-in replacement for Redis Publish/Subscribe messaging system,\n* Supports both programmatic and declarative configuration, \n* Embeddable but can be used as a language-independent service with *olric-server*,\n* Supports different eviction algorithms (including LRU and TTL),\n* Highly available and horizontally scalable,\n* Provides best-effort consistency guarantees without being a complete CP (indeed PA/EC) solution,\n* Supports replication by default (with sync and async options),\n* Quorum-based voting for replica control (Read/Write quorums),\n* Supports atomic operations,\n* Provides an iterator on distributed maps,\n* Provides a plugin interface for service discovery daemons,\n* Provides a locking primitive which inspired by [SETNX of Redis](https://redis.io/commands/setnx#design-pattern-locking-with-codesetnxcode),\n\n## Possible Use Cases\n\nOlric is an eventually consistent, unordered key/value data store. It supports various eviction mechanisms for distributed caching implementations. Olric \nalso provides publish-subscribe messaging, data replication, failure detection and simple anti-entropy services. \n\nIt's good at distributed caching and publish/subscribe messaging.\n\n## Table of Contents\n\n* [Features](#features)\n* [Support](#support)\n* [Installing](#installing)\n  * [Docker](#docker)\n  * [Kubernetes](#kubernetes)\n  * [Working with Docker Compose](#working-with-docker-compose)\n* [Getting Started](#getting-started)\n  * [Operation Modes](#operation-modes)\n    * [Embedded Member](#embedded-member)\n    * [Client-Server](#client-server)\n* [Golang Client](#golang-client)\n* [Cluster Events](#cluster-events)\n* [Commands](#commands)\n  * [Distributed Map](#distributed-map)\n    * [DM.PUT](#dmput)\n    * [DM.GET](#dmget)\n    * [DM.DEL](#dmdel)\n    * [DM.EXPIRE](#dmexpire)\n    * [DM.PEXPIRE](#dmpexpire)\n    * [DM.DESTROY](#dmdestroy)\n    * [Atomic Operations](#atomic-operations)\n      * [DM.INCR](#dmincr)\n      * [DM.DECR](#dmdecr)\n      * [DM.GETPUT](#dmgetput)\n      * [DM.INCRBYFLOAT](#dmincrbyfloat)\n    * [Locking](#locking)\n      * [DM.LOCK](#dmlock)\n      * [DM.UNLOCK](#dmunlock)\n      * [DM.LOCKLEASE](#dmlocklease)\n      * [DM.PLOCKLEASE](#dmplocklease)\n    * [DM.SCAN](#dmscan)\n  * [Publish-Subscribe](#publish-subscribe)\n    * [SUBSCRIBE](#subscribe)\n    * [PSUBSCRIBE](#psubscribe)\n    * [UNSUBSCRIBE](#unsubscribe)\n    * [PUNSUBSCRIBE](#punsubscribe)\n    * [PUBSUB CHANNELS](#pubsub-channels)\n    * [PUBSUB NUMPAT](#pubsub-numpat)\n    * [PUBSUB NUMSUB](#pubsub-numsub)\n    * [QUIT](#quit)\n    * [PING](#ping)\n  * [Cluster](#cluster)\n    * [CLUSTER.ROUTINGTABLE](#clusterroutingtable)\n    * [CLUSTER.MEMBERS](#clustermembers)\n  * [Others](#others)\n    * [PING](#ping)\n    * [STATS](#stats)\n* [Configuration](#configuration)\n    * [Embedded Member Mode](#embedded-member-mode)\n      * [Manage the configuration in YAML format](#manage-the-configuration-in-yaml-format)\n    * [Client-Server Mode](#client-server-mode)\n    * [Network Configuration](#network-configuration)\n    * [Service discovery](#service-discovery)\n    * [Timeouts](#timeouts)\n* [Architecture](#architecture)\n  * [Overview](#overview)\n  * [Consistency and Replication Model](#consistency-and-replication-model)\n    * [Last-write-wins conflict resolution](#last-write-wins-conflict-resolution)\n    * [PACELC Theorem](#pacelc-theorem)\n    * [Read-Repair on DMaps](#read-repair-on-dmaps)\n    * [Quorum-based Replica Control](#quorum-based-replica-control)\n    * [Simple Split-Brain Protection](#simple-split-brain-protection)\n  * [Eviction](#eviction)\n    * [Expire with TTL](#expire-with-ttl)\n    * [Expire with MaxIdleDuration](#expire-with-maxidleduration)\n    * [Expire with LRU](#expire-with-lru)\n  * [Lock Implementation](#lock-implementation)\n  * [Storage Engine](#storage-engine)\n* [Samples](#samples)\n* [Contributions](#contributions)\n* [License](#license)\n* [About the name](#about-the-name)\n\n\n## Features\n\n* Designed to share some transient, approximate, fast-changing data between servers,\n* Accepts arbitrary types as value,\n* Only in-memory,\n* Uses Redis protocol,\n* Compatible with existing Redis clients,\n* Embeddable but can be used as a language-independent service with olric-server,\n* GC-friendly storage engine,\n* O(1) running time for lookups,\n* Supports atomic operations,\n* Provides a lock implementation which can be used for non-critical purposes,\n* Different eviction policies: LRU, MaxIdleDuration and Time-To-Live (TTL),\n* Highly available,\n* Horizontally scalable,\n* Provides best-effort consistency guarantees without being a complete CP (indeed PA/EC) solution,\n* Distributes load fairly among cluster members with a [consistent hash function](https://github.com/buraksezer/consistent),\n* Supports replication by default (with sync and async options),\n* Quorum-based voting for replica control,\n* Thread-safe by default,\n* Provides an iterator on distributed maps,\n* Provides a plugin interface for service discovery daemons and cloud providers,\n* Provides a locking primitive which inspired by [SETNX of Redis](https://redis.io/commands/setnx#design-pattern-locking-with-codesetnxcode),\n* Provides a drop-in replacement of Redis' Publish-Subscribe messaging feature.\n\nSee [Architecture](#architecture) section to see details.\n\n## Support\n\nWe have a few communication channels: \n\n* [Issue Tracker](https://github.com/olric-data/olric/issues)\n* [Discord server](https://discord.gg/ahK7Vjr8We)\n\nYou should know that the issue tracker is only intended for bug reports and feature requests.\n\nSoftware doesn't maintain itself. If you need support on complex topics or request new features, please consider [sponsoring Olric](https://github.com/sponsors/buraksezer).\n\n## Installing\n\nWith a correctly configured Golang environment:\n\n```\ngo install github.com/olric-data/olric/cmd/olric-server@v0.5.7\n```\n\nNow you can start using Olric:\n\n```\nolric-server -c cmd/olric-server/olric-server-local.yaml\n```\n\nSee [Configuration](#configuration) section to create your cluster properly.\n\n### Docker\n\nYou can launch `olric-server` Docker container by running the following command. \n\n```bash\ndocker run -p 3320:3320 olricio/olric-server:v0.5.4\n``` \n\nThis command will pull olric-server Docker image and run a new Olric Instance. You should know that the container exposes \n`3320` and `3322` ports. \n\nNow, you can access an Olric cluster using any Redis client including `redis-cli`:\n\n```bash\nredis-cli -p 3320\n127.0.0.1:3320\u003e DM.PUT my-dmap my-key \"Olric Rocks!\"\nOK\n127.0.0.1:3320\u003e DM.GET my-dmap my-key\n\"Olric Rocks!\"\n127.0.0.1:3320\u003e\n```\n\n## Getting Started\n\nWith olric-server, you can create an Olric cluster with a few commands. This is how to install olric-server:\n\n```bash\ngo install github.com/olric-data/olric/cmd/olric-server@v0.5.7\n```\n\nLet's create a cluster with the following:\n\n```\nolric-server -c \u003cYOUR_CONFIG_FILE_PATH\u003e\n```\n\nYou can find the sample configuration file under `cmd/olric-server/olric-server-local.yaml`. It can perfectly run with single node. \nolric-server also supports `OLRIC_SERVER_CONFIG` environment variable to set configuration. Just like that: \n\n```\nOLRIC_SERVER_CONFIG=\u003cYOUR_CONFIG_FILE_PATH\u003e olric-server\n```\n\nOlric uses [hashicorp/memberlist](https://github.com/hashicorp/memberlist) for failure detection and cluster membership. \nCurrently, there are different ways to discover peers in a cluster. You can use a static list of nodes in your configuration. \nIt's ideal for development and test environments. Olric also supports Consul, Kubernetes and all well-known cloud providers\nfor service discovery. Please take a look at [Service Discovery](#service-discovery) section for further information.\n\nSee [Client-Server](#client-server) section to get more information about this deployment scenario.\n\n#### Maintaining a list of peers manually\n\nBasically, there is a list of nodes under `memberlist` block in the configuration file. In order to create an Olric cluster, \nyou just need to add `Host:Port` pairs of the other nodes. Please note that the `Port` is the memberlist port of the peer.\nIt is `3322` by default. \n\n```yaml\nmemberlist:\n  peers:\n    - \"localhost:3322\"\n```\n\nThanks to [hashicorp/memberlist](https://github.com/hashicorp/memberlist), Olric nodes can share the full list of members \nwith each other. So an Olric node can discover the whole cluster by using a single member address.\n\n#### Embedding into your Go application.\n\nSee [Samples](#samples) section to learn how to embed Olric into your existing Golang application.\n\n### Operation Modes\n\nOlric has two different operation modes.\n\n#### Embedded Member\n\nIn Embedded Member Mode, members include both the application and Olric data and services. The advantage of the Embedded\nMember Mode is having a low-latency data access and locality.\n\n#### Client-Server\n\nIn Client-Server Mode, Olric data and services are centralized in one or more servers, and they are accessed by the \napplication through clients. You can have a cluster of servers that can be independently created and scaled. Your clients \ncommunicate with these members to reach to Olric data and services on them.\n\nClient-Server deployment has advantages including more predictable and reliable performance, easier identification\nof problem causes and, most importantly, better scalability. When you need to scale in this deployment type, just add more\nOlric server members. You can address client and server scalability concerns separately.\n\n## Golang Client\n\nThe official Golang client is defined by the `Client` interface. There are two different implementations of that interface in \nthis repository. `EmbeddedClient` provides a client implementation for [embedded-member](#embedded-member) scenario, \n`ClusterClient` provides an implementation of the same interface for [client-server](#client-server) deployment scenario. \nObviously, you can use `ClusterClient` for your embedded-member deployments. But it's good to use `EmbeddedClient` provides \na better performance due to localization of the queries.\n\nSee the client documentation on [pkg.go.dev](https://pkg.go.dev/github.com/olric-data/olric/@v0.5.7)\n\n## Cluster Events\n\nOlric can send push cluster events to `cluster.events` channel. Available cluster events:\n\n* node-join-event\n* node-left-event\n* fragment-migration-event\n* fragment-received-even\n\nIf you want to receive these events, set `true` to `EnableClusterEventsChannel` and subscribe to `cluster.events` channel. \nThe default is `false`.\n\nSee [events/cluster_events.go](events/cluster_events.go) file to get more information about events.\n\n## Commands\n\nOlric uses Redis protocol and supports Redis-style commands to query the database. You can use any Redis client, including\n`redis-cli`. The official Go client is a thin layer around [go-redis/redis](https://github.com/go-redis/redis) package. \nSee [Golang Client](#golang-client) section for the documentation.\n\n### Distributed Map\n\n#### DM.PUT \n\nDM.PUT sets the value for the given key. It overwrites any previous value for that key.\n\n```\nDM.PUT dmap key value [ EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds ] [ NX | XX]\n```\n\n**Example:**\n```\n127.0.0.1:3320\u003e DM.PUT my-dmap my-key value\nOK\n```\n\n**Options:**\n\nThe DM.PUT command supports a set of options that modify its behavior:\n\n* **EX** *seconds* -- Set the specified expire time, in seconds.\n* **PX** *milliseconds* -- Set the specified expire time, in milliseconds.\n* **EXAT** *timestamp-seconds* -- Set the specified Unix time at which the key will expire, in seconds.\n* **PXAT** *timestamp-milliseconds* -- Set the specified Unix time at which the key will expire, in milliseconds.\n* **NX** -- Only set the key if it does not already exist.\n* **XX** -- Only set the key if it already exist.\n\n**Return:**\n\n* **Simple string reply:** OK if DM.PUT was executed correctly.\n* **KEYFOUND:** (error) if the DM.PUT operation was not performed because the user specified the NX option but the condition was not met.\n* **KEYNOTFOUND:** (error) if the DM.PUT operation was not performed because the user specified the XX option but the condition was not met.\n\n#### DM.GET\n\nDM.GET gets the value for the given key. It returns (error)`KEYNOTFOUND` if the key doesn't exist. \n\n```\nDM.GET dmap key\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.GET dmap key\n\"value\"\n```\n\n**Return:**\n\n**Bulk string reply**: the value of key, or (error)`KEYNOTFOUND` when key does not exist.\n\n#### DM.DEL\n\nDM.DEL deletes values for the given keys. It doesn't return any error if the key does not exist.\n\n```\nDM.DEL dmap key [key...]\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.DEL dmap key1 key2\n(integer) 2\n```\n\n**Return:**\n\n* **Integer reply**: The number of keys that were removed.\n\n#### DM.EXPIRE\n\nDM.EXPIRE updates or sets the timeout for the given key. It returns `KEYNOTFOUND` if the key doesn't exist. After the timeout has expired, \nthe key will automatically be deleted. \n\nThe timeout will only be cleared by commands that delete or overwrite the contents of the key, including DM.DEL, DM.PUT, DM.GETPUT.\n\n```\nDM.EXPIRE dmap key seconds\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.EXPIRE dmap key 1\nOK\n```\n\n**Return:**\n\n* **Simple string reply:** OK if DM.EXPIRE was executed correctly.\n* **KEYNOTFOUND:** (error) when key does not exist.\n\n#### DM.PEXPIRE\n\nDM.PEXPIRE updates or sets the timeout for the given key. It returns `KEYNOTFOUND` if the key doesn't exist. After the timeout has expired,\nthe key will automatically be deleted.\n\nThe timeout will only be cleared by commands that delete or overwrite the contents of the key, including DM.DEL, DM.PUT, DM.GETPUT.\n\n```\nDM.PEXPIRE dmap key milliseconds\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.PEXPIRE dmap key 1000\nOK\n```\n\n**Return:**\n\n* **Simple string reply:** OK if DM.EXPIRE was executed correctly.\n* **KEYNOTFOUND:** (error) when key does not exist.\n\n#### DM.DESTROY\n\nDM.DESTROY flushes the given DMap on the cluster. You should know that there is no global lock on DMaps. DM.PUT and DM.DESTROY commands\nmay run concurrently on the same DMap. \n\n```\nDM.DESTROY dmap\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.DESTROY dmap\nOK\n```\n\n**Return:**\n\n* **Simple string reply:** OK, if DM.DESTROY was executed correctly.\n\n### Atomic Operations\n\nOperations on key/value pairs are performed by the partition owner. In addition, atomic operations are guarded by a lock implementation which can be found under `internal/locker`. It means that\nOlric guaranties consistency of atomic operations, if there is no network partition. Basic flow for `DM.INCR`:\n\n* Acquire the lock for the given key,\n* Call `DM.GET` to retrieve the current value,\n* Calculate the new value,\n* Call `DM.PUT` to set the new value,\n* Release the lock.\n\nIt's important to know that if you call `DM.PUT` and `DM.GETPUT` concurrently on the same key, this will break the atomicity.\n\n`internal/locker` package is provided by [Docker](https://github.com/moby/moby).\n\n**Important note about consistency:**\n\nYou should know that Olric is a PA/EC (see [Consistency and Replication Model](#consistency-and-replication-model)) product. So if your network is stable, all the operations on key/value\npairs are performed by a single cluster member. It means that you can be sure about the consistency when the cluster is stable. It's important to know that computer networks fail\noccasionally, processes crash and random GC pauses may happen. Many factors can lead a network partitioning. If you cannot tolerate losing strong consistency under network partitioning,\nyou need to use a different tool for atomic operations.\n\nSee [Hazelcast and the Mythical PA/EC System](https://dbmsmusings.blogspot.com/2017/10/hazelcast-and-mythical-paec-system.html) and [Jepsen Analysis on Hazelcast 3.8.3](https://hazelcast.com/blog/jepsen-analysis-hazelcast-3-8-3/) for more insight on this topic.\n\n\n#### DM.INCR\n\nDM.INCR atomically increments the number stored at key by delta. The return value is the new value after being incremented or an error.\n\n```\nDM.INCR dmap key delta\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.INCR dmap key 10\n(integer) 10\n```\n\n**Return:**\n\n* **Integer reply:** the value of key after the increment.\n\n#### DM.DECR\n\nDM.DECR atomically decrements the number stored at key by delta. The return value is the new value after being incremented or an error.\n\n```\nDM.DECR dmap key delta\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.DECR dmap key 10\n(integer) 0\n```\n\n**Return:**\n\n* **Integer reply:** the value of key after the increment.\n\n#### DM.GETPUT\n\nDM.GETPUT atomically sets key to value and returns the old value stored at the key.\n\n```\nDM.GETPUT dmap key value\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.GETPUT dmap key value-1\n(nil)\n127.0.0.1:3320\u003e DM.GETPUT dmap key value-2\n\"value-1\"\n```\n\n**Return:**\n\n* **Bulk string reply**: the old value stored at the key.\n\n#### DM.INCRBYFLOAT\n\nDM.INCRBYFLOAT atomically increments the number stored at key by delta. The return value is the new value after being incremented or an error.\n\n```\nDM.INCRBYFLOAT dmap key delta\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.PUT dmap key 10.50\nOK\n127.0.0.1:3320\u003e DM.INCRBYFLOAT dmap key 0.1\n\"10.6\"\n127.0.0.1:3320\u003e DM.PUT dmap key 5.0e3\nOK\n127.0.0.1:3320\u003e DM.INCRBYFLOAT dmap key 2.0e2\n\"5200\"\n```\n\n**Return:**\n\n* **Bulk string reply**: the value of key after the increment.\n\n\n### Locking\n\n**Important:** The lock provided by DMap implementation is approximate and only to be used for non-critical purposes.\n\nThe DMap implementation is already thread-safe to meet your thread safety requirements. When you want to have more control on the\nconcurrency, you can use **DM.LOCK** command. Olric borrows the locking algorithm from Redis. Redis authors propose\nthe following algorithm:\n\n\u003e The command \u003cSET resource-name anystring NX EX max-lock-time\u003e is a simple way to implement a locking system with Redis.\n\u003e\n\u003e A client can acquire the lock if the above command returns OK (or retry after some time if the command returns Nil), and remove the lock just using DEL.\n\u003e\n\u003e The lock will be auto-released after the expire time is reached.\n\u003e\n\u003e It is possible to make this system more robust modifying the unlock schema as follows:\n\u003e\n\u003e Instead of setting a fixed string, set a non-guessable large random string, called token.\n\u003e Instead of releasing the lock with DEL, send a script that only removes the key if the value matches.\n\u003e This avoids that a client will try to release the lock after the expire time deleting the key created by another client that acquired the lock later.\n\nEquivalent of `SETNX` command in Olric is `DM.PUT dmap key value NX`. DM.LOCK command are properly implements\nthe algorithm which is proposed above.\n\nYou should know that this implementation is subject to the clustering algorithm. So there is no guarantee about reliability in the case of network partitioning. I recommend the lock implementation to be used for\nefficiency purposes in general, instead of correctness.\n\n**Important note about consistency:**\n\nYou should know that Olric is a PA/EC (see [Consistency and Replication Model](#consistency-and-replication-model)) product. So if your network is stable, all the operations on key/value\npairs are performed by a single cluster member. It means that you can be sure about the consistency when the cluster is stable. It's important to know that computer networks fail\noccasionally, processes crash and random GC pauses may happen. Many factors can lead a network partitioning. If you cannot tolerate losing strong consistency under network partitioning,\nyou need to use a different tool for locking.\n\nSee [Hazelcast and the Mythical PA/EC System](https://dbmsmusings.blogspot.com/2017/10/hazelcast-and-mythical-paec-system.html) and [Jepsen Analysis on Hazelcast 3.8.3](https://hazelcast.com/blog/jepsen-analysis-hazelcast-3-8-3/) for more insight on this topic.\n\n#### DM.LOCK\n\nDM.LOCK sets a lock for the given key. The acquired lock is only valid for the key in this DMap.\nIt returns immediately if it acquires the lock for the given key. Otherwise, it waits until deadline.\n\nDM.LOCK returns a token. You must keep that token to unlock the key. Using prefixed keys is highly recommended.\nIf the key does already exist in the DMap, DM.LOCK will wait until the deadline is exceeded.\n\n```\nDM.LOCK dmap key seconds [ EX seconds | PX milliseconds ]\n```\n\n**Options:**\n\n* **EX** *seconds* -- Set the specified expire time, in seconds.\n* **PX** *milliseconds* -- Set the specified expire time, in milliseconds.\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.LOCK dmap lock.key 10\n2363ec600be286cb10fbb35181efb029\n```\n\n**Return:**\n\n* **Simple string reply:** a token to unlock or lease the lock.\n* **NOSUCHLOCK**: (error) returned when the requested lock does not exist.\n* **LOCKNOTACQUIRED**: (error) returned when the requested lock could not be acquired.\n\n#### DM.UNLOCK\n\nDM.UNLOCK releases an acquired lock for the given key. It returns `NOSUCHLOCK` if there is no lock for the given key.\n\n```\nDM.UNLOCK dmap key token\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.UNLOCK dmap key 2363ec600be286cb10fbb35181efb029\nOK\n```\n\n**Return:**\n\n* **Simple string reply:** OK if DM.UNLOCK was executed correctly.\n* **NOSUCHLOCK**: (error) returned when the lock does not exist.\n\n#### DM.LOCKLEASE\n\nDM.LOCKLEASE sets or updates the timeout of the acquired lock for the given key. It returns `NOSUCHLOCK` if there is no lock for the given key.\n\nDM.LOCKLEASE accepts seconds as timeout.\n\n```\nDM.LOCKLEASE dmap key token seconds\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.LOCKLEASE dmap key 2363ec600be286cb10fbb35181efb029 100\nOK\n```\n\n**Return:**\n\n* **Simple string reply:** OK if DM.UNLOCK was executed correctly.\n* **NOSUCHLOCK**: (error) returned when the lock does not exist.\n\n#### DM.PLOCKLEASE\n\nDM.PLOCKLEASE sets or updates the timeout of the acquired lock for the given key. It returns `NOSUCHLOCK` if there is no lock for the given key.\n\nDM.PLOCKLEASE accepts milliseconds as timeout.\n\n```\nDM.LOCKLEASE dmap key token milliseconds\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.PLOCKLEASE dmap key 2363ec600be286cb10fbb35181efb029 1000\nOK\n```\n\n**Return:**\n\n* **Simple string reply:** OK if DM.PLOCKLEASE was executed correctly.\n* **NOSUCHLOCK**: (error) returned when the lock does not exist.\n\n#### DM.SCAN\n\nDM.SCAN is a cursor based iterator. This means that at every call of the command, the server returns an updated cursor \nthat the user needs to use as the cursor argument in the next call.\n\nAn iteration starts when the cursor is set to 0, and terminates when the cursor returned by the server is 0. The iterator runs\nlocally on every partition. So you need to know the partition count. If the returned cursor is 0 for a particular partition,\nyou have to start scanning the next partition. \n\n```\nDM.SCAN partID dmap cursor [ MATCH pattern | COUNT count ]\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e DM.SCAN 3 bench 0\n1) \"96990\"\n2)  1) \"memtier-2794837\"\n    2) \"memtier-8630933\"\n    3) \"memtier-6415429\"\n    4) \"memtier-7808686\"\n    5) \"memtier-3347072\"\n    6) \"memtier-4247791\"\n    7) \"memtier-3931982\"\n    8) \"memtier-7164719\"\n    9) \"memtier-4710441\"\n   10) \"memtier-8892916\"\n127.0.0.1:3320\u003e DM.SCAN 3 bench 96990\n1) \"193499\"\n2)  1) \"memtier-429905\"\n    2) \"memtier-1271812\"\n    3) \"memtier-7835776\"\n    4) \"memtier-2717575\"\n    5) \"memtier-95312\"\n    6) \"memtier-2155214\"\n    7) \"memtier-123931\"\n    8) \"memtier-2902510\"\n    9) \"memtier-2632291\"\n   10) \"memtier-1938450\"\n```\n### Publish-Subscribe\n\n**SUBSCRIBE**, **UNSUBSCRIBE** and **PUBLISH** implement the Publish/Subscribe messaging paradigm where \nsenders are not programmed to send their messages to specific receivers. Rather, published messages are characterized \ninto channels, without knowledge of what (if any) subscribers there may be. Subscribers express interest in one or more \nchannels, and only receive messages that are of interest, without knowledge of what (if any) publishers there are. \nThis decoupling of publishers and subscribers can allow for greater scalability and a more dynamic network topology.\n\n**Important note:** In an Olric cluster, clients can subscribe to every node, and can also publish to every other node. The cluster\nwill make sure that published messages are forwarded as needed.\n\n*Source of this section: [https://redis.io/commands/?group=pubsub](https://redis.io/commands/?group=pubsub)*\n\n#### SUBSCRIBE\n\nSubscribes the client to the specified channels.\n\n```\nSUBSCRIBE channel [channel...]\n```\n\nOnce the client enters the subscribed state it is not supposed to issue any other commands, except for additional **SUBSCRIBE**, \n**PSUBSCRIBE**, **UNSUBSCRIBE**, **PUNSUBSCRIBE**, **PING**, and **QUIT** commands.\n\n#### PSUBSCRIBE\n\nSubscribes the client to the given patterns.\n\n```\nPSUBSCRIBE pattern [ pattern ...]\n```\n\nSupported glob-style patterns:\n\n* `h?llo` subscribes to hello, hallo and hxllo\n* `h*llo` subscribes to hllo and heeeello\n* `h[ae]llo` subscribes to hello and hallo, but not hillo\n* Use **\\\\** to escape special characters if you want to match them verbatim.\n\n#### UNSUBSCRIBE\n\nUnsubscribes the client from the given channels, or from all of them if none is given.\n\n```\nUNSUBSCRIBE [channel [channel ...]]\n```\n\nWhen no channels are specified, the client is unsubscribed from all the previously subscribed channels. In this case, \na message for every unsubscribed channel will be sent to the client.\n\n#### PUNSUBSCRIBE\n\nUnsubscribes the client from the given patterns, or from all of them if none is given.\n\n```\nPUNSUBSCRIBE [pattern [pattern ...]]\n```\n\nWhen no patterns are specified, the client is unsubscribed from all the previously subscribed patterns. In this case, \na message for every unsubscribed pattern will be sent to the client.\n\n#### PUBSUB CHANNELS\n\nLists the currently active channels.\n\n```\nPUBSUB CHANNELS [pattern]\n```\n\nAn active channel is a Pub/Sub channel with one or more subscribers (excluding clients subscribed to patterns).\n\nIf no pattern is specified, all the channels are listed, otherwise if pattern is specified only channels matching the \nspecified glob-style pattern are listed.\n\n#### PUBSUB NUMPAT\n\nReturns the number of unique patterns that are subscribed to by clients (that are performed using the PSUBSCRIBE command).\n\n```\nPUBSUB NUMPAT\n```\n\nNote that this isn't the count of clients subscribed to patterns, but the total number of unique patterns all the clients are subscribed to.\n\n**Important note**: In an Olric cluster, clients can subscribe to every node, and can also publish to every other node. The cluster \nwill make sure that published messages are forwarded as needed. That said, PUBSUB's replies in a cluster only report information \nfrom the node's Pub/Sub context, rather than the entire cluster.\n\n#### PUBSUB NUMSUB\n\nReturns the number of subscribers (exclusive of clients subscribed to patterns) for the specified channels.\n\n```\nPUBSUB NUMSUB [channel [channel ...]]\n```\nNote that it is valid to call this command without channels. In this case it will just return an empty list.\n\n**Important note**: In an Olric cluster, clients can subscribe to every node, and can also publish to every other node. The cluster \nwill make sure that published messages are forwarded as needed. That said, PUBSUB's replies in a cluster only report information \nfrom the node's Pub/Sub context, rather than the entire cluster.\n\n#### QUIT\n\nAsk the server to close the connection. The connection is closed as soon as all pending replies have been written to the client.\n\n```\nQUIT\n```\n### Cluster\n\n#### CLUSTER.ROUTINGTABLE\n\nCLUSTER.ROUTINGTABLE returns the latest view of the routing table. Simply, it's a data structure that maps\npartitions to members.\n\n```\nCLUSTER.ROUTINGTABLE\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e CLUSTER.ROUTINGTABLE\n 1) 1) (integer) 0\n     2) 1) \"127.0.0.1:3320\"\n     3) (empty array)\n  2) 1) (integer) 1\n     2) 1) \"127.0.0.1:3320\"\n     3) (empty array)\n  3) 1) (integer) 2\n     2) 1) \"127.0.0.1:3320\"\n     3) (empty array)\n```\n\nIt returns an array of arrays. \n\n**Fields:**\n\n```\n1) (integer) 0 \u003c- Partition ID\n  2) 1) \"127.0.0.1:3320\" \u003c- Array of the current and previous primary owners\n  3) (empty array) \u003c- Array of backup owners. \n```\n\n#### CLUSTER.MEMBERS\n\nCLUSTER.MEMBERS returns an array of known members by the server.\n\n```\nCLUSTER.MEMBERS\n```\n\n**Example:**\n\n```\n127.0.0.1:3320\u003e CLUSTER.MEMBERS\n1) 1) \"127.0.0.1:3320\"\n   2) (integer) 1652619388427137000\n   3) \"true\"\n```\n\n**Fields:**\n\n```\n1) 1) \"127.0.0.1:3320\" \u003c- Member's name in the cluster\n   2) (integer) 1652619388427137000 \u003c-Member's birthedate\n   3) \"true\" \u003c- Is cluster coordinator (the oldest node)\n```\n\n### Others\n\n#### PING\n\nReturns PONG if no argument is provided, otherwise return a copy of the argument as a bulk. This command is often used to\ntest if a connection is still alive, or to measure latency.\n\n```\nPING\n```\n\n#### STATS\n\nThe STATS command returns information and statistics about the server in JSON format. See `stats/stats.go` file.\n\n## Configuration\n\nOlric supports both declarative and programmatic configurations. You can choose one of them depending on your needs.\nYou should feel free to ask any questions about configuration and integration. Please see [Support](#support) section.\n\n### Embedded-Member Mode\n\n#### Programmatic Configuration\nOlric provides a function to generate default configuration to use in embedded-member mode:\n\n```go\nimport \"github.com/olric-data/olric/config\"\n...\nc := config.New(\"local\")\n```\n\nThe `New` function takes a parameter called `env`. It denotes the network environment and consumed by [hashicorp/memberlist](https://github.com/hashicorp/memberlist). \nDefault configuration is good enough for distributed caching scenario. In order to see all configuration parameters, please take a look at [this](https://godoc.org/github.com/olric-data/olric/config).\n\nSee [Sample Code](#sample-code) section for an introduction.\n\n#### Declarative configuration with YAML format\n\nYou can also import configuration from a YAML file by using the `Load` function:\n\n```go\nc, err := config.Load(path/to/olric.yaml)\n```\n\nA sample configuration file in YAML format can be found [here](https://github.com/olric-data/olric/blob/master/cmd/olric-server/olric-server.yaml). This may be the most appropriate way to manage the Olric configuration.\n\n\n### Client-Server Mode\n\nOlric provides **olric-server** to implement client-server mode. olric-server gets a YAML file for the configuration. The most basic  functionality of olric-server is that \ntranslating YAML configuration into Olric's configuration struct. A sample `olric-server.yaml` file  is being provided [here](https://github.com/olric-data/olric/blob/master/cmd/olric-server/olric-server.yaml).\n\n### Network Configuration\n\nIn an Olric instance, there are two different TCP servers. One for Olric, and the other one is for memberlist. `BindAddr` is very\ncritical to deploy a healthy Olric node. There are different scenarios:\n\n* You can freely set a domain name or IP address as `BindAddr` for both Olric and memberlist. Olric will resolve and use it to bind.\n* You can freely set `localhost`, `127.0.0.1` or `::1` as `BindAddr` in development environment for both Olric and memberlist.\n* You can freely set `0.0.0.0` as `BindAddr` for both Olric and memberlist. Olric will pick an IP address, if there is any.\n* If you don't set `BindAddr`, hostname will be used, and it will be resolved to get a valid IP address.\n* You can set a network interface by using `Config.Interface` and `Config.MemberlistInterface` fields. Olric will find an appropriate IP address for the given interfaces, if there is any.\n* You can set both `BindAddr` and interface parameters. In this case Olric will ensure that `BindAddr` is available on the given interface.\n\nYou should know that Olric needs a single and stable IP address to function properly. If you don't know the IP address of the host at the deployment time, \nyou can set `BindAddr` as `0.0.0.0`. Olric will very likely to find an IP address for you.\n\n### Service Discovery\n\nOlric provides a service discovery interface which can be used to implement plugins. \n\nWe currently have a bunch of service discovery plugins for automatic peer discovery on cloud environments:\n\n* [olric-data/olric-consul-plugin](https://github.com/olric-data/olric-consul-plugin) provides a plugin using Consul.\n* [olric-data/olric-cloud-plugin](https://github.com/olric-data/olric-cloud-plugin) provides a plugin for well-known cloud providers. Including Kubernetes.\n* [justinfx/olric-nats-plugin](https://github.com/justinfx/olric-nats-plugin) provides a plugin using nats.io\n\nIn order to get more info about installation and configuration of the plugins, see their GitHub page. \n\n### Timeouts\n\nOlric nodes supports setting `KeepAlivePeriod` on TCP sockets. \n\n**Server-side:**\n\n##### config.KeepAlivePeriod \n\nKeepAlivePeriod denotes whether the operating system should send keep-alive messages on the connection.\n\n**Client-side:**\n \n##### config.DialTimeout\n\nTimeout for TCP dial. The timeout includes name resolution, if required. When using TCP, and the host in the address \nparameter resolves to multiple IP addresses, the timeout is spread over each consecutive dial, such that each is\ngiven an appropriate fraction of the time to connect.\n\n##### config.ReadTimeout\n\nTimeout for socket reads. If reached, commands will fail with a timeout instead of blocking. Use value -1 for no \ntimeout and 0 for default. The default is config.DefaultReadTimeout\n\n##### config.WriteTimeout\n\nTimeout for socket writes. If reached, commands will fail with a timeout instead of blocking. The default is config.DefaultWriteTimeout\n\n## Architecture\n\n### Overview\n\nOlric uses:\n* [hashicorp/memberlist](https://github.com/hashicorp/memberlist) for cluster membership and failure detection,\n* [buraksezer/consistent](https://github.com/buraksezer/consistent) for consistent hashing and load balancing,\n* [Redis Serialization Protocol](https://github.com/tidwall/redcon) for communication.\n\nOlric distributes data among partitions. Every partition is being owned by a cluster member and may have one or more backups for redundancy. \nWhen you read or write a DMap entry, you transparently talk to the partition owner. Each request hits the most up-to-date version of a\nparticular data entry in a stable cluster.\n\nIn order to find the partition which the key belongs to, Olric hashes the key and mod it with the number of partitions:\n\n```\npartID = MOD(hash result, partition count)\n```\n\nThe partitions are being distributed among cluster members by using a consistent hashing algorithm. In order to get details, please see [buraksezer/consistent](https://github.com/buraksezer/consistent). \n\nWhen a new cluster is created, one of the instances is elected as the **cluster coordinator**. It manages the partition table: \n\n* When a node joins or leaves, it distributes the partitions and their backups among the members again,\n* Removes empty previous owners from the partition owners list,\n* Pushes the new partition table to all the members,\n* Pushes the partition table to the cluster periodically.\n\nMembers propagate their birthdate(POSIX time in nanoseconds) to the cluster. The coordinator is the oldest member in the cluster.\nIf the coordinator leaves the cluster, the second oldest member gets elected as the coordinator.\n\nOlric has a component called **rebalancer** which is responsible for keeping underlying data structures consistent:\n\n* Works on every node,\n* When a node joins or leaves, the cluster coordinator pushes the new partition table. Then, the **rebalancer** runs immediately and moves the partitions and backups to their new hosts,\n* Merges fragmented partitions.\n\nPartitions have a concept called **owners list**. When a node joins or leaves the cluster, a new primary owner may be assigned by the \ncoordinator. At any time, a partition may have one or more partition owners. If a partition has two or more owners, this is called **fragmented partition**. \nThe last added owner is called **primary owner**. Write operation is only done by the primary owner. The previous owners are only used for read and delete.\n\nWhen you read a key, the primary owner tries to find the key on itself, first. Then, queries the previous owners and backups, respectively.\nThe delete operation works the same way.\n\nThe data(distributed map objects) in the fragmented partition is moved slowly to the primary owner by the **rebalancer**. Until the move is done,\nthe data remains available on the previous owners. The DMap methods use this list to query data on the cluster.\n\n*Please note that, 'multiple partition owners' is an undesirable situation and the **rebalancer** component is designed to fix that in a short time.*\n\n### Consistency and Replication Model\n\n**Olric is an AP product** in the context of [CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem), which employs the combination of primary-copy \nand [optimistic replication](https://en.wikipedia.org/wiki/Optimistic_replication) techniques. With optimistic replication, when the partition owner \nreceives a write or delete operation for a key, applies it locally, and propagates it to the backup owners.\n\nThis technique enables Olric clusters to offer high throughput. However, due to temporary situations in the system, such as network\nfailure, backup owners can miss some updates and diverge from the primary owner. If a partition owner crashes while there is an\ninconsistency between itself and the backups, strong consistency of the data can be lost.\n\nTwo types of backup replication are available: **sync** and **async**. Both types are still implementations of the optimistic replication\nmodel.\n\n* **sync**: Blocks until write/delete operation is applied by backup owners.\n* **async**: Just fire \u0026 forget.\n\n#### Last-write-wins conflict resolution\n\nEvery time a piece of data is written to Olric, a timestamp is attached by the client. Then, when Olric has to deal with conflict data in the case \nof network partitioning, it simply chooses the data with the most recent timestamp. This called LWW conflict resolution policy.\n\n#### PACELC Theorem\n\nFrom Wikipedia:\n\n\u003e In theoretical computer science, the [PACELC theorem](https://en.wikipedia.org/wiki/PACELC_theorem) is an extension to the [CAP theorem](https://en.wikipedia.org/wiki/CAP_theorem). It states that in case of network partitioning (P) in a \n\u003e distributed computer system, one has to choose between availability (A) and consistency (C) (as per the CAP theorem), but else (E), even when the system is \n\u003e running normally in the absence of partitions, one has to choose between latency (L) and consistency (C).\n\nIn the context of PACELC theorem, Olric is a **PA/EC** product. It means that Olric is considered to be **consistent** data store if the network is stable. \nBecause the key space is divided between partitions and every partition is controlled by its primary owner. All operations on DMaps are redirected to the \npartition owner. \n\nIn the case of network partitioning, Olric chooses **availability** over consistency. So that you can still access some parts of the cluster when the network is unreliable, \nbut the cluster may return inconsistent results.  \n\nOlric implements read-repair and quorum based voting system to deal with inconsistencies in the DMaps. \n\nReadings on PACELC theorem:\n* [Please stop calling databases CP or AP](https://martin.kleppmann.com/2015/05/11/please-stop-calling-databases-cp-or-ap.html)\n* [Problems with CAP, and Yahoo’s little known NoSQL system](https://dbmsmusings.blogspot.com/2010/04/problems-with-cap-and-yahoos-little.html)\n* [A Critique of the CAP Theorem](https://arxiv.org/abs/1509.05393)\n* [Hazelcast and the Mythical PA/EC System](https://dbmsmusings.blogspot.com/2017/10/hazelcast-and-mythical-paec-system.html)\n\n#### Read-Repair on DMaps\n\nRead repair is a feature that allows for inconsistent data to be fixed at query time. Olric tracks every write operation with a timestamp value and assumes \nthat the latest write operation is the valid one. When you want to access a key/value pair, the partition owner retrieves all available copies for that pair\nand compares the timestamp values. The latest one is the winner. If there is some outdated version of the requested pair, the primary owner propagates the latest\nversion of the pair. \n\nRead-repair is disabled by default for the sake of performance. If you have a use case that requires a more strict consistency control than a distributed caching \nscenario, you can enable read-repair via the configuration. \n\n#### Quorum-based replica control\n\nOlric implements Read/Write quorum to keep the data in a consistent state. When you start a write operation on the cluster and write quorum (W) is 2, \nthe partition owner tries to write the given key/value pair on its own data storage and on the replica nodes. If the number of successful write operations \nis below W, the primary owner returns `ErrWriteQuorum`. The read flow is the same: if you have R=2 and the owner only access one of the replicas, \nit returns `ErrReadQuorum`.\n\n#### Simple Split-Brain Protection\n\nOlric implements a technique called *majority quorum* to manage split-brain conditions. If a network partitioning occurs, and some members\nlost the connection to rest of the cluster, they immediately stops functioning and return an error to incoming requests. This behaviour is controlled by\n`MemberCountQuorum` parameter. It's default `1`. \n\nWhen the network healed, the stopped nodes joins again the cluster and fragmented partitions is merged by their primary owners in accordance with \n*LWW policy*. Olric also implements an *ownership report* mechanism to fix inconsistencies in partition distribution after a partitioning event. \n\n### Eviction\nOlric supports different policies to evict keys from distributed maps. \n\n#### Expire with TTL\nOlric implements TTL eviction policy. It shares the same algorithm with [Redis](https://redis.io/commands/expire#appendix-redis-expires):\n\n\u003e Periodically Redis tests a few keys at random among keys with an expire set. All the keys that are already expired are deleted from the keyspace.\n\u003e\n\u003e Specifically this is what Redis does 10 times per second:\n\u003e\n\u003e * Test 20 random keys from the set of keys with an associated expire.\n\u003e * Delete all the keys found expired.\n\u003e * If more than 25% of keys were expired, start again from step 1.\n\u003e\n\u003e This is a trivial probabilistic algorithm, basically the assumption is that our sample is representative of the whole key space, and we continue to expire until the percentage of keys that are likely to be expired is under 25%\n\nWhen a client tries to access a key, Olric returns `ErrKeyNotFound` if the key is found to be timed out. A background task evicts keys with the algorithm described above.\n\n#### Expire with MaxIdleDuration\n\nMaximum time for each entry to stay idle in the DMap. It limits the lifetime of the entries relative to the time of the last read \nor write access performed on them. The entries whose idle period exceeds this limit are expired and evicted automatically. \nAn entry is idle if no Get, Put, PutEx, Expire, PutIf, PutIfEx on it. Configuration of MaxIdleDuration feature varies by \npreferred deployment method. \n\n#### Expire with LRU\n\nOlric implements LRU eviction method on DMaps. Approximated LRU algorithm is borrowed from Redis. The Redis authors proposes the following algorithm:\n\n\u003e It is important to understand that the eviction process works like this:\n\u003e \n\u003e * A client runs a new command, resulting in more data added.\n\u003e * Redis checks the memory usage, and if it is greater than the maxmemory limit , it evicts keys according to the policy.\n\u003e * A new command is executed, and so forth.\n\u003e\n\u003e So we continuously cross the boundaries of the memory limit, by going over it, and then by evicting keys to return back under the limits.\n\u003e\n\u003e If a command results in a lot of memory being used (like a big set intersection stored into a new key) for some time the memory \n\u003e limit can be surpassed by a noticeable amount. \n\u003e\n\u003e **Approximated LRU algorithm**\n\u003e\n\u003e Redis LRU algorithm is not an exact implementation. This means that Redis is not able to pick the best candidate for eviction, \n\u003e that is, the access that was accessed the most in the past. Instead it will try to run an approximation of the LRU algorithm, \n\u003e by sampling a small number of keys, and evicting the one that is the best (with the oldest access time) among the sampled keys.\n\nOlric tracks access time for every DMap instance. Then it picks and sorts some configurable amount of keys to select keys for eviction.\nEvery node runs this algorithm independently. The access log is moved along with the partition when a network partition is occured.\n\n#### Configuration of eviction mechanisms\n\nHere is a simple configuration block for `olric-server.yaml`: \n\n```\ncache:\n  numEvictionWorkers: 1\n  maxIdleDuration: \"\"\n  ttlDuration: \"100s\"\n  maxKeys: 100000\n  maxInuse: 1000000 # in bytes\n  lRUSamples: 10\n  evictionPolicy: \"LRU\" # NONE/LRU\n```\n\nYou can also set cache configuration per DMap. Here is a simple configuration for a DMap named `mydmap`:\n\n```\ndmaps:\n  mydmap:\n    maxIdleDuration: \"60s\"\n    ttlDuration: \"300s\"\n    maxKeys: 500000 # in-bytes\n    lRUSamples: 20\n    evictionPolicy: \"NONE\" # NONE/LRU\n```\n\nIf you prefer embedded-member deployment scenario, please take a look at [config#CacheConfig](https://godoc.org/github.com/olric-data/olric/config#CacheConfig) and [config#DMapCacheConfig](https://godoc.org/github.com/olric-data/olric/config#DMapCacheConfig) for the configuration.\n\n\n### Lock Implementation\n\nThe DMap implementation is already thread-safe to meet your thread safety requirements. When you want to have more control on the\nconcurrency, you can use **LockWithTimeout** and **Lock** methods. Olric borrows the locking algorithm from Redis. Redis authors propose\nthe following algorithm:\n\n\u003e The command \u003cSET resource-name anystring NX EX max-lock-time\u003e is a simple way to implement a locking system with Redis.\n\u003e\n\u003e A client can acquire the lock if the above command returns OK (or retry after some time if the command returns Nil), and remove the lock just using DEL.\n\u003e\n\u003e The lock will be auto-released after the expire time is reached.\n\u003e\n\u003e It is possible to make this system more robust modifying the unlock schema as follows:\n\u003e\n\u003e Instead of setting a fixed string, set a non-guessable large random string, called token.\n\u003e Instead of releasing the lock with DEL, send a script that only removes the key if the value matches.\n\u003e This avoids that a client will try to release the lock after the expire time deleting the key created by another client that acquired the lock later.\n\nEquivalent of`SETNX` command in Olric is `PutIf(key, value, IfNotFound)`. Lock and LockWithTimeout commands are properly implements\nthe algorithm which is proposed above. \n\nYou should know that this implementation is subject to the clustering algorithm. So there is no guarantee about reliability in the case of network partitioning. I recommend the lock implementation to be used for \nefficiency purposes in general, instead of correctness.\n\n**Important note about consistency:**\n\nYou should know that Olric is a PA/EC (see [Consistency and Replication Model](#consistency-and-replication-model)) product. So if your network is stable, all the operations on key/value \npairs are performed by a single cluster member. It means that you can be sure about the consistency when the cluster is stable. It's important to know that computer networks fail \noccasionally, processes crash and random GC pauses may happen. Many factors can lead a network partitioning. If you cannot tolerate losing strong consistency under network partitioning, \nyou need to use a different tool for locking.\n\nSee [Hazelcast and the Mythical PA/EC System](https://dbmsmusings.blogspot.com/2017/10/hazelcast-and-mythical-paec-system.html) and [Jepsen Analysis on Hazelcast 3.8.3](https://hazelcast.com/blog/jepsen-analysis-hazelcast-3-8-3/) for more insight on this topic.\n             \n### Storage Engine\n\nOlric implements a GC-friendly storage engine to store large amounts of data on RAM. Basically, it applies an append-only log file approach with indexes. \nOlric inserts key/value pairs into pre-allocated byte slices (table in Olric terminology) and indexes that memory region by using Golang's built-in map. \nThe data type of this map is `map[uint64]uint64`. When a pre-allocated byte slice is full Olric allocates a new one and continues inserting the new data into it. \nThis design greatly reduces the write latency.\n\nWhen you want to read a key/value pair from the Olric cluster, it scans the related DMap fragment by iterating over the indexes(implemented by the built-in map). \nThe number of allocated byte slices should be small. So Olric would find the key immediately but technically, the read performance depends on the number of keys in the fragment. \nThe effect of this design on the read performance is negligible.\n\nThe size of the pre-allocated byte slices is configurable.\n\n## Samples\n\nIn this section, you can find code snippets for various scenarios.\n\n### Embedded-member scenario\n#### Distributed map\n```go\npackage main\n\nimport (\n  \"context\"\n  \"fmt\"\n  \"log\"\n  \"time\"\n\n  \"github.com/olric-data/olric\"\n  \"github.com/olric-data/olric/config\"\n)\n\nfunc main() {\n  // Sample for Olric v0.5.x\n\n  // Deployment scenario: embedded-member\n  // This creates a single-node Olric cluster. It's good enough for experimenting.\n\n  // config.New returns a new config.Config with sane defaults. Available values for env:\n  // local, lan, wan\n  c := config.New(\"local\")\n\n  // Callback function. It's called when this node is ready to accept connections.\n  ctx, cancel := context.WithCancel(context.Background())\n  c.Started = func() {\n    defer cancel()\n    log.Println(\"[INFO] Olric is ready to accept connections\")\n  }\n\n  // Create a new Olric instance.\n  db, err := olric.New(c)\n  if err != nil {\n    log.Fatalf(\"Failed to create Olric instance: %v\", err)\n  }\n\n  // Start the instance. It will form a single-node cluster.\n  go func() {\n    // Call Start at background. It's a blocker call.\n    err = db.Start()\n    if err != nil {\n      log.Fatalf(\"olric.Start returned an error: %v\", err)\n    }\n  }()\n\n  \u003c-ctx.Done()\n\n  // In embedded-member scenario, you can use the EmbeddedClient. It implements\n  // the Client interface.\n  e := db.NewEmbeddedClient()\n\n  dm, err := e.NewDMap(\"bucket-of-arbitrary-items\")\n  if err != nil {\n    log.Fatalf(\"olric.NewDMap returned an error: %v\", err)\n  }\n\n  ctx, cancel = context.WithCancel(context.Background())\n\n  // Magic starts here!\n  fmt.Println(\"##\")\n  fmt.Println(\"Simple Put/Get on a DMap instance:\")\n  err = dm.Put(ctx, \"my-key\", \"Olric Rocks!\")\n  if err != nil {\n    log.Fatalf(\"Failed to call Put: %v\", err)\n  }\n\n  gr, err := dm.Get(ctx, \"my-key\")\n  if err != nil {\n    log.Fatalf(\"Failed to call Get: %v\", err)\n  }\n\n  // Olric uses the Redis serialization format.\n  value, err := gr.String()\n  if err != nil {\n    log.Fatalf(\"Failed to read Get response: %v\", err)\n  }\n\n  fmt.Println(\"Response for my-key:\", value)\n  fmt.Println(\"##\")\n\n  // Don't forget the call Shutdown when you want to leave the cluster.\n  ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)\n  defer cancel()\n\n  err = db.Shutdown(ctx)\n  if err != nil {\n    log.Printf(\"Failed to shutdown Olric: %v\", err)\n  }\n}\n```\n\n#### Publish-Subscribe\n\n```go\npackage main\n\nimport (\n  \"context\"\n  \"fmt\"\n  \"log\"\n  \"time\"\n\n  \"github.com/olric-data/olric\"\n  \"github.com/olric-data/olric/config\"\n)\n\nfunc main() {\n  // Sample for Olric v0.5.x\n\n  // Deployment scenario: embedded-member\n  // This creates a single-node Olric cluster. It's good enough for experimenting.\n\n  // config.New returns a new config.Config with sane defaults. Available values for env:\n  // local, lan, wan\n  c := config.New(\"local\")\n\n  // Callback function. It's called when this node is ready to accept connections.\n  ctx, cancel := context.WithCancel(context.Background())\n  c.Started = func() {\n    defer cancel()\n    log.Println(\"[INFO] Olric is ready to accept connections\")\n  }\n\n  // Create a new Olric instance.\n  db, err := olric.New(c)\n  if err != nil {\n    log.Fatalf(\"Failed to create Olric instance: %v\", err)\n  }\n\n  // Start the instance. It will form a single-node cluster.\n  go func() {\n    // Call Start at background. It's a blocker call.\n    err = db.Start()\n    if err != nil {\n      log.Fatalf(\"olric.Start returned an error: %v\", err)\n    }\n  }()\n\n  \u003c-ctx.Done()\n\n  // In embedded-member scenario, you can use the EmbeddedClient. It implements\n  // the Client interface.\n  e := db.NewEmbeddedClient()\n\n  ps, err := e.NewPubSub()\n  if err != nil {\n    log.Fatalf(\"olric.NewPubSub returned an error: %v\", err)\n  }\n\n  ctx, cancel = context.WithCancel(context.Background())\n\n  // Olric implements a drop-in replacement of Redis Publish-Subscribe messaging\n  // system. PubSub client is just a thin layer around go-redis/redis.\n  rps := ps.Subscribe(ctx, \"my-channel\")\n\n  // Get a message to read messages from my-channel\n  msg := rps.Channel()\n\n  go func() {\n    // Publish a message here.\n    _, err := ps.Publish(ctx, \"my-channel\", \"Olric Rocks!\")\n    if err != nil {\n      log.Fatalf(\"PubSub.Publish returned an error: %v\", err)\n    }\n  }()\n\n  // Consume messages\n  rm := \u003c-msg\n\n  fmt.Printf(\"Received message: \\\"%s\\\" from \\\"%s\\\"\", rm.Channel, rm.Payload)\n\n  // Don't forget the call Shutdown when you want to leave the cluster.\n  ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)\n  defer cancel()\n\n  err = e.Close(ctx)\n  if err != nil {\n    log.Printf(\"Failed to close EmbeddedClient: %v\", err)\n  }\n}\n```\n\n### Client-Server scenario\n#### Distributed map\n\n```go\npackage main\n\nimport (\n  \"context\"\n  \"fmt\"\n  \"log\"\n  \"time\"\n\n  \"github.com/olric-data/olric\"\n)\n\nfunc main() {\n  // Sample for Olric v0.5.x\n\n  // Deployment scenario: client-server\n\n  // NewClusterClient takes a list of the nodes. This list may only contain a\n  // load balancer address. Please note that Olric nodes will calculate the partition owner\n  // and proxy the incoming requests.\n  c, err := olric.NewClusterClient([]string{\"localhost:3320\"})\n  if err != nil {\n    log.Fatalf(\"olric.NewClusterClient returned an error: %v\", err)\n  }\n\n  // In client-server scenario, you can use the ClusterClient. It implements\n  // the Client interface.\n  dm, err := c.NewDMap(\"bucket-of-arbitrary-items\")\n  if err != nil {\n    log.Fatalf(\"olric.NewDMap returned an error: %v\", err)\n  }\n\n  ctx, cancel := context.WithCancel(context.Background())\n\n  // Magic starts here!\n  fmt.Println(\"##\")\n  fmt.Println(\"Simple Put/Get on a DMap instance:\")\n  err = dm.Put(ctx, \"my-key\", \"Olric Rocks!\")\n  if err != nil {\n    log.Fatalf(\"Failed to call Put: %v\", err)\n  }\n\n  gr, err := dm.Get(ctx, \"my-key\")\n  if err != nil {\n    log.Fatalf(\"Failed to call Get: %v\", err)\n  }\n\n  // Olric uses the Redis serialization format.\n  value, err := gr.String()\n  if err != nil {\n    log.Fatalf(\"Failed to read Get response: %v\", err)\n  }\n\n  fmt.Println(\"Response for my-key:\", value)\n  fmt.Println(\"##\")\n\n  // Don't forget the call Shutdown when you want to leave the cluster.\n  ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)\n  defer cancel()\n\n  err = c.Close(ctx)\n  if err != nil {\n    log.Printf(\"Failed to close ClusterClient: %v\", err)\n  }\n}\n```\n\n### SCAN on DMaps\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/olric-data/olric\"\n\t\"github.com/olric-data/olric/config\"\n)\n\nfunc main() {\n\t// Sample for Olric v0.5.x\n\n\t// Deployment scenario: embedded-member\n\t// This creates a single-node Olric cluster. It's good enough for experimenting.\n\n\t// config.New returns a new config.Config with sane defaults. Available values for env:\n\t// local, lan, wan\n\tc := config.New(\"local\")\n\n\t// Callback function. It's called when this node is ready to accept connections.\n\tctx, cancel := context.WithCancel(context.Background())\n\tc.Started = func() {\n\t\tdefer cancel()\n\t\tlog.Println(\"[INFO] Olric is ready to accept connections\")\n\t}\n\n\t// Create a new Olric instance.\n\tdb, err := olric.New(c)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to create Olric instance: %v\", err)\n\t}\n\n\t// Start the instance. It will form a single-node cluster.\n\tgo func() {\n\t\t// Call Start at background. It's a blocker call.\n\t\terr = db.Start()\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"olric.Start returned an error: %v\", err)\n\t\t}\n\t}()\n\n\t\u003c-ctx.Done()\n\n\t// In embedded-member scenario, you can use the EmbeddedClient. It implements\n\t// the Client interface.\n\te := db.NewEmbeddedClient()\n\n\tdm, err := e.NewDMap(\"bucket-of-arbitrary-items\")\n\tif err != nil {\n\t\tlog.Fatalf(\"olric.NewDMap returned an error: %v\", err)\n\t}\n\n\tctx, cancel = context.WithCancel(context.Background())\n\n\t// Magic starts here!\n\tfmt.Println(\"##\")\n\tfmt.Println(\"Insert 10 keys\")\n\tvar key string\n\tfor i := 0; i \u003c 10; i++ {\n\t\tif i%2 == 0 {\n\t\t\tkey = fmt.Sprintf(\"even:%d\", i)\n\t\t} else {\n\t\t\tkey = fmt.Sprintf(\"odd:%d\", i)\n\t\t}\n\t\terr = dm.Put(ctx, key, nil)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Failed to call Put: %v\", err)\n\t\t}\n\t}\n\n\ti, err := dm.Scan(ctx)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to call Scan: %v\", err)\n\t}\n\n\tfmt.Println(\"Iterate over all the keys\")\n\tfor i.Next() {\n\t\tfmt.Println(\"\u003e\u003e Key\", i.Key())\n\t}\n\n\ti.Close()\n\n\ti, err = dm.Scan(ctx, olric.Match(\"^even:\"))\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to call Scan: %v\", err)\n\t}\n\n\tfmt.Println(\"\\n\\nScan with regex: ^even:\")\n\tfor i.Next() {\n\t\tfmt.Println(\"\u003e\u003e Key\", i.Key())\n\t}\n\n\ti.Close()\n\n\t// Don't forget the call Shutdown when you want to leave the cluster.\n\tctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\n\terr = db.Shutdown(ctx)\n\tif err != nil {\n\t\tlog.Printf(\"Failed to shutdown Olric: %v\", err)\n\t}\n}\n```\n\n#### Publish-Subscribe\n```go\npackage main\n\nimport (\n  \"context\"\n  \"fmt\"\n  \"log\"\n  \"time\"\n\n  \"github.com/olric-data/olric\"\n)\n\nfunc main() {\n  // Sample for Olric v0.5.x\n\n  // Deployment scenario: client-server\n\n  // NewClusterClient takes a list of the nodes. This list may only contain a\n  // load balancer address. Please note that Olric nodes will calculate the partition owner\n  // and proxy the incoming requests.\n  c, err := olric.NewClusterClient([]string{\"localhost:3320\"})\n  if err != nil {\n    log.Fatalf(\"olric.NewClusterClient returned an error: %v\", err)\n  }\n\n  // In client-server scenario, you can use the ClusterClient. It implements\n  // the Client interface.\n  ps, err := c.NewPubSub()\n  if err != nil {\n    log.Fatalf(\"olric.NewPubSub returned an error: %v\", err)\n  }\n\n  ctx, cancel := context.WithCancel(context.Background())\n\n  // Olric implements a drop-in replacement of Redis Publish-Subscribe messaging\n  // system. PubSub client is just a thin layer around go-redis/redis.\n  rps := ps.Subscribe(ctx, \"my-channel\")\n\n  // Get a message to read messages from my-channel\n  msg := rps.Channel()\n\n  go func() {\n    // Publish a message here.\n    _, err := ps.Publish(ctx, \"my-channel\", \"Olric Rocks!\")\n    if err != nil {\n      log.Fatalf(\"PubSub.Publish returned an error: %v\", err)\n    }\n  }()\n\n  // Consume messages\n  rm := \u003c-msg\n\n  fmt.Printf(\"Received message: \\\"%s\\\" from \\\"%s\\\"\", rm.Channel, rm.Payload)\n\n  // Don't forget the call Shutdown when you want to leave the cluster.\n  ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)\n  defer cancel()\n\n  err = c.Close(ctx)\n  if err != nil {\n    log.Printf(\"Failed to close ClusterClient: %v\", err)\n  }\n}\n\n```\n\n## Contributions\n\nPlease don't hesitate to fork the project and send a pull request or just e-mail me to ask questions and share ideas.\n\n## License\n\nThe Apache License, Version 2.0 - see LICENSE for more details.\n\n## About the name\n\nThe inner voice of Turgut Özben who is the main character of [Oğuz Atay's masterpiece -The Disconnected-](https://www.themodernnovel.org/asia/other-asia/turkey/oguz-atay/the-disconnected/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Folric-data%2Folric","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Folric-data%2Folric","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Folric-data%2Folric/lists"}