bosgood
bosgood

Reputation: 2042

How do I implement Redis pipelined requests with Booksleeve?

I'm a bit mixed up about the difference between a Redis transaction and pipeline and ultimately how to use pipelines with Booksleeve. I see that Booksleeve has support for the Redis transaction feature (MULTI/EXEC), but there is no mention in its API/tests about a pipelining feature. However, it's clear in other implementations that there is a distinction between pipelines and transactions, namely in atomicity, as evidenced in the redis-ruby version below, but in some places the terms seem to be used interchangeably.

redis-ruby implementation:

r.pipelined {
  # these commands will be pipelined
  r.get("insensitive_key")
}

r.multi {
  # these commands will be executed atomically
  r.set("sensitive_key")
}

I'd just use MULTI/EXEC instead but they seem to block all other users until the transaction has completed (not necessary in my case), so I worry about their performance. Has anyone used pipelines with Booksleeve or have any ideas about how to implement them?

Upvotes: 6

Views: 3547

Answers (3)

Marc Gravell
Marc Gravell

Reputation: 1062770

In BookSleeve, everything is always pipelined. There are no synchronous operations. Not a single one. As such, every operation returns some form of Task (could be a vanilla Task, could be a Task<string>, Task<long>, etc), which at some point in the future (i.e. when redis responds) will have a value. You can use Wait at your calling code to perform a synchronous wait, or ContinueWith / await (C# 5 language feature) to perform an asynchronous callback.

Transactions are no different; they are pipelined. The only subtle change with transactions is that they are additionally buffered at the call-site until complete (since it is a multiplexer, we can't start pipelining transaction-related messages until we have a complete unit-of-work, as it would adversely impact other callers on the same multiplexer).

So: the reason there is no explicit .pipelined is that everything is pipelined and asynchronous.

Upvotes: 6

alphazero
alphazero

Reputation: 27224

Pipelining is a protocol level communication strategy and has nothing to do with atomicity. It is entirely orthogonal to notion of 'transactions'. (For example, you can use MULTI .. EXEC in a pipelined connection.)

What is pipelining?

The most basic connector to redis would be a synchronous client interacting in a request-reply manner. Client sends a request, and then waits for response from Redis before sending the next request.

In pipelining, the client can keep sending requests without pausing to see the Redis response for each request. Redis is, of course, a single threaded server and a natural serialization point, and thus request order is preserved and reflected in the response order. This means, the client can have one thread sending requests (typically by dequeuing from a request queue) and another thread is constantly processing responses from Redis. Note that of course you can still use pipelining with a single threaded client, but you do lose some of the efficiencies. The two threaded model allows for full utilization of your local CPU and the network bandwidth (e.g. saturation).

If you are following this so far, you must ask yourself: well, how are the request and responses matched on the client side? Good question! There are various ways to approach this. In JRedis, I wrap requests in a (java) Future object, to deal with the asynchrony of the request/response processing. Everytime a request is sent, a corresponding Future object is wrapped by a pending response object and is queued. The response listener simply dequeues from this queue 1 item at a time and parses the response (stream) and updates the future object.

Now the end user of the client can either be exposed to a synchronous or an asynchronous interface. If the interface is synchronous, the implementation naturally must block on the Future's response.

If you have followed so far, then it should be clear that a single threaded app using synchronous semantics with pipelining defeats the entire purpose of pipelining (since the app is blocking on the response and is not busy feeding the client additional requests.) But if the app is multithreaded, a synchronous interface to the pipeline allows you to use a single connection while processing N client-app threads. (So here, it is a implementation strategy to help build a thread-safe connection.)

If the interface to pipeline is asynchronous, then even a single threaded client app can benefit. Throughput increases at least by an order of magnitude.

(Caveats with pipelining: It is non-trivial to write a fault-tolerant pipelined client.)

Ideally I should use a diagram, but pay attention to what happens at the end of the clip: http://www.youtube.com/watch?v=NeK5ZjtpO-M

Upvotes: 4

Carlos Mendes
Carlos Mendes

Reputation: 2000

Here is the link to Redis Transactions Documentation

Regarding BookSleeve, please refer to this post from Marc.

"CreateTransaction() creates a staging area to build commands (using exactly the same API) and capture future results. Then, when Execute() is called the buffered commands are assembled into a MULTI/EXEC unit and sent down in a contiguous block (the multiplexer will send all of these together, obviously)."

If you create your commands inside a transaction they will automatically be "pipelined".

Upvotes: 1

Related Questions