-   [](https://platform.exaloop.io/)
-   Databases
-   [Redis](https://platform.exaloop.io/db/redis/)
-   Client applications

# Redis Client Applications

The native Redis client supports typed values, pipelines, transactions, streams, and Pub/Sub. Use it to combine Redis access with computation in a standalone Codon application; the Redis server needs no Codon module.

This guide covers the common command families and the differences between batched commands, transactions, and replayable event consumption.

**Before you begin:** install the [all-SDK distribution](https://platform.exaloop.io/start/distribution/) and run a development Redis server on localhost. The examples write keys; use a database without application data you need to keep.

## Read, Compute, and Write

An ordinary Redis server needs no Codon module for client access. Save the example below as `client.codon`. It writes two readings, computes their total in Codon, and stores a summary hash. Use a development database: the example writes to `reading:a`, `reading:b`, and `codon:summary`.

```python title="client.codon"
from exaloop.databases.redis import Redis
import sys

port = int(sys.argv[1]) if len(sys.argv) > 1 else 6379
with Redis(port=port) as client:
    client.mset({"reading:a": 1.5, "reading:b": 2.5})
    readings = client.mget(["reading:a", "reading:b", "reading:missing"], float)
    total = 0.0
    for value in readings:
        if value is not None:
            total += value
    client.hset("codon:summary", {"total": str(total), "source": "Codon"})
    print(client.hgetall("codon:summary", str))
```

With a prebuilt SDK and a Redis server listening on port 6379:

```sh title="Terminal"
exaloop --with redis run -release client.codon
```

Use an individual SDK without the CLI

```sh title="Terminal"
codon run -release -plugin /path/to/sdk/redis client.codon
```

The resulting hash contains a total of `4.0`. Requesting `float` in `mget()` converts the stored values for the calculation; the missing key becomes `None` and is skipped.

## Connections and Security

`Redis()` connects to the local server on port 6379. For a remote deployment, provide ACL credentials from private configuration and enable verified TLS: `Redis(host="redis.example.com", ssl=True, ssl_ca_certs="/etc/ssl/redis-ca.pem")`.

Each concurrent worker needs a dedicated client. A `with` block closes it deterministically; connection loss raises `ConnectionError` without retrying writes whose outcome may be uncertain. Server command failures raise `ResponseError` instead. The [connection reference](https://platform.exaloop.io/db/reference/#redis) lists all options.

## Types and Materialization

Reads return bytes unless you ask for a type. For example, `client.get("count", int)` reads an integer and `client.hgetall("record", str)` reads a hash as text. Missing values use `Optional[T]`, so they are distinct from an empty string or zero.

Choose the Redis data structure that matches your operation:

| Data | Common writes | Common reads and materialized results |
| --- | --- | --- |
| Strings and binary blobs | `set`, `mset`, `append`, `setrange`, `incr`, `incrbyfloat` | `get -> Optional[T]`, `mget -> List[Optional[T]]`, `getrange -> bytes`, `strlen` |
| Hashes | `hset(key, mapping)`, `hdel`, `hincrby` | `hget`, `hmget`, `hgetall -> Dict[T,T]`, `hexists`, `hlen` |
| Lists | `lpush`, `rpush`, `lset`, `ltrim` | `lrange -> List[T]`, `lpop/rpop -> Optional[T]`, `llen`, `blpop -> Optional[Tuple[T,T]]` |
| Sets | `sadd`, `srem`, `spop` | `smembers/sunion/sinter/sdiff -> Set[T]`, `sismember`, `scard` |
| Sorted sets | `zadd(key, member_to_score)`, `zincrby`, `zrem` | `zrange -> List[T]`, `zrange_withscores -> List[Tuple[T,float]]`, `zscore -> Optional[float]`, `zcard` |
| Bitmaps and bitfields | `setbit`, `bitop`, `bitfield` | `getbit`, `bitcount`, `bitfield -> List[Optional[int]]` |
| HyperLogLog | `pfadd`, `pfmerge` | `pfcount -> int` (approximate cardinality) |
| Geospatial indexes | `geoadd(key, member_to_lon_lat)` | `geopos -> List[Optional[Tuple[float,float]]]`, `geodist -> Optional[float]`, `geosearch -> List[T]` |
| Streams and consumer groups | `xadd`, `xdel`, `xtrim`, `xgroup_create`, `xack`, `xclaim` | `xrange`, `xread`, `xreadgroup`, `xpending`, `xlen` |

Common Redis options keep their familiar names. For example, `set()` accepts `ex` for an expiry in seconds and `nx` to write only if the key does not exist. You can also use `client[key]` for string values; direct indexing raises `KeyError` for a missing key.

## Large Collections and Bulk Work

Use `scan_iter(match="prefix:*", count=100)` to walk a keyspace without a single large `KEYS` request. Like Redis SCAN, it is not a snapshot and may return duplicates while keys are changing. Split large reads and writes into batches to avoid holding an entire workload in memory.

A nontransactional pipeline sends queued commands together and then reads all replies, avoiding one network round trip per command:

```python title="Source"
from exaloop.databases.redis import Redis

with Redis() as client:
    with client.pipeline(transaction=False) as batch:
        batch.set("batch:count", "10").incr("batch:count").get("batch:count")
        replies = batch.execute()
        assert replies[1].int() == 11
        assert replies[2].as_type(int) == 11
        for reply in replies:
            reply.close()
```

Call `execute()` to send the queued work, then read the replies in command order. The example sets a counter to 10, increments it, and reads back 11. Leaving the block without `execute()` discards the queue.

## Transactions

Without `transaction=False`, a pipeline uses `MULTI`/`EXEC`, so Redis executes the queued commands together without interleaving other clients' commands. This does not provide SQL-style rollback: a failed command does not undo earlier writes in the transaction.

For a read/modify/write operation, use WATCH to detect a concurrent update. The example retries the whole operation up to three times:

```python title="Source"
from exaloop.databases.redis import Redis, WatchError

with Redis() as client:
    for attempt in range(3):
        try:
            with client.pipeline() as transaction:
                transaction.watch("balance")
                with transaction.read("GET", "balance") as reply:
                    balance = 0 if reply.is_null else reply.as_type(int)
                transaction.set("balance", balance + 1)
                results = transaction.execute()
                for result in results:
                    result.close()
            break
        except WatchError:
            if attempt == 2:
                raise
```

Keep the client's connection dedicated to this operation until the transaction finishes. If a watched key changes, `WatchError` lets the application decide whether to retry.

## Streams and Pub/Sub

Use streams when consumers need stored events and acknowledgements. An entry has an ID and a collection of fields; `xread()` groups the returned entries by stream:

```python title="Source"
from exaloop.databases.redis import Redis

with Redis() as client:
    entry = client.xadd("measurements", {"sensor": "A", "value": "12.5"})
    rows = client.xread({"measurements": "0"}, count=100, T=str)
    assert rows[0][0] == "measurements"
    assert client.xlen("measurements") > 0
```

For consumer groups, create the group with `xgroup_create()`, read deliveries with `xreadgroup()`, and acknowledge completed work with `xack()`. If you use a blocking read, give the socket a longer timeout than the requested wait.

Pub/Sub is a simpler choice for live notifications that do not need replay. It uses its own connection, so publishing does not interrupt the subscriber:

```python title="Source"
from exaloop.databases.redis import Redis

with Redis() as client:
    with client.pubsub() as subscription:
        subscription.subscribe("events")
        assert subscription.get_message(1.0).__val__().kind == "subscribe"
        client.publish("events", b"updated")
        message = subscription.get_message(1.0)
        assert message is not None
        assert message.channel == b"events" and message.data == b"updated"
```

The first message confirms the subscription; the next contains the published event. `get_message()` returns `None` on timeout. A disconnected Pub/Sub consumer misses events, so choose streams when recovery matters.

## Commands Beyond the Convenience API

Use `execute_command("COMMAND", *args)` for a command without a convenience method, including commands provided by an installed Redis module. It returns a reply that you can read through typed accessors; see the [reply reference](https://platform.exaloop.io/db/reference/#redis).

The client is synchronous and single-node. Cluster routing, Sentinel discovery, and connection pooling are not supported.

## Next Steps

-   [Package the client](https://platform.exaloop.io/db/redis/deployment/#client-applications) with its runtime dependencies.
-   [Create a server command](https://platform.exaloop.io/db/redis/commands/) to combine a short sequence of operations inside Redis.
-   Review the [connection and reply reference](https://platform.exaloop.io/db/reference/#redis) for configuration and lower-level commands.

Source: https://platform.exaloop.io/db/redis/client/
