Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Catalog · How the catalog works

Random project

hermes

Modern Redis client for Delphi — thread-safe, centralized RESP parser, Synapse or Indy transport adapters

View on GitHub

Anyone may press Interesting, signed in or not. It helps others find this project.

Member rating

No ratings yet.

Sign in to rate this project.

README

# Hermes

Hermes

Modern Redis client for Delphi

Thread-safe, with centralized RESP parser, decoupled transport adapters, and first-class features.

BSD-3-Clause • Playbook • Recipes • Architecture


Highlights

  • Thread-safe — no shared mutable state, safe for concurrent use
  • Centralized RESP parser — typed, with RESP2 coverage and native RESP3 type parsing in the core
  • Interchangeable adapters — Synapse (lightweight, Linux-friendly) or Indy (native TLS)
  • Event-driven pool — semaphore, warmup, health-check, automatic keep-alive
  • Pipeline — batch commands with massive throughput (50k+ ops/s)
  • Streams — XADD, XREAD, consumer groups, XClaim, XAutoClaim
  • Cluster — automatic discovery, MOVED/ASK, read preference, failover
  • Sentinel — discovery + automatic failover with master re-discovery
  • TLS — encrypted connection via Indy OpenSSL 1.1
  • Pub/Sub — subscribe, pattern subscribe, listener on dedicated thread
  • Transactions — MULTI/EXEC, WATCH (optimistic locking), DISCARD

Documentation

Performance in context

Hermes was designed to avoid the most common bottlenecks in "traditional" Redis usage in Delphi:

  • simple client for sequential, predictable flow
  • dedicated client per thread for real concurrency
  • pool for connection reuse
  • pipeline to reduce roundtrips in batches

In reproducible local benchmarks, the most significant gain appears when the flow correctly uses pipeline, reducing the per-command round-trip cost.

Scenario Hermes Reading
Sequential (SET+GET+DEL) 7,982 ops/s simple, predictable baseline
Multi-thread (8 threads) 20,781 ops/s ~2.6x over baseline with dedicated client per thread
Pool (8 threads) 10,661 ops/s ~1.3x over baseline with connection reuse
Pipeline x20 52,800 ops/s ~6.6x over baseline by amortizing roundtrips
text
Relative throughput (baseline = sequential)
Sequential   | ###### 1.0x
Pool         | ######## 1.3x
Multi-thread | ################ 2.6x
Pipeline x20 | ######################################## 6.6x

Local reference: Redis 8.6.1 standalone on Windows, standard benchmark from the 10-benchmark sample with threads=8 and iterations/thread=200. Absolute numbers vary with CPU, network, transport, and topology. Full methodology and recipes in the benchmark playbook.

Redis Compatibility

The compatibility summary, the command family matrix, and notes on RESP2/RESP3 are in the Compatibility Playbook.

Installation

Clone the repository and add the paths to your project:

pascal
src/
src/client/
src/cluster/
src/core/
src/features/
src/net/
src/vendor/synalist/

Dependencies

  • Delphi 11 Alexandria (or later)
  • Synapse — included in src/vendor/synalist/ (vanilla from ararat.cz)
  • Indy — included in Delphi (required only for TLS)
  • OpenSSL 1.1 — libssl-1_1.dll + libcrypto-1_1.dll (required only for TLS)

Quick start

pascal
uses
  Hermes.Client, Hermes.Interfaces;

var
  Client: IHermesClient;
begin
  Client := THermesClient.New('127.0.0.1', 6379)
    .ConnectTimeout(2000)
    .ReadTimeout(2000);

  Client.SetValue('key', 'value', 60);
  WriteLn(Client.GetValue('key').AsString); // 'value'
  Client.Delete('key');
end;

RESP3 opt-in

pascal
uses
  Hermes.Client, Hermes.Interfaces;

var
  Client: IHermesClient;
begin
  Client := THermesClient.New('127.0.0.1', 6379)
    .ProtocolVersion(3);

  Client.Connect;
end;

Use this mode only with Redis 6.0+, where HELLO 3 exists.

Pool

pascal
uses
  Hermes.Features.Pool, Hermes.Features.Interfaces;

var
  Pool: IHermesPool;
  Client: IHermesClient;
begin
  Pool := THermesPool.New(
    function: IHermesClient
    begin
      Result := THermesClient.New('127.0.0.1', 6379);
    end, 8)
    .Warmup(4)
    .HealthCheckOnAcquire(True);

  Client := Pool.Acquire;
  try
    Client.SetValue('key', 'value', 60);
  finally
    Pool.Release(Client);
  end;
end;

Pipeline

pascal
uses
  Hermes.Features.Pipeline, Hermes.Features.Interfaces;

var
  Pipe: IHermesPipeline;
begin
  Pipe := THermesPipeline.New(Client);
  Pipe.QueueRaw(['SET', 'k1', 'v1']);
  Pipe.QueueRaw(['SET', 'k2', 'v2']);
  Pipe.QueueRaw(['GET', 'k1']);
  Pipe.Execute; // 3 commands, 1 roundtrip
end;

TLS (Indy)

pascal
uses
  Hermes.Net.Indy.TLS, Hermes.Net.Interfaces;

var
  Transport: IHermesTransport;
begin
  // AVerifyPeer=True (default) valida a cadeia do certificado do servidor
  // contra o CAFile informado. Use AVerifyPeer=False apenas em desenvolvimento
  // com certificado self-signed - nunca em producao.
  Transport := THermesIndyTLSTransport.New('C:\path\to\ca-bundle.pem', True);
  Client := THermesClient.New('redis.example.com', 6380, Transport);
end;

Cluster

pascal
uses
  Hermes.Cluster.Client, Hermes.Cluster.Topology, Hermes.Cluster.Types;

var
  Topology: THermesClusterTopology;
  Cluster: IHermesClusterClient;
begin
  Topology := THermesClusterTopology.Create;
  Cluster := THermesClusterClient.New(Topology, NodeFactory, Seeds);
  Cluster.RefreshTopology;
  Cluster.Execute('mykey', ['SET', 'mykey', 'value']); // auto-route by slot
end;

Sentinel

pascal
uses
  Hermes.Sentinel, Hermes.Sentinel.Client;

var
  Client: IHermesClient;
begin
  Client := THermesSentinelAwareClient.New(
    THermesSentinelDiscovery.New(['sentinel1:26379', 'sentinel2:26380']),
    'mymaster');
  // Automatically re-discovers master on each Connect (supports failover)
end;

Project structure

pascal
src/
  core/           RESP parser, serializer, types, errors
  client/         Main interface, command facade
  cluster/        Cluster client, topology, router, sentinel
  features/       Pool, Pipeline, Streams, Scanner, PubSub, Health
  net/            Transport adapters (Synapse, Indy, TLS)
  vendor/         Vanilla Synalist (blcksock, ssl)

samples/          Executable examples for each feature
tests/            Unit and real smoke tests
docs/             Technical documentation

Samples

Sample Description
01-basic Strings, hash, list, pipeline, pool, scanner
02-pipeline Batch + sequential vs pipeline comparison
03-pool Warmup, multi-thread, health-check
04-streams XADD, XREAD, consumer groups, XRANGE
05-pubsub Subscribe, publish, listener thread
06-transactions MULTI/EXEC, WATCH, DISCARD
07-cluster Discovery, MOVED, multicluster
08-sentinel Discovery + failover
09-tls TLS connection via Indy
10-benchmark Performance comparison
11-vcl-postgres-cache Real VCL CRUD with PostgreSQL + Redis cache, listings, and invalidation

Tests

bash
# Unit tests (no Redis required)
Hermes.Tests.exe

# Start local real environment (standalone + TLS + sentinel + cluster)
pwsh ./tests/runtime/start-real-env.ps1

# Real smoke tests (requires Redis at localhost:6379)
Hermes.RealTests.exe

# Cluster tests (requires cluster on ports 7001-7003)
Hermes.ClusterRealTests.exe

# Stop local real environment
pwsh ./tests/runtime/stop-real-env.ps1

Technical debt

RESP3

The Hermes.Core.RESP core now recognizes, in addition to classic RESP2, the main native RESP3 types: boolean, double, map, set, push, attribute, big number, blob error, and verbatim string.

Current state: the central parser already covers RESP3 and the client can also negotiate this protocol via ProtocolVersion(3).

Current limit: the default lifecycle still stays in RESP2 by default to preserve compatibility with Redis 5.x installations and legacy environments. RESP3 is therefore opt-in, not the global default.

TLS via Synapse

The THermesSynapseTLSTransport adapter uses the Synapse ssl_openssl11 plugin. In environments where SSL_CTX_new returns nil silently (observed with OpenSSL 1.1.1j/1.1.1w on Windows), the handshake fails without an error.

Recommendation: use THermesIndyTLSTransport for TLS in production. Indy TLS in a local smoke environment can be started via tests/runtime/start-real-env.ps1.

Legacy benchmark

The samples/10-benchmark sample now compiles in default mode with only the Hermes benchmark.

Comparison with legacy client: optional. To enable comparison with the external Redis.Client unit, compile the sample with USE_LEGACY_REDISCLIENT.

The Synapse TLS adapter remains in the project for scenarios where vanilla synalist works correctly (confirmed on Linux with synalist from ararat.cz).

Synalist vendor vs ACBr

The project includes vanilla synalist in src/vendor/synalist/ (rev 278 from ararat.cz). If your project already uses synalist from another source (e.g., ACBr), adjust the search path to point to the desired version. Both work for plain transport (TCP).

The Olympian Family

Poseidon commands the seas — raw transport, the force of the waves. Triton guards his father's waters — manages what flows, holds what must not be lost. Pegasus flies through the skies — born from Medusa's blood, by the sword Hermes gave to Perseus. Hermes runs between all realms — carries messages between gods, mortals and monsters, faster than any wave.

Project Myth Role
Poseidon God of the seas Async transport layer — IOCP/epoll, raw I/O
Triton Son of Poseidon, guardian of the depths Generic resource pool — connections, clients, SMTP
Pegasus Born from Poseidon's blood, ridden by heroes HTTP framework — routing, middleware, providers
Hermes (this lib) Messenger of the gods, guide between realms Redis client — fast key-value, pub/sub, messaging

License

BSD License

Credits

  • Ararat Synapse — socket library for Pascal
  • Inspired by improvements over delphiredisclient

🇧🇷 Leia este documento em português: README_pt-br.md