Engineering guides

Backend systems, Node.js, TypeScript, AI coding, fintech infrastructure, and engineering career proof.

Back to engineering guides

Latest posts

Page 2 of 14

Redis eviction policies: choose what may disappear

An eviction policy is a data-loss policy when Redis stores anything authoritative, so separate durable state from disposable cache before tuning memory.

Negative caching in Redis: cache missing without hiding new data

Caching a not-found result can block repeated abusive lookups, but its TTL should be shorter because the missing record may be created soon.

Stop cache stampedes with stale-while-revalidate in Redis

When a popular key expires, serving a bounded stale value while one worker refreshes it can protect the origin from a synchronized traffic spike.

Redis Lua idempotency keys: reserve, complete, and replay

An idempotency record needs states for in-progress and completed work so concurrent retries do not both execute and completed retries receive the same response.

Redis distributed locks need fencing tokens

A lock can expire while its original holder is paused, so downstream systems need a monotonically increasing fencing token to reject stale owners.

Trim Redis Streams without deleting unread events

Stream retention should account for the slowest required consumer and replay window instead of trimming only to a convenient item count.

Recover stuck Redis Streams messages with XAUTOCLAIM

XAUTOCLAIM moves idle pending messages to a healthy consumer, but the handler must tolerate receiving work more than once.

Redis client-side caching in Node.js: invalidation without stale data

Server-assisted client-side caching reduces network reads, but reconnects, invalidations, TTLs, and memory limits decide whether values stay safe.

PgBouncer transaction pooling and prepared statement surprises

Transaction pooling reuses server connections across clients, so session assumptions and prepared statement behavior must match the PgBouncer version and driver settings.

PostgreSQL covering indexes with INCLUDE: avoid over-wide keys

INCLUDE columns can enable index-only scans without affecting key order, but every extra column increases index size and write cost.

JSONB GIN indexes: jsonb_ops vs jsonb_path_ops

The default GIN operator class supports more query forms while jsonb_path_ops can be smaller and faster for containment-heavy workloads.

PostgreSQL advisory locks: choose the lock key carefully

Advisory locks coordinate application-defined resources, but PostgreSQL does not know whether two integer keys represent the same business object.

SKIP LOCKED job queues in PostgreSQL: safe worker pattern

FOR UPDATE SKIP LOCKED lets workers claim different rows concurrently, but leases, retries, ordering, and poison jobs still need explicit design.

Retry PostgreSQL deadlocks without repeating side effects

A deadlock aborts one transaction by design, so retry the whole transaction only when surrounding side effects are idempotent or deferred.

statement_timeout vs lock_timeout in PostgreSQL

lock_timeout limits time waiting for a lock while statement_timeout limits the total statement duration; production APIs often need both at different values.

Use OLD and NEW in PostgreSQL RETURNING clauses

PostgreSQL 18 can return old and new values from data-changing statements, reducing extra reads when building change records.

Virtual generated columns in PostgreSQL 18: compute on read

Virtual generated columns avoid stored duplicate values but charge computation on reads and allow only suitable immutable expressions.

PostgreSQL uuidv7(): ordered UUIDs without application generators

PostgreSQL 18 can generate time-ordered UUIDv7 values, improving index locality while preserving globally unique opaque identifiers.

Handle unhandled rejections in Node.js without pretending recovery

An unhandled rejection can leave unknown application state, so log structured context, begin graceful shutdown, and fix the missing ownership path.

Undici connection pools: avoid socket explosions in Node.js

Outbound HTTP needs bounded pools per origin so traffic spikes do not create unlimited sockets or hide slow dependencies behind queues.

HTTP keep-alive tuning in Node.js behind a load balancer

Keep-alive timeouts must align across client, server, proxy, and load balancer or reused sockets will fail at awkward boundaries.

Node.js stream backpressure: stop buffering the whole response

Backpressure means a producer slows down when the consumer cannot accept more data, preventing memory growth under slow clients.

Cancel child processes safely with AbortController in Node.js

Canceling the parent promise is not enough; the child process must receive a signal, respect a deadline, and be reaped without leaving descendants.

Worker threads for CPU-heavy Node.js work: when they help

Worker threads help CPU-bound JavaScript by moving computation off the event loop, but transfer cost and memory can erase the benefit for tiny jobs.

Enforce coverage thresholds with node:test

Coverage thresholds are useful as a regression floor, not proof that behavior is tested or assertions are meaningful.

Mock timers with the Node.js test runner without flaky sleeps

Mocked time makes expiry and retry tests deterministic when application code reads time through supported APIs rather than waiting in real time.

Node.js permission model: reduce what a compromised process can reach

Process permissions add defense in depth by restricting files, child processes, workers, and other capabilities the application does not need.

AsyncLocalStorage in Node.js: request context without parameter chains

AsyncLocalStorage can carry request IDs and tenant context through asynchronous work, but context must start at a trusted boundary and never replace authorization checks.

TypeScript satisfies vs annotation: preserve useful inference

The satisfies operator checks compatibility while preserving the expression’s narrower inferred type, unlike some broad annotations.

Declaration maps in TypeScript: debug the source, not dist files

Declaration maps let editor navigation return to original TypeScript source, but published paths must match the package contents.