Signals Reference
Arsenic detects 62 signals across three severity levels — 30 semantic signals plus 32 Redis command signals. Every signal includes a human-readable explanation — not just a metric. Click any signal to view its full documentation page.
🔴 Critical Signals (14)
High-impact issues requiring immediate attention. Route these to PagerDuty or on-call systems.
hot_pathcriticalSlow query on a high-frequency execution path — both slow AND frequent.
Fix: Add indexes, implement caching, add pagination.
n_plus_onecriticalN queries fired in a loop where one batched query would do.
Fix: Use populate(), include, or a single IN clause.
unbounded_querycriticalNo LIMIT/take — can return entire collections on large datasets.
Fix: Always add pagination limits to list queries.
blocking_iocriticalSynchronous operation blocking the Node.js event loop.
Fix: Use async alternatives; move CPU work to worker threads.
retry_loopcriticalQuery retrying excessively — often signals deadlock or contention.
Fix: Investigate root cause; add backoff and retry limits.
write_contentioncriticalMultiple concurrent writes to the same document/row.
Fix: Use atomic operations, queues, or optimistic locking.
connection_pool_exhaustioncriticalPool is full — new requests queue or fail.
Fix: Increase pool size, reduce connection hold time, fix leaks.
redis_keyscriticalKEYS performs a full keyspace scan, blocking all Redis ops while running.
Fix: Replace with SCAN cursor iteration.
redis_flushallcriticalWipes every key in every database on the instance. Irreversible.
Fix: Restrict via Redis ACLs. Use targeted DEL or UNLINK.
redis_flushdbcriticalWipes all keys in the selected database. Equally destructive.
Fix: Restrict via ACLs. Use selective key deletion instead.
redis_blpopcriticalBlocks the client connection until data is available or timeout fires.
Fix: Always set a timeout. Use a dedicated connection pool.
redis_brpopcriticalBlocks the client connection until data is available or timeout fires.
Fix: Always set a timeout. Use a dedicated connection pool.
redis_brpoplpushcriticalBlocks until source list has data. Deprecated since Redis 6.2.
Fix: Replace with BLMOVE.
redis_blmovecriticalBlocks client until source list has data or timeout fires.
Fix: Always specify a timeout to prevent indefinite blocking.
⚠️ Warning Signals (32)
Issues worth tracking before they escalate. Route these to Slack or dashboards.
slow_querywarningQuery duration exceeded slowQueryThresholdMs.
Fix: Add indexes, optimise query shape, or increase threshold.
fan_outwarningOne HTTP request fired many DB queries — aggregated latency adds up.
Fix: Batch queries, use DataLoader, or denormalise.
high_variance_latencywarningSame query runs fast sometimes, slow others — suggests lock contention.
Fix: Investigate locks, index fragmentation, or cold cache.
high_cpuwarningExcessive CPU used during query execution.
Fix: Profile the query; check for full scans or regex filters.
high_memorywarningLarge heap allocation during query — often from unbounded results.
Fix: Add LIMIT, use streaming, or paginate results.
large_payloadwarningResponse payload is very large — high serialisation cost.
Fix: Add projection, pagination, or field exclusion.
deprecated_apiwarningCalling a deprecated driver or ORM API.
Fix: Migrate to the recommended replacement.
overfetchingwarningSelecting all fields when only a subset is used.
Fix: Add field projection / select() to limit returned fields.
read_heavy_hotspotwarningSame records read at very high frequency — cache pressure.
Fix: Cache hot records; add read replicas for scaling.
redis_hgetallwarningFetches all hash fields even when only a few are needed.
Fix: Use HMGET with explicit field names, or HSCAN.
redis_smemberswarningReturns the full set — no pagination, no limit.
Fix: Use SSCAN to iterate, or SRANDMEMBER for sampling.
redis_lrangewarningWide ranges on large lists are O(N) in the range size.
Fix: Use narrow ranges or cursor-based pagination.
redis_sortwarningSORT is O(N+M*log(M)) — expensive on large collections.
Fix: Pre-sort at write time using sorted sets.
redis_scanwarningSCAN are O(N) when fully iterated.
Fix: Use count hints; move full scans to background jobs.
redis_sscanwarningSSCAN iterates all set members across batches — same total cost as SMEMBERS when run to completion.
Fix: Use COUNT hints. Prefer SISMEMBER for point lookups on small sets.
redis_hscanwarningHSCAN iterates all hash fields — same total cost as HGETALL when run to completion.
Fix: Use COUNT hints. Use HGET/HMGET for targeted field access.
redis_zscanwarningZSCAN iterates all sorted set members — same total cost as ZRANGE 0 -1 when run to completion.
Fix: Use COUNT hints. Use ZSCORE or ZRANK for point lookups.
redis_sunionwarningSUNION iterates all members of all input sets.
Fix: Cache results for stable inputs.
redis_sinterwarningSINTER iterates all members to compute intersection.
Fix: Cache results for stable inputs.
redis_sdiffwarningSDIFF iterates all input sets to compute difference.
Fix: Cache results for stable inputs.
redis_sunionstorewarningSame O(N) cost as SUNION plus a write.
Fix: Run as background job, cache with TTL.
redis_sinterstorewarningSame O(N) cost as SINTER plus a write.
Fix: Run as background job, cache with TTL.
redis_sdiffstorewarningSame O(N) cost as SDIFF plus a write.
Fix: Run as background job, cache with TTL.
redis_zrangewarningO(log(N)+M) — linear in the number of elements returned.
Fix: Paginate with LIMIT offset count.
redis_zrangebyscorewarningLinear in the number of elements in the score range.
Fix: Paginate with LIMIT offset count.
redis_zrangebylexwarningLinear in the number of elements in the lex range.
Fix: Paginate with LIMIT offset count.
redis_zrevrangewarningSame cost as ZRANGE in reverse — linear in returned elements.
Fix: Paginate with LIMIT.
redis_zrevrangebyscorewarningSame cost as ZRANGEBYSCORE in reverse.
Fix: Paginate with LIMIT.
redis_zinterstorewarningO(N*K)+O(M*log(M)) — scales with number of sets and their sizes.
Fix: Cache results, run in background workers.
redis_zunionstorewarningO(N)+O(M*log(M)) — linear in total members across all input sets.
Fix: Cache results, run in background workers.
redis_objectwarningOBJECT ENCODING/REFCOUNT/IDLETIME adds overhead in hot paths.
Fix: Use only for diagnostics, never in application hot paths.
redis_waitwarningBlocks the client until N replicas confirm the write or timeout fires.
Fix: Set a reasonable timeout; use only where strong consistency is required.
ℹ️ Info Signals (16 — opt-in)
Positive signals confirming healthy patterns. Disabled by default to reduce noise. Enable with emitPositiveSignals: true.
Info signals are disabled by default
const monitor = createMonitor({
emitPositiveSignals: true, // opt-in
exporter: (event) => {
if (event.signals.includes('cache_candidate')) {
console.log('Consider caching:', event.model, event.operation);
}
},
});fast_queryinfoQuery completed significantly under slowQueryThresholdMs.
bounded_queryinfoQuery includes a LIMIT/take clause — result size is bounded.
indexed_lookupinfoQuery is hitting an index — not a full collection scan.
stable_latencyinfoLow variance in query duration — predictable performance.
cached_queryinfoResult served from cache — no database round trip needed.
index_hitinfoQuery plan confirms index usage.
single_queryinfoOnly one query fired for this request — no N+1 detected.
optimized_joininfoJoin is using indexes — not a nested loop full scan.
connection_reusedinfoReusing an existing pooled connection — no handshake overhead.
low_memoryinfoQuery allocated minimal heap memory.
low_cpuinfoLow CPU usage during query execution.
stable_responseinfoResponse time matches established baseline — no regression.
cache_candidateinfoRepeated identical query — a good candidate for caching.
healthy_hot_pathinfoHigh-frequency path with consistently fast execution. Healthy.
redis_multiinfoMULTI itself is O(1). Arsenic tracks it to observe transaction boundaries and detect long or unmatched blocks.
redis_execinfoA slow EXEC means the queued commands are expensive, not EXEC itself. Always handle the null return from a WATCH conflict.
Signal routing patterns
import { createMonitor, SignalSeverity } from '@periodic/arsenic';
const monitor = createMonitor({
exporter: (event) => {
switch (event.severity) {
case SignalSeverity.CRITICAL:
sendToPagerDuty(event);
break;
case SignalSeverity.WARNING:
sendToSlack(event);
break;
case SignalSeverity.INFO:
logger.info('db.event', event);
break;
}
},
});Signal-based filtering
const monitor = createMonitor({
exporter: (event) => {
const criticalSignals = ['hot_path', 'n_plus_one', 'unbounded_query'];
if (event.signals.some(s => criticalSignals.includes(s))) {
triggerPagerDuty(event);
}
},
});