Skip to main content

Multi-Server (Cluster) Mode

Mumara Campaigns can run across several application servers behind a load balancer. Since version 7.1, an optional Multi-server (cluster) mode keeps the configuration on every server in sync automatically: a settings change saved on any one server is applied to all servers within a minute, with no manual step on each machine.

This page describes the required deployment layout, how to enable cluster mode, how the synchronization works, and how scheduled tasks behave in a cluster.

Single-server installations

Cluster mode is off by default and is meant purely for load-balanced, multi-server deployments. On a single server there is nothing to synchronize — leave it off, and nothing about your installation changes.


Deployment Layout

A Mumara cluster consists of two or more application servers ("nodes") that share the same database and the same storage. Each node runs the full Mumara codebase and its own web server and cron.

ResourceScopeNotes
Database (MySQL/MariaDB)SharedAll nodes point at the same database server
storage/ directorySharedMounted from shared storage (e.g. an NFS export) on every node
.env fileSharedKeep a single .env on the shared storage and symlink each node's .env to it (see below)
Application code (public_html/)Per nodeEach node has its own copy of the codebase
bootstrap/cache/Per nodeHolds each node's own compiled config cache — must not be shared
Cron (schedule:run)Per nodeEvery node runs the standard every-minute scheduler cron
Cache storeShared (recommended)Use redis or database for CACHE_STORE so cross-server task locks work — see Scheduled Tasks in a Cluster

Shared .env

Most Application Settings live in the shared database and storage, but a few (queue driver, application URL/name, environment, debug mode) are written to the server's .env file. For those to propagate across the cluster, the .env must be shared too:

  1. Place the real .env on the shared storage (the same mount storage/ uses).
  2. On each node, replace the local .env with a symlink to the shared file.

Mumara's managed cluster deployment scripts set this up automatically: the first server creates the shared .env, and each additional server links to it.


Enabling Cluster Mode

There are two ways to enable it:

  • Application Settings — go to Settings → App Settings → General and turn on Multi-server (cluster) mode. This is the normal way and can be toggled at runtime.
  • Environment variable — set CLUSTER_MODE=true in the (shared) .env. This force-enables cluster mode regardless of the UI toggle and is intended for environment-driven deployments.

With cluster mode on, the scheduler on every node registers the config:sync-cluster command to run every minute. With it off, the command is never scheduled and the whole mechanism is dormant — single-server installations pay no cost.


How Configuration Sync Works

Laravel freezes configuration into a per-server compiled cache (bootstrap/cache/config.php). Without cluster mode, saving Application Settings rebuilds that cache only on the node that served the request — the other nodes keep serving a stale snapshot, so a change can appear to "not stick" depending on which server a later request lands on.

Cluster mode closes that gap with a shared epoch counter:

  1. Every settings save (and every .env write made through the application) bumps a shared config_cache_epoch value stored in the database, which all nodes can see.
  2. Each node's config:sync-cluster run (every minute) compares that shared epoch against a node-local marker (bootstrap/cache/settings_epoch).
  3. When a node is behind, it rebuilds its own config cache (php artisan config:cache) from the already-shared settings data and records the new epoch. A local file lock prevents overlapping rebuilds on the same node.

The settings data itself needs no copying — it already lives in the shared database, the shared settings file on storage/, and the shared .env. Each node only re-freezes that shared data into its local cache.

There is no Redis dependency and no per-request cost; the only overhead is a sub-second check once per minute per node.


Scheduled Tasks in a Cluster

Every node runs the standard cron entry (php artisan schedule:run every minute). Mumara's scheduler is cluster-aware:

  • Tasks that must run exactly once per cluster — campaign sending and preparation, evergreen scheduling, stuck-campaign resume/recovery, suppression processing, segment processing and counting, credit/limit resets, database alterations, maintenance, statistics rollups, and update/addon checks — are protected with Laravel's onOneServer() lock, so only one node executes them per tick. This prevents double-prepared campaigns (duplicate sends), duplicate copy/move inserts, duplicate notifications, and double DDL.
  • Tasks that must run on every node — processing each node's own local tracking spool files (email:opened, email:clicked), cleaning node-local exported files, feed fetching, and config:sync-cluster itself — deliberately run on all nodes.
Shared cache store required for cross-server locks

onOneServer() relies on a cache store that all nodes shareredis, memcached, or database. Set CACHE_STORE accordingly in a cluster. With the file cache driver the lock is only as shared as the cache directory: if storage/framework/cache is on the shared NFS mount it still works, but if each node caches locally, every node will acquire its "own" lock and protected tasks will run on all nodes anyway.


Verifying the Cluster

After enabling cluster mode:

  1. Save any Application Setting on one node.
  2. Confirm the shared epoch advanced:
    SELECT setting_value FROM application_settings
    WHERE setting_name = 'config_cache_epoch';
  3. Within a minute, every node's bootstrap/cache/settings_epoch file should contain the same value, and the changed setting should be live on all nodes regardless of which one the load balancer picks.

Troubleshooting

SymptomLikely cause
Settings change only applies on one serverCluster mode is off, or cron is not running on the other nodes
Queue driver / app URL changes don't propagateThe nodes' .env files are separate copies instead of symlinks to the shared .env
A scheduled task runs on every node (duplicate emails, duplicate notifications)CACHE_STORE is file with node-local cache directories — switch to redis or database
config:sync-cluster never appears in the schedulerCluster mode is not enabled (the command is only registered while the toggle or CLUSTER_MODE is on)