Cluster Cache: Topology and Node Setup

Cluster Cache: Topology and Node Setup#

This page explains the runtime API and the exact pattern for adding the third, fourth, or Nth independently running application node.

Bootstrap one runtime per node#

Build the Node Cache configuration, Cluster configuration, and transport once in your application’s service container or bootstrap. Keep the resulting ClusterRuntime and inject either the runtime or its cache facade where needed.

use Infocyph\CacheLayer\Cluster\ClusterCache;
use Infocyph\CacheLayer\Cluster\ClusterCacheConfig;
use Infocyph\CacheLayer\Node\NodeCacheConfig;

$runtime = ClusterCache::create(
    node: new NodeCacheConfig(
        sqliteFile: '/var/cache/catalog/node-cache.sqlite',
        namespace: 'catalog',
        lockDirectory: '/var/cache/catalog/locks',
    ),
    cluster: new ClusterCacheConfig(
        cluster: 'production',
        nodeId: gethostname() ?: 'catalog-unknown-node',
    ),
    transport: $transport,
);

$cache = $runtime->cache();

The returned runtime exposes this small coordination API:

Method

Meaning

cache(): Cache

The normal local Node Cache facade.

invalidateKey(string $key)

Invalidate one key locally and publish an event.

invalidateTag(string $tag)

Invalidate one tag locally and publish an event.

clearNamespace()

Clear the local namespace and publish an event.

invalidateTags(array $tags)

Publish one invalidation for each unique tag.

consume(?int $limit)

Replay up to limit events and return the count read.

recoverIfRequired(): bool

Perform retention-gap recovery; normally called by consume.

status(): ClusterStatus

Report cursor, transport, consume, and recovery health.

The critical boundary is $runtime->cache(): all ordinary get(), set(), remember(), delete(), and clear() calls are local only. Use the runtime’s three invalidation methods when an operation must be distributed.

Adding the third, fourth, or Nth node#

There is no central cluster object that receives a list of node instances and no node-registration call. A cluster is formed when independently running application nodes use the same cluster name and shared transport. Each node creates its own runtime, local SQLite file, and consumer.

For example, this bootstrap function is deployed on every web/worker node. The deployment supplies a different $nodeId on each machine or instance:

use Infocyph\CacheLayer\Cluster\ClusterCache;
use Infocyph\CacheLayer\Cluster\ClusterCacheConfig;
use Infocyph\CacheLayer\Cluster\ClusterRuntime;
use Infocyph\CacheLayer\Cluster\Transport\InvalidationTransportInterface;
use Infocyph\CacheLayer\Node\NodeCacheConfig;

function bootCatalogCluster(string $nodeId, InvalidationTransportInterface $transport): ClusterRuntime
{
    return ClusterCache::create(
        node: new NodeCacheConfig(
            sqliteFile: "/var/cache/catalog/{$nodeId}.sqlite",
            namespace: 'catalog',
        ),
        cluster: new ClusterCacheConfig(
            cluster: 'production-catalog', // identical on every node
            nodeId: $nodeId,                // unique on every node
        ),
        transport: $transport,              // points to one shared event store
    );
}

$nodeOne = bootCatalogCluster('catalog-web-01', $transport);
$nodeTwo = bootCatalogCluster('catalog-web-02', $transport);
$nodeThree = bootCatalogCluster('catalog-web-03', $transport);
// Continue with any number of nodes.

In a real deployment these three calls run in separate processes/machines—not in one PHP request. Each process normally creates its own PDO connection to the same database or its own client connection to the selected transport.

The node-addition flow is:

  1. Provision a private local cache directory/SQLite file for the new node.

  2. Give it a unique nodeId while retaining the existing cluster name, namespace, and transport configuration.

  3. Start its consumer before or alongside application traffic.

  4. The new node begins with no cursor, safely reads retained events, and then serves local cache reads/writes like every other node.

  5. Existing nodes require no configuration change; they already publish into the common transport.

When node one executes $nodeOne->invalidateKey('product.42'), it invalidates its own cache and publishes an event. Nodes two, three, and every later node apply that event when their individual consumers next call consume().