DragonFly is the in-memory database at the heart of Mundial de Clicks. It stores all vote counters, rate-limit token buckets, clicks-per-minute (CPM) measurements, captcha session quotas, and daily IP limits. Because DragonFly speaks the Redis protocol, the app uses a standard Redis client — no custom driver is needed.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/midudev/mundial-de-clicks/llms.txt
Use this file to discover all available pages before exploring further.
Connection options
The app supports two ways to point at DragonFly.REDIS_URL takes priority when both are defined.
Option A — REDIS_URL (recommended)
A single URL in redis://[:password@]host:port format. In Coolify this is the internal service name; in Docker Compose it is the container name.
Option B — individual variables
Three separate variables: DRAGONFLY_HOST, DRAGONFLY_PORT, and optionally DRAGONFLY_PASSWORD. These are ignored whenever REDIS_URL is present.
In local development you don’t need to set either option. The app defaults to
redis://localhost:6379, which is exactly where docker compose up -d dragonfly
binds the container.Key schema
The table below documents every DragonFly key used by the app. All keys are managed atomically using native Redis/DragonFly commands (ZINCRBY, INCR, HSET, EXPIRE, etc.) — no Lua scripts or transactions are required.
| Key pattern | Type | Description |
|---|---|---|
votes:ranking | Sorted set (ZSET) | Country codes as members, total vote counts as scores. Used for the live leaderboard. |
votes:total | Integer | Global counter of all valid votes cast. |
votes:blocked | Integer | Global counter of clicks that were rejected (rate-limited or flagged). |
cpm:{epochSecond} | Integer | Vote count in that one-second bucket. The app sums the last 60 buckets to compute clicks per minute. |
rl:{ip} | Hash | Token-bucket state for rate limiting: fields tokens (remaining) and updated (ms timestamp). |
cap:sess:{id} | Integer | Remaining vote quota for a verified captcha session. |
cap:sess-ip:{id} | String | IP address that created this captcha session. Used to prevent session sharing. |
daily:ip:{ip}:{day} | Integer | Running vote total for an IP address on a given UTC day (YYYY-MM-DD). |
daily:voter:{id}:{day} | Integer | Running vote total for a voter_id cookie on a given UTC day. |
The sorted set
votes:ranking has exactly one member per participating
country. Memory usage for the ranking structure is negligible regardless of
how many votes are cast.Persistence
DragonFly persists data through RDB-style snapshots — it does not support append-only file (AOF) mode. The recommended configuration (used indocker-compose.yml) is:
| Option | Value | Effect |
|---|---|---|
--dir | /data | Directory where the snapshot file is written |
--dbfilename | snapshot | Filename for the RDB snapshot |
--snapshot_cron | */5 * * * * | Snapshot every 5 minutes |
dragonfly-data Docker named volume mounts to /data and persists the snapshot file across container restarts, image upgrades, and host reboots.
In-memory fallback
When neitherREDIS_URL nor DRAGONFLY_HOST is set, config.redis.url resolves to the default redis://localhost:6379. If a connection to that address fails at startup, the app’s hasDragonfly flag is set to false and it falls back to an in-process memory store (castVotesMemory / readWorldMemory).
In fallback mode:
- The full UI, SSE live updates, and vote API all work correctly.
- Votes are not persisted — they are lost on every process restart.
- Rate limiting and daily caps operate against the in-process store (not shared across multiple instances).
Command timeout
REDIS_COMMAND_TIMEOUT_MS (default 2000 ms). If DragonFly does not respond within this window, the request fails immediately instead of hanging indefinitely. This protects the Node event loop and keeps the SSE poller responsive even during a DragonFly hiccup.
Increase this value if DragonFly is under exceptionally heavy write load and you are seeing spurious timeout errors in the logs. Do not set it too high — a long timeout under failure conditions will cause queued requests to pile up.
Production sizing
Throughput
DragonFly handles tens of thousands of ops/sec on a single core. The
Mundial de Clicks workload — one
ZINCRBY, one INCR, and a token-bucket
HSET per vote — is extremely lightweight. Even a VPS with 1–2 vCPUs is
unlikely to saturate DragonFly.Memory
Memory usage is minimal. The ranking ZSET holds one entry per country
(typically 16). Rate-limit hashes and CPM counters are short-lived and
auto-expire. Expect well under 10 MB of working set for a busy election
day.
memlock ulimit
The
ulimits: memlock: -1 entry in docker-compose.yml is required.
DragonFly attempts to lock its working set in physical RAM for predictable
latency. Without this setting, the Linux kernel may refuse the mlock
syscall, causing DragonFly to log errors or degrade performance.Single poller design
The app runs one background poller that reads the full ranking from
DragonFly every second and caches it in process memory. SSE clients receive
that cached snapshot — DragonFly read load is constant regardless of how
many browsers are watching the leaderboard.