perf(loki.process): Run stages that implement the Processor interface together to reduce channel usage - #6882
Draft
csmarchbanks wants to merge 2 commits into
Draft
Conversation
Recognizes stages wrapped via the Processor interface (docker, label_drop, label_keep, logfmt, luhn, output, pattern, replace, static_labels, template, tenant, timestamp) as safe to run as plain function calls instead of their own channel and goroutine, since Processor.Process can never drop or fan out an entry. Pipeline.Run fuses maximal contiguous runs of these into one goroutine; matcherStage caches the same fusion for its nested pipeline at construction, recursing through further nested match blocks. Every other stage (json, drop, sampling, limit, split_json, multiline, etc.) keeps its existing Run()-based channel behavior unchanged. Assisted-by: Claude Sonnet 5
Self-review turned up two real coverage gaps: a top-level pipeline with qualifying stages on both sides of a non-qualifying one (exercises the flush/restart boundary in Pipeline.Run), and two independent match blocks where a drop precedes an unrelated keep (exercises composeNarrow's skip short-circuit across match boundaries, not just within one). Also fixes the trySyncNarrow comment, which omitted "regex" from the list of toStage-wrapped stages that silently qualify for fusion. Assisted-by: Claude Sonnet 5
csmarchbanks
force-pushed
the
loki-process-narrow-processor-fusion
branch
from
August 13, 2026 22:41
4666443 to
55c7007
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brief description of Pull Request
Alternative, smaller-diff approach to the same problem as #6873 and #6875:
loki.processgives every pipeline stage its own goroutine and unbuffered channel, even though most stages are pure synchronous single-entry transforms.This PR doesn't introduce a new stage interface or touch any stage implementation. It recognizes stages already wrapped via the existing
Processorinterface (docker,label_drop,label_keep,logfmt,luhn,output,pattern,regex,replace,static_labels,template,tenant,timestamp) as safe to call directly as plain functions instead of through a channel, sinceProcessor.Processhas no return value to signal drop or fan-out.Pipeline.Runfuses maximal contiguous runs of these into one goroutine;matcherStagecaches the same fusion for its nested pipeline at construction, recursing through further nestedmatchblocks. Every other stage (json,labels,drop,sampling,limit,metric,pack,truncate,windowsevent,decolorize,eventlogmessage,geoip,structured_metadata[_drop],split_json,cri,multiline) keeps its existingRun()-based channel behavior unchanged. Diff is two non-test files (match.go,pipeline.go).This PR is chained on top of #6879 (shared benchmark commit).
git checkout loki-process-benchmarksvsgit checkout loki-process-narrow-processor-fusionreproduces the numbers below.benchstat,GOMAXPROCS=2,-count=10:PipelineManyRulesis one stream, mostly idle.PipelineOneMultilineAmongManyRuleschecks that a single stage needing its own channel (nestingstage.multiline) doesn't drag the rest of a 1000-rule pipeline back to the old per-stage-channel cost.PipelineManyStreamsSaturatedmodels many concurrent streams competing for the CPU, which is the throughput number that matters for a loaded instance: +564% entries/sec, with -80% memory and -82% allocs.Directly against #6873 (same benchmarks,
GOMAXPROCS=2, both branches diffed against the same #6879 base): latency and throughput are within noise of each other (geomean -0.11%) on this benchmark suite, because every rule here isstage.static_labels, which both PRs already fuse identically. This suite can't show the actual coverage difference: #6873 additionally fusesstage.labels,drop,sampling,limit,json,metric,pack,truncate,windowsevent,decolorize,eventlogmessage,geoip, andstructured_metadata[_drop]by retrofitting those 13 stage implementations to a newSyncStageinterface (a 25-file diff); this PR fuses only the pre-existingProcessor-wrapped set and leaves every other stage's implementation untouched. OnPipelineManyStreamsSaturated, this PR also shows ~24% more memory and ~2x the allocs/op of #6873 (16.15k vs 8.06k) — traced toPipeline.Runrecomputing which stages fuse on every call instead of caching that decision once at construction the way #6873'ssyncFndoes; in productionRunis only called once per component (re)configuration, so this doesn't scale with traffic, but it's a real, fixable gap relative to #6873's approach.Verified with
go test -raceacrossinternal/component/loki/process/...andgolangci-lint(no new findings).Pull Request Details
Issue(s) fixed by this Pull Request
Notes to the Reviewer
PR Checklist