Skip to content

About

An event-loop-native Redis client for asyncio, written in Rust.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

BeerStream2

A Redis client for asyncio that lives on the event loop. Written in Rust, built for the request path of Poltergeist.

Every command costs one write(2), one read(2) and one Future. There is no connection pool to lock, no background runtime and no cross-thread handoff: commands are written to the socket as they are issued and replies are matched to their waiters in order, which is how Redis itself works.

Why it is fast

  • The socket is registered with the asyncio loop. The client calls loop.add_reader once per connection and parses RESP straight out of the socket buffer when the loop calls back.
  • One Future per command, created with loop.create_future() and resolved from inside the read callback with the converted value.
  • Concurrent commands share one connection. Redis is single-threaded and answers in order, so a single connection with in-flight commands is both the simplest and the fastest arrangement. More connections are available for very large values.
  • Pipelines are one write. pipeline() buffers the encoded commands and execute() sends them, wrapped in MULTI/EXEC by default, as a single write with a single Future.

Measured against redis-py 8.1 on the same machine, 32 concurrent tasks, GET of a 64-byte value: 4.7 µs of CPU per command instead of 31 µs, and 150k instead of 30k commands per second from a single process.

Usage

import beerstream2

redis = beerstream2.Client(host="127.0.0.1", port=6379, database=0)
await redis.connect()

await redis.set("session:1", "abc", ex=3600)
value = await redis.get("session:1")          # "abc" or None
await redis.set("lock", "1", nx=True, ex=30)   # True or None
ranks = await redis.zrevrange("board", 0, 9)   # list[str]

async with redis.pipeline(transaction=True) as pipeline:
    pipeline.incr("rate:ip")
    pipeline.expire("rate:ip", 60, nx=True)
    count, _ = await pipeline.execute()

await redis.close()

Every method returns an asyncio Future, so the calls are awaited like any coroutine. The client must be created and used from one event loop; uvloop and the default loop both work.

Commands and return values

Replies are decoded to str, matching redis-py with decode_responses=True:

Method Returns
get, getex(key, ex=) `str
set(key, value, ex=, nx=) True, or None when nx did not set
mget(keys) `list[str
delete(*keys), exists(*keys), incr, sadd, srem, zadd(key, mapping), zrem, zcard, publish, dbsize int
expire(key, seconds, nx=) bool
smembers set[str]
zrevrange(key, start, stop) list[str]
zrevrank(key, member) `int
ping True
info dict with numbers parsed and keyspace lines as nested dicts
execute_command(*args) the reply converted by type

Values sent to the server may be str, int, float or bytes.

Pipelines

pipeline(transaction=True) returns a Pipeline with the same command methods, which buffer instead of sending. execute() sends the buffer and resolves with one result per command, in order. With transaction=True the commands run inside MULTI/EXEC. An error reply to any command rejects the whole execute() with ResponseError. The pipeline works as an async context manager and empties itself on exit.

Connections

connect() opens the configured connections, sends AUTH and SELECT as needed and waits for a PING. A connection the server drops fails every command in flight with ConnectionLost and is reopened on the next command.

Errors

beerstream2.Error is the base. ResponseError is an error reply from the server, ProtocolError an unparseable reply, ConnectionLost a dropped socket and ClientClosed a call after close().

Limits

  • RESP2 only; no pub/sub subscriptions, no blocking commands, no cluster.
  • No TLS; the client is meant for a private network.
  • Host names are resolved synchronously when a connection is opened.

Building

uv venv && uvx maturin develop --release
cargo test

Wheels for Python 3.14 on manylinux x86_64 and aarch64 are built by the GitHub workflow and attached to each v* release.

About

An event-loop-native Redis client for asyncio, written in Rust.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages