As33
@periodic/
arsenic

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.

14
14 Critical
32
32 Warning
16
16 Info

🔴 Critical Signals (14)

High-impact issues requiring immediate attention. Route these to PagerDuty or on-call systems.

hot_pathcritical

Slow query on a high-frequency execution path — both slow AND frequent.

Fix: Add indexes, implement caching, add pagination.

n_plus_onecritical

N queries fired in a loop where one batched query would do.

Fix: Use populate(), include, or a single IN clause.

unbounded_querycritical

No LIMIT/take — can return entire collections on large datasets.

Fix: Always add pagination limits to list queries.

blocking_iocritical

Synchronous operation blocking the Node.js event loop.

Fix: Use async alternatives; move CPU work to worker threads.

retry_loopcritical

Query retrying excessively — often signals deadlock or contention.

Fix: Investigate root cause; add backoff and retry limits.

write_contentioncritical

Multiple concurrent writes to the same document/row.

Fix: Use atomic operations, queues, or optimistic locking.

connection_pool_exhaustioncritical

Pool is full — new requests queue or fail.

Fix: Increase pool size, reduce connection hold time, fix leaks.

redis_keyscritical

KEYS performs a full keyspace scan, blocking all Redis ops while running.

Fix: Replace with SCAN cursor iteration.

redis_flushallcritical

Wipes every key in every database on the instance. Irreversible.

Fix: Restrict via Redis ACLs. Use targeted DEL or UNLINK.

redis_flushdbcritical

Wipes all keys in the selected database. Equally destructive.

Fix: Restrict via ACLs. Use selective key deletion instead.

redis_blpopcritical

Blocks the client connection until data is available or timeout fires.

Fix: Always set a timeout. Use a dedicated connection pool.

redis_brpopcritical

Blocks the client connection until data is available or timeout fires.

Fix: Always set a timeout. Use a dedicated connection pool.

redis_brpoplpushcritical

Blocks until source list has data. Deprecated since Redis 6.2.

Fix: Replace with BLMOVE.

redis_blmovecritical

Blocks 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_querywarning

Query duration exceeded slowQueryThresholdMs.

Fix: Add indexes, optimise query shape, or increase threshold.

fan_outwarning

One HTTP request fired many DB queries — aggregated latency adds up.

Fix: Batch queries, use DataLoader, or denormalise.

high_variance_latencywarning

Same query runs fast sometimes, slow others — suggests lock contention.

Fix: Investigate locks, index fragmentation, or cold cache.

high_cpuwarning

Excessive CPU used during query execution.

Fix: Profile the query; check for full scans or regex filters.

high_memorywarning

Large heap allocation during query — often from unbounded results.

Fix: Add LIMIT, use streaming, or paginate results.

large_payloadwarning

Response payload is very large — high serialisation cost.

Fix: Add projection, pagination, or field exclusion.

deprecated_apiwarning

Calling a deprecated driver or ORM API.

Fix: Migrate to the recommended replacement.

overfetchingwarning

Selecting all fields when only a subset is used.

Fix: Add field projection / select() to limit returned fields.

read_heavy_hotspotwarning

Same records read at very high frequency — cache pressure.

Fix: Cache hot records; add read replicas for scaling.

redis_hgetallwarning

Fetches all hash fields even when only a few are needed.

Fix: Use HMGET with explicit field names, or HSCAN.

redis_smemberswarning

Returns the full set — no pagination, no limit.

Fix: Use SSCAN to iterate, or SRANDMEMBER for sampling.

redis_lrangewarning

Wide ranges on large lists are O(N) in the range size.

Fix: Use narrow ranges or cursor-based pagination.

redis_sortwarning

SORT is O(N+M*log(M)) — expensive on large collections.

Fix: Pre-sort at write time using sorted sets.

redis_scanwarning

SCAN are O(N) when fully iterated.

Fix: Use count hints; move full scans to background jobs.

redis_sscanwarning

SSCAN 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_hscanwarning

HSCAN 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_zscanwarning

ZSCAN 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_sunionwarning

SUNION iterates all members of all input sets.

Fix: Cache results for stable inputs.

redis_sinterwarning

SINTER iterates all members to compute intersection.

Fix: Cache results for stable inputs.

redis_sdiffwarning

SDIFF iterates all input sets to compute difference.

Fix: Cache results for stable inputs.

redis_sunionstorewarning

Same O(N) cost as SUNION plus a write.

Fix: Run as background job, cache with TTL.

redis_sinterstorewarning

Same O(N) cost as SINTER plus a write.

Fix: Run as background job, cache with TTL.

redis_sdiffstorewarning

Same O(N) cost as SDIFF plus a write.

Fix: Run as background job, cache with TTL.

redis_zrangewarning

O(log(N)+M) — linear in the number of elements returned.

Fix: Paginate with LIMIT offset count.

redis_zrangebyscorewarning

Linear in the number of elements in the score range.

Fix: Paginate with LIMIT offset count.

redis_zrangebylexwarning

Linear in the number of elements in the lex range.

Fix: Paginate with LIMIT offset count.

redis_zrevrangewarning

Same cost as ZRANGE in reverse — linear in returned elements.

Fix: Paginate with LIMIT.

redis_zrevrangebyscorewarning

Same cost as ZRANGEBYSCORE in reverse.

Fix: Paginate with LIMIT.

redis_zinterstorewarning

O(N*K)+O(M*log(M)) — scales with number of sets and their sizes.

Fix: Cache results, run in background workers.

redis_zunionstorewarning

O(N)+O(M*log(M)) — linear in total members across all input sets.

Fix: Cache results, run in background workers.

redis_objectwarning

OBJECT ENCODING/REFCOUNT/IDLETIME adds overhead in hot paths.

Fix: Use only for diagnostics, never in application hot paths.

redis_waitwarning

Blocks 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

They emit on every healthy query and can be very noisy in production. Enable selectively — in development, or routed to a dedicated low-priority log.
typescript
const monitor = createMonitor({
  emitPositiveSignals: true, // opt-in
  exporter: (event) => {
    if (event.signals.includes('cache_candidate')) {
      console.log('Consider caching:', event.model, event.operation);
    }
  },
});

Signal routing patterns

typescript
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

typescript
const monitor = createMonitor({
  exporter: (event) => {
    const criticalSignals = ['hot_path', 'n_plus_one', 'unbounded_query'];
    if (event.signals.some(s => criticalSignals.includes(s))) {
      triggerPagerDuty(event);
    }
  },
});