← All posts

Using Go 1.25 Flight Recorder in Production

Go 1.25’s flight recorder changes production tracing from a reactive tool into a rolling source of pre-incident evidence. Here is how to use it to inspect scheduler pressure, contention, GC assist work, and short-lived failures in Go services and blockchain infrastructure.

Go 1.25 adds a flight recorder for runtime tracing. The Go blog reports that it keeps a rolling in-memory trace and lets you snapshot it on demand. For teams running Go services in production, this changes how you approach short, hard-to-reproduce failures.

Most performance incidents do not last long. A latency spike, a stalled goroutine, a burst of GC work, a scheduler imbalance. By the time you attach a profiler or turn on full tracing, the event is gone. Traditional tracing is rich, but it is expensive to leave on all the time. A rolling buffer shifts the tradeoff. You keep recent runtime history nearby, then export it when a signal tells you something went wrong.

This matters if you own Go APIs, workers, stream processors, or blockchain infrastructure written in Go. Execution clients, indexers, relayers, and signing services often fail in brief windows under load. When your incident only appears for a few seconds, a snapshot of what the runtime saw right before failure is often more useful than logs written after the fact.

What a flight recorder changes in production

The old choice was simple and bad. Either run with low-overhead metrics and logs, then accept blind spots, or enable deep tracing and pay a cost all day. A flight recorder gives you a third option. Keep a bounded trace buffer in memory. Dump it when a trigger fires.

For a practitioner, the shift is operational, not academic.

You get pre-incident context. That is the main value. Postmortems often fail because evidence starts after the alert. A rolling trace gives you the lead-up.

You also get better correlation across runtime subsystems:

  • goroutine creation and blocking
  • scheduler handoffs and runnable backlog
  • network poller activity
  • syscall stalls
  • garbage collection assists and pauses
  • heap pressure around the event window

Those signals help you separate symptoms from causes. A request timeout might look like network trouble in logs. The trace might show a CPU-saturated worker pool, long GC assists, or a mutex bottleneck upstream.

The practical point is this. You should treat flight recording as incident evidence collection, not as another dashboard. It is most useful when paired with explicit capture conditions and a process for preserving the artifact.

What to inspect first in a captured trace

A trace dump is dense. If you start everywhere, you waste time. Start with timing and contention.

First, line up the snapshot with the external symptom. Match the trace window to the alert timestamp, request IDs in logs, or p99 latency movement in metrics. You want a narrow incident story, not a general tour of runtime behavior.

Then inspect four areas.

1. Runnable goroutines versus available CPUs

Look for periods where many goroutines are runnable but not running. This often points to CPU saturation, poor work partitioning, or a bursty workload pinned by GOMAXPROCS limits.

Common failure modes:

  • too many background workers competing with request paths
  • hot loops with little yielding
  • large fan-out jobs arriving at once
  • container CPU limits lower than expected

Verification steps:

  • compare runnable backlog with host and container CPU metrics
  • check whether scheduler pressure aligns with latency growth
  • inspect recent deploys for changed worker counts or batch sizes

2. Blocking and lock contention

A short incident often comes from one lock turning hot for a few seconds. In Go services, this is common around caches, shared maps behind mutexes, connection pools, and rate-limit state.

Look for:

  • goroutines parked on mutexes or channels
  • long waits around pool acquisition
  • a single owner goroutine serializing work

Where teams go wrong:

  • treating average latency as healthy while tails explode
  • using coarse locks around I/O
  • hiding contention behind helper packages with no targeted metrics

3. GC assist pressure and allocation bursts

Stop looking only for long pause times. In many services, the bigger issue is assist work during allocation-heavy bursts. Request handlers pay GC costs while allocating, and throughput drops before anyone notices a pause.

Inspect:

  • allocation spikes before the incident
  • higher assist activity in hot request paths
  • object churn from decoding, buffering, or repeated []byte growth

Typical causes:

  • large temporary objects in JSON or protobuf processing
  • per-request caches
  • excessive string formatting in hot paths
  • missed object reuse in batch pipelines

4. Syscalls and network stalls

Go traces expose time spent waiting on the network and kernel. This is useful when logs only show timeouts.

Check whether the application was idle and waiting, or busy and unable to progress. Those are different incidents with different fixes.

Common sources:

  • exhausted DB or RPC connection pools
  • DNS stalls
  • TLS handshake bursts
  • slow disk during compaction, WAL sync, or snapshot work

For blockchain systems, this is often where client behavior surfaces. A relayer timing out on upstream RPC, or an execution component stalling on disk writes, produces a very different trace signature from internal lock contention.

How to trigger snapshots without flooding storage

A flight recorder only helps if you capture the right windows. Trigger too often and you create noise. Trigger too late and the useful history is already overwritten.

Set capture rules from symptoms you already trust.

Good starting triggers:

  • p99 latency above a threshold for a short sustained window
  • error-rate burst tied to one endpoint or worker type
  • goroutine count jump beyond normal daily range
  • repeated OOM-near conditions or heap-growth anomalies
  • RPC timeout cluster against one dependency

Keep the trigger logic close to your service health model. A single slow request is rarely enough. A short rolling breach across several signals is better.

A practical pattern is two-stage capture:

  1. Detect an anomaly from metrics.
  2. Snapshot the recorder and attach incident metadata.

The metadata matters. Store service version, commit SHA, pod or host identity, container limits, GOMAXPROCS, and a compact view of concurrent alerts. Without this, later comparison across traces becomes guesswork.

Where this commonly fails:

  • trace files are overwritten by the next event
  • alerts fire after pod eviction or restart
  • capture runs on every replica, creating too much data
  • no retention policy exists for incident artifacts

A simple fix is leader-only capture per workload shard, with rate limits and artifact naming tied to incident time.

Where flight recording helps most in Go blockchain systems

Blockchain infrastructure has many short-lived failure modes. Go remains common in execution clients, tooling, RPC gateways, indexers, and signing-adjacent services. These systems often combine high concurrency with uneven external dependencies.

Flight recording is useful in a few recurring cases.

RPC gateways and indexers

Symptoms:

  • brief latency spikes during reorgs or popular contract events
  • backlog growth after upstream node slowdown
  • timeout bursts on archive reads

What to inspect:

  • worker saturation versus upstream wait time
  • channel and queue buildup between fetch, decode, and persist stages
  • allocation churn in ABI decode and JSON-RPC marshaling

Relayers and bridge components

Symptoms:

  • missed submission windows
  • nonce management stalls
  • duplicate retry storms

What to inspect:

  • lock contention around signer state and nonce tracking
  • timer behavior and goroutine leaks in retry loops
  • syscall waits during key access, RPC submission, or durable writes

Validators and node-adjacent services

Symptoms:

  • brief missed duties
  • delayed mempool processing
  • snapshot or compaction interference

What to inspect:

  • scheduler pressure during batch verification or state transitions
  • disk-related blocking during compaction or snapshot operations
  • GC pressure tied to state object churn

The key is to define expected failure signatures before the incident. If you know what backlog growth, lock contention, or assist pressure looks like in your system, a captured trace becomes evidence, not mystery.

How to verify fixes after you find a signal

A trace points to a runtime pattern. It does not prove the code change on its own. Verification needs controlled comparison.

After you identify a likely issue, test in three steps.

First, reproduce the pressure shape, not the exact incident. If the trace showed allocator churn under bursty decode work, recreate bursty decode work. If it showed mutex pressure around a shared cache, hit the cache with the same concurrency pattern.

Second, compare before and after traces under the same load. Do not rely only on end-user latency. Check whether the runtime pattern changed:

  • fewer runnable goroutines waiting for CPU
  • shorter mutex hold and wait periods
  • lower assist activity
  • less time parked on network or syscalls

Third, confirm the cost of always-on recording in your environment. A bounded recorder is lighter than continuous export, but overhead still exists. Measure CPU, memory, and artifact size under representative traffic. Keep those numbers in your runbook.

This is also where operational tooling matters. If you run many Go services, keep a standard trace-capture path, naming scheme, and retention policy across them. Consistency speeds up incident review more than one-off heroics.

What to watch next

The flight recorder in Go 1.25 moves tracing closer to routine production operations. The next step for most teams is policy, not code. Define when to snapshot, where to store artifacts, and who reviews them. Then test the whole path during a controlled fault.

If your services depend on LLM components alongside Go backends, it is worth pressure-testing those prompt boundaries too. Pigfox’s Prompt-Injection & System-Prompt-Leak Tester fits that specific part of the stack. For pure Go runtime work, the main task is simpler. Capture evidence before the incident evaporates.