Skip to content

perf(gateway): avoid redundant Kong writes - #539

Open
ron96g wants to merge 3 commits into
mainfrom
optimize-kong-writes
Open

ron96g wants to merge 3 commits into
mainfrom
optimize-kong-writes

Conversation

@ron96g

@ron96g ron96g commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

Avoid redundant Kong Admin API writes during Gateway reconciliation.

The Kong client now reads the current Service, Route, Consumer, Plugin, Upstream, and Target state, normalizes it to the controller-owned representation, and writes only when the desired configuration differs. This preserves correction of out-of-band Kong changes while preventing no-op upserts during periodic resyncs and duplicate reconciles.

Changes

• Split the former  pkg/kong/client/client.go  into entity-specific client files.
• Add reusable  GET → project → compare → write  reconciliation logic.
• Normalize Kong defaults, JSON value types, and unordered config lists to prevent false differences.
• Reconcile circuit-breaker upstream targets instead of appending identical targets.
• Keep consumer-group membership checks, even when Consumer upserts are skipped.
• Add  gateway_kong_reconcile_total{entity,outcome}  to measure writes, no-op reconciliations, and errors.
• Sort  StringMap  JSON entries for deterministic plugin configuration.
• Add comprehensive tests for unchanged, changed, missing, reordered, and invalid Kong responses.

@ron96g ron96g self-assigned this Aug 4, 2026
@ron96g ron96g added performance Any feature that improves performance stability Improves stability in some way labels Aug 4, 2026
@BjoernKarma

Copy link
Copy Markdown
Contributor

Measured impact of this PR (identical burst test)

Continuing the reproduction methodology from PR #662's status quo comment: same bjoernk vcluster, same 100 Routes / 100 Consumers / 10,000 ConsumeRoutes workload, same burst script (./burst-update.sh bjoernk perf-gateway route 30 50 — 100 Route annotation-only patches × 30 iterations, 50-way parallel, ~13.5 min wall clock), deployed as image optimize-kong-writes.

Note: this branch does not include the watch-predicate fix from #662, so the underlying reconcile fan-out (every Route update still enqueues all subscribing ConsumeRoutes) is still present. This test isolates the effect of the Kong-write dedup alone.

Kong write amplification — eliminated

New metric gateway_kong_reconcile_total{entity,outcome} shows every single Kong-facing reconcile during the burst was detected as a no-op and skipped, despite ~3,000 Route update events flooding through the system:

entity reconciled (unchanged) during burst
route 2,861
service 2,861
plugin 8,553
consumer 105

Zero incremental writes were sent to Kong's Admin API for any of these — confirming the diff-before-write logic works correctly under sustained churn, not just idle-state.

Route controller's own queue wait — improved ~27x

avg queue wait (route controller)
main baseline (status quo) ~10.08s (sum=28,547s / count=2,832)
this PR (optimize-kong-writes) ~0.38s (sum=1,385s / count=3,688)

Skipping the redundant Kong HTTP round-trips makes each Route reconcile complete much faster, freeing the worker sooner — so the Route controller's own queue backs up far less even under the same event volume.

ConsumeRoute controller — still backed up (expected)

ConsumeRoute's average queue wait was still ~6.9s (sum=1,796,938s / count=261,835) during this run. This is expected: this PR doesn't change which events are admitted into the queue, only how expensive processing each admitted item is. The fan-out itself (one Route update → reconcile of all subscribers) is only fixed by #662's watch predicates.

Takeaway

This PR and #662 address two independent parts of the same regression:

Combining both should give the best result — happy to run a combined build next if useful.

A small number of outcome="error" reconciles were also observed (service=31, consumer=2, plugin=1) during the burst; these look like transient/intermittent Kong API errors under load rather than something introduced by this PR, but flagging for visibility.

@BjoernKarma
BjoernKarma marked this pull request as ready for review September 21, 2026 14:04
Copilot AI lite review requested due to automatic review settings September 21, 2026 14:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Critical reconciliation correctness issues remain in plugin bindings and upstream target pagination/selection.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 3 High severity · 1 Medium severity

Open (4)
What changed in this PR

This pull request refactors Kong reconciliation to avoid redundant writes while normalizing state, adding metrics, and expanding test coverage.

Changes:

  • Adds entity-specific reconciliation with GET/project/compare/write logic.
  • Normalizes Kong defaults, unordered values, targets, and plugin configuration.
  • Adds reconciliation metrics and regenerated mocks.
  • Updates dependencies, tooling, circuit-breaker integration, and tests.
File Description
tools/​snapshotter/​go.sum Updates dependency checksums.
tools/​snapshotter/​go.mod Adds indirect dependency metadata.
gateway/​tools/​mockery.yaml Configures mock generation.
gateway/​pkg/​kong/​client/​upstream.go Reconciles upstreams and targets.
gateway/​pkg/​kong/​client/​upstream_test.go Tests upstream reconciliation.
gateway/​pkg/​kong/​client/​route.go Reconciles services and routes.
gateway/​pkg/​kong/​client/​route_test.go Tests route reconciliation.
gateway/​pkg/​kong/​client/​response.go Handles Kong responses.
gateway/​pkg/​kong/​client/​reconcile.go Provides generic reconciliation logic.
gateway/​pkg/​kong/​client/​reconcile_test.go Tests reconciliation and normalization.
gateway/​pkg/​kong/​client/​plugin/​suite_test.go Tests plugin encoding.
gateway/​pkg/​kong/​client/​plugin/​encode.go Encodes plugin string maps.
gateway/​pkg/​kong/​client/​plugin.go Reconciles plugins.
gateway/​pkg/​kong/​client/​plugin_test.go Tests plugin reconciliation.
gateway/​pkg/​kong/​client/​normalize.go Normalizes Kong representations.
gateway/​pkg/​kong/​client/​mock/​mock_KongClient.go Regenerates client mocks.
gateway/​pkg/​kong/​client/​mock/​mock_KongAdminApi.go Regenerates Admin API mocks.
gateway/​pkg/​kong/​client/​metrics.go Adds reconciliation metrics.
gateway/​pkg/​kong/​client/​metrics_test.go Tests reconciliation metrics.
gateway/​pkg/​kong/​client/​kongclient.go Defines client interfaces.
gateway/​pkg/​kong/​client/​helpers_test.go Adds shared test helpers.
gateway/​pkg/​kong/​client/​error.go Refines Kong error handling.
gateway/​pkg/​kong/​client/​error_test.go Tests error handling.
gateway/​pkg/​kong/​client/​consumer.go Reconciles consumers and memberships.
gateway/​pkg/​kong/​client/​consumer_test.go Tests consumer reconciliation.
gateway/​pkg/​kong/​client/​client.go Removes the monolithic implementation.
gateway/​pkg/​kong/​client/​client_test.go Removes obsolete tests.
gateway/​pkg/​kong/​api/​client.gen.go Regenerates Kong API client code.
gateway/​Makefile Updates generation and lint tooling.
gateway/​internal/​features/​mock/​mock_FeaturesBuilder.go Updates generated mocks.
gateway/​internal/​features/​mock/​mock_Feature.go Updates generated mocks.
gateway/​internal/​features/​feature/​circuit_breaker.go Delegates upstream reconciliation.
gateway/​internal/​features/​feature/​circuit_breaker_test.go Tests circuit-breaker integration.
gateway/​internal/​controller/​suite_test.go Updates mock setup.
gateway/​go.mod Adds Prometheus and test dependencies.
Files not reviewed (5)
  • gateway/internal/features/mock/mock_Feature.go: Generated file
  • gateway/internal/features/mock/mock_FeaturesBuilder.go: Generated file
  • gateway/pkg/kong/api/client.gen.go: Generated file
  • gateway/pkg/kong/client/mock/mock_KongAdminApi.go: Generated file
  • gateway/pkg/kong/client/mock/mock_KongClient.go: Generated file

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +299 to +300
desired.Consumer, desired.Route, desired.Service = nil, nil, nil
current.Consumer, current.Route, current.Service = nil, nil, nil
Comment on lines +232 to +235
if e.tags != nil && len(*e.tags) > 0 {
tags := strings.Join(*e.tags, ",")
params.Tags = &tags
}
Comment on lines +263 to +266
if params.Offset != nil && *response.JSON200.Offset == *params.Offset {
return nil, false, fmt.Errorf("target list pagination offset did not advance")
}
params.Offset = response.JSON200.Offset
Comment on lines +68 to +69
slices.Sort(entries)
return json.Marshal(entries)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Any feature that improves performance stability Improves stability in some way

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants