Elyra
Elyra The coding agent e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Conductor Local project conductor Elyra SQL Anywhere Replication-ready SQL engine
Release notes
Changelog
Elyra
Shared cache

Shared cache

Askr ships a small shared-memory cache exposed to PHP. It's a fixed-slot hash table living in an anonymous shared mmap the master creates before forking, so every worker process sees the same physical table — no IPC, no locks on the hot path beyond a per-slot spinlock.

This gives you cache, atomic counters (rate limiting) and locks in the Askr binary — no Redis for small/mid deployments.

Enabling it

askr serve --root ./public --worker-script examples/laravel-worker.php \
  --cache-slots 16384

or in askr.toml:

[cache]
slots = 16384   # each slot is ~4.3 KB; 16384 ≈ 70 MB

The cache is off when slots = 0 (the default). Sizing: pick roughly expected_entries × 1.3. Each slot holds one entry inline; keys are capped at 250 bytes and values at ~4 KB (larger values simply aren't cached).

PHP API

When the cache is enabled, these functions are available to any PHP running under Askr (web workers, the worker loop, and queue/scheduler sidecars):

Function
askr_cache_get(string $key): ?string Value, or null on miss/expiry.
askr_cache_set(string $key, string $value, int $ttl = 0): bool ttl seconds (0 = forever). false if too large.
askr_cache_delete(string $key): bool true if it existed.
askr_cache_increment(string $key, int $delta = 1, int $ttl = 0): int Atomic add; returns the new value.
askr_cache_flush(): void Empty the table.
askr_cache_set('greeting', 'hello', 60);
echo askr_cache_get('greeting');              // hello  (from any worker)
$n = askr_cache_increment('rate:'.$ip, 1, 60); // atomic across all workers

The counter is atomic across every worker process — ideal for rate limiting.

Laravel cache driver

examples/AskrCacheStore.php is a Laravel Store backed by these functions. Register it in your worker script or a service provider:

use Illuminate\Support\Facades\Cache;

require '/opt/askr/examples/AskrCacheStore.php';

Cache::extend('askr', fn ($app) =>
    Cache::repository(new AskrCacheStore(config('cache.prefix', ''))));

Add a store in config/cache.php (or set CACHE_STORE=askr):

'askr' => ['driver' => 'askr'],

Then use the cache normally — it's shared across all workers:

Cache::put('user:1', $user, 300);
$user = Cache::get('user:1');
Cache::increment('hits');                       // atomic
Cache::remember('report', 600, fn () => build()); // computed once, shared

Integers/floats are stored unserialized so increment()/decrement() (used by Laravel's rate limiter) are truly atomic in shared memory; other values are serialized.

Semantics & limits

  • Eviction: on a hash collision beyond the probe window (16 slots), the colliding entry is overwritten. Sizing the table generously avoids this.
  • TTL is lazy (checked on read) — expired entries free their slot on the next access to it.
  • Best-effort under crashes: a per-slot spinlock is stolen if a holder dies mid-operation, so a crashed worker can't deadlock the table; a torn write can only yield a stale/garbage value (never memory unsafety — reads are length clamped), which a cache tolerates.
  • Not persistent: the table lives in RAM and is empty on restart.

For very large values, many GB of data, or cross-host sharing, use a real cache (Redis/Memcached) — Askr's cache targets the common single-host case.