redpanda/migrator: avoid O(N²) schema registry fan-out in translate_ids sync - #4731
Open
alextreichler wants to merge 3 commits into
Open
redpanda/migrator: avoid O(N²) schema registry fan-out in translate_ids sync#4731alextreichler wants to merge 3 commits into
alextreichler wants to merge 3 commits into
Conversation
…ids sync With translate_ids enabled, each schema was registered at the destination via franz-go's CreateSchema, which resolves the returned ID through SchemaUsagesByID: a fetch of every subject-version sharing that ID, spawned as one unbounded goroutine per usage. Identical schema bodies deduplicate to a single destination ID, so syncing N such subjects cost ~N(N+1)/2 destination requests, none of them bounded by max_parallel_http_requests. Against registries with heavily shared schema bodies this produced request bursts far above the configured concurrency limit (measured: 826 usage reads and peak concurrency 42-79 for 40 subjects with a limit of 2), overloading single-node registries into connection resets. Register with RegisterSchema (one POST, idempotent: returns the existing ID for an already-registered identical schema) plus one LookupSchema to resolve the destination version, making the sync O(N) with concurrency bounded by the worker pool. Measured after: 40 registrations, 0 fan-out reads, peak concurrency exactly at the configured limit. Adds an integration regression test that measures destination traffic through a counting reverse proxy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alextreichler
marked this pull request as ready for review
August 27, 2026 16:45
Drop the LookupSchema call after RegisterSchema: the destination version it resolved only fed a log field (schemaInfo's sole functional consumer is the ID), and a transient lookup failure would have aborted an otherwise-successful registration. The translated-ID sync now costs exactly one request per schema. Add the new fan-out integration test to the TESTING.md catalog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…den test Extend the RegisterSchema swap to the translate_ids: false branch (the default configuration), where CreateSchemaWithIDAndVersion performed the same unbounded SchemaUsagesByID fan-out; the ID and version are inputs there, so no lookup is needed at all. The redpanda#26331 fallback is preserved unchanged. Harden the fan-out regression test per review: cover both ID-translation modes (table-driven, IMPORT destination for fixed IDs), seed the source via RegisterSchema so setup does not itself fan out, guard against a vacuous pass by requiring one registration per subject, tighten the fan-out bounds to exactly zero usage-endpoint requests, use the package's standard sync timeout, and fix the new file's copyright year. Remove the now-unused schemaInfoFromSubjectSchema. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Problem
With
schema_registry.translate_ids: true, theredpanda_migratoroutput registers each schema at the destination via franz-go'sCreateSchema, which resolves the returned ID throughSchemaUsagesByID— a fetch of every subject-version sharing that ID, spawned as one unbounded goroutine per usage.Identical schema bodies deduplicate to a single destination ID, so syncing N such subjects costs ~N(N+1)/2 destination requests instead of N, and none of those requests are bounded by
max_parallel_http_requests. Against a registry with heavily shared schema bodies (e.g. per-environment copies of the same schemas) this produces sustained request bursts that overload single-node registries intoconnection reset by peer, which fails the sync, which fails the output connect, which restarts the pipeline and replays the whole quadratic sweep — observed in a production migration as 169 consecutive connect failures with zero topics migrated.A side effect of the fan-out is actively misleading errors: the failing GET names an unrelated subject that merely shares the schema ID (
sync subject schema <A> ...: unable to GET ".../subjects/<B>/versions/1"), making healthy subjects look broken.Fix
In the
translate_idsbranch ofsyncSubjectSchema, replaceCreateSchemawith:RegisterSchema— one POST; idempotent (returns the existing ID for an already-registered identical schema), preserving the previous create-or-reuse semantics;LookupSchema— to resolve the destination version forschemaInfoand logging.Two sequential requests per schema inside the bounded worker pool: O(N) total, concurrency capped by
max_parallel_http_requests. franz-go's ownRegisterSchemadocs recommend exactly this trade. Thetranslate_ids: falsepath is unchanged.Measurements
From the included regression test (40 subjects sharing one schema body,
max_parallel_http_requests: 2, counting reverse proxy in front of the destination registry):GET /subjects/S/versions/V)Validation
TestIntegrationSchemaRegistryMigratorSyncSharedSchemaFanoutfails on the previous code and passes with the fix.TestIntegrationSchemaRegistryMigrator*integration tests pass exceptSyncWithReferences, which fails identically on unmodified main (pre-existing, unrelated).go vet,gofumptclean.Follow-ups (not in this PR)
The same fan-out pattern exists in:
translate_ids: falsebranch (CreateSchemaWithIDAndVersionfans out internally) — fixable withRegisterSchema(ctx, subject, sch, ss.ID, ss.Version), no lookup needed;internal/impl/confluent/srwrapper (CreateSchema/CreateSchemaWithIDAndVersionboth discard everything but the ID), which the standaloneschema_registryoutput andschema_registry_encodeprocessor inherit;🤖 Generated with Claude Code