Code Monkey home page Code Monkey logo

elixir-memcache's Introduction

Memcache

Memcache client for Elixir with support for pipelining.

{:ok, memcache} = Memcache.connect()
:ok = Memcache.set(memcache, "key", "value")
{:ok, "value"} = Memcache.get(memcache, "key")

Pipelined Execution

All commands (get, set, etc.) can be executed both individually or as part of a batch of commands. In other words, these are equivalent in terms of behavior:

:ok = Memcache.set(memcache, "key1", "value1")
:ok = Memcache.set(memcache, "key2", "value2")
[:ok, :ok] =
  Memcache.batch()
  |> Memcache.set("key1", "value1")
  |> Memcache.set("key2", "value2")
  |> Memcache.execute(memcache)

However, note that they are not equivalent in terms of performance. The batched commands are executed using pipelining: the requests for all commands are sent, then the responses for all commands are received. This allows for a large degree of concurrency. Typically we can execute a batch of commands in only slightly more time than a single command.

# Sequential
│                │
│ ───request1──► │
│ ◄──response1── │
│                │
│ ───request2──► │
│ ◄──response2── │
│                │

# Pipelined
│                │
│ ───request1──► │
│ ───request2──► │
│                │
│ ◄──response1── │
│ ◄──response2── │
│                │

State Machines

Memcache is often accessed in certain patterns. For example, in the "read-through cache" pattern, we read a cached value, or if not found, generate the value and write it back. Or in the "read-modify-write" pattern, we read a value, modify it, and write it back, taking advantage of the cas (compare-and-set) command to avoid losing data from race conditions.

When we have many instances of such patterns, doing them linearly may introduce too much latency. Doing them in parallel with multiple processes (perhaps using Task.async_stream) may introduce too much CPU overhead.

It would be nice to take advantage of pipelined execution. Run the first Memcache command for all instances as a batch, do any CPU work, then run the second command for all instances, and so on until they're all done. This gets us most of the concurrency of parallel execution, but without the CPU overhead.

The Memcache.StateMachine module provides this capability. An access pattern is encoded as a state machine, then many instances of these state machines can be run simultaneously. It includes some examples (described below) as well as the ability to encode custom state machines.

Memcache.StateMachine.new()
|> Memcache.StateMachine.read_through(cache_key, fn ->
  MyDatabase.expensive_call()
end)
|> Memcache.StateMachine.execute(memcache)

Read-Through Cache

    ┌─────┐   ok    ┌────────┐
──► │ get │ ──────► │ return │
    └─────┘         └────────┘
       │     ┌─────┐    ▲
       └───► │ set │ ───┘
 not_found   └─────┘

The original and probably most common way of using Memcache. We first try to look up a cached value with get. If found, we're done. Otherwise generate the value (perhaps with an expensive database call or computation), then write the value back with a set.

Memcache.StateMachine.read_through(memcache, cache_key, fn ->
  MyDatabase.expensive_call()
end)

Read-Modify-Write

         ok       ┌─────┐      ok
       ┌────────► │ cas │ ────────┐
       │          └─────┘         ▼
    ┌──────┐ ◄───────┘        ┌────────┐
──► │ gets │   exists         │ return │
    └──────┘ ◄───────┐        └────────┘
       │          ┌─────┐         ▲
       └────────► │ add │ ────────┘
      not_found   └─────┘      ok

In this case we're using Memcache as a (semi-)persistent datastore. We read a value, modify it, and write it back. The key is to use cas and add operations to ensure no data is lost to race conditions. If two processes simultaneously do a read-modify-write, the modification of one could be lost. Whichever cas goes second will detect that the modification from the first and return an error, which indicates we should start over and get the value again.

Memcache.StateMachine.read_modify_write(memcache, cache_key, fn
  {:error, :not_found} -> "initial value"
  {:ok, old_value} -> old_value <> "new value"
end)

Locking

    ┌─────┐  ok   ┌────────┐     ┌────────┐
──► │ add │ ────► │ delete │ ──► │ return │
    └─────┘       └────────┘     └────────┘
      ▲ │
      └─┘ exists

Sometimes you need to ensure that only one process is doing something at a time. Write a key in Memcache to get a lock, then do the thing, then delete the key to remove the lock. Using an add command ensures that you only get the lock if no one else has it.

Memcache.StateMachine.with_lock(memcache, cache_key, fn ->
  function_that_only_one_process_should_call_at_a_time()
end)

Server Implementation

A native Elixir Memcached server implementation is included, so that you can run your application's unit tests without installing and starting a separate Memcached process.

{:ok, server} = Memcache.Server.start_link(port: 11212)

Installation

If available in Hex, the package can be installed by adding memcache to your list of dependencies in mix.exs:

def deps do
  [
    {:memcache, "~> 0.1.0"}
  ]
end

elixir-memcache's People

Contributors

pguillory avatar

Stargazers

 avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.