Skip to content

Benchmarking - DO NOT MERGE#4487

Open
prakhargarg105 wants to merge 525 commits into
redpanda-data:mainfrom
prakhargarg105:benchmarking
Open

Benchmarking - DO NOT MERGE#4487
prakhargarg105 wants to merge 525 commits into
redpanda-data:mainfrom
prakhargarg105:benchmarking

Conversation

@prakhargarg105

Copy link
Copy Markdown
Contributor

No description provided.

prakhargarg105 and others added 30 commits June 5, 2026 13:38
A customer-shaped CDC workload: 75M orders rows, sustained 5K writes/sec
for 15 minutes after a 2-minute warmup, swept across 1/2/4/8 vCPU.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One command per lifecycle stage. The default `aws:bench` invocation
provisions, runs the sweep, renders results, and tears down.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The local Docker benches stay the developer-iteration path; the new
benchmarking/aws/ framework is the path for the published numbers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch the runner/load-gen defaults + the postgres orders-cdc scenario
from c7i.* (Intel x86_64) to c8g.* (Graviton arm64) so the existing
arm64 AL2023 AMI and arm64 Go builds work. Keep c7i.* in the vCPU lookup
table for backward compatibility with existing test fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
translateInfraSource was emitting YAML for nested-map var values, which
terraform's -var parser rejects. Switch to json.Marshal so the comment
that already says "JSON-encoded" is now true.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
terraform -chdir=<stack> changes the working directory before resolving
-backend-config, so a repo-relative backend.hcl path is interpreted
inside the stack directory and fails to read. Resolve to absolute up
front.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three issues caught on the first real apply against AWS:

1. Security group descriptions used an em-dash; AWS rejects non-ASCII in
   GroupDescription. Replace with hyphens.
2. aws_s3_bucket_lifecycle_configuration rule now requires an explicit
   filter or prefix in newer provider versions. Add filter {} to fall
   under the all-objects default.
3. The destroy defer was registered after the shared apply succeeded,
   so a failed shared apply left orphan resources. Move the defer
   before any apply.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wal_level isn't a user-settable parameter on AWS RDS — setting it
directly fails ModifyDBParameterGroup with "Could not find parameter
with name: wal_level". The RDS-specific equivalent is
rds.logical_replication=1, which makes RDS set wal_level=logical for us.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16.4 is no longer available in us-east-2 (RDS retires patch versions).
16.14 is the current latest in the postgres16 parameter group family.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
combineReset was writing \$POSTGRES_DSN as a shell-variable reference,
but the SSM script environment doesn't carry that variable, so psql fell
back to the local Unix socket and failed before the first sweep point.
Substitute the DSN value directly (same pattern as renderWorkloadScript
and runSeeder), and wrap with set -euo pipefail + ON_ERROR_STOP=1 so a
real psql failure surfaces.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
postgres_cdc and other enterprise connectors refuse to start without a
license. Add a --license-file flag (defaulting to $REDPANDA_LICENSE_FILEPATH),
upload the license to the staging bucket alongside the binary + config,
and set REDPANDA_LICENSE_FILEPATH=/opt/bench/license.jwt in the bench
script env so Connect picks it up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
stat() succeeds even when macOS TCC / sandbox blocks read, so the
validation passed but stageArtefacts later failed after ~8 minutes
of terraform apply. Open the file for read at validate-time so the
permissions error surfaces before any AWS provisioning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add *.license, *.jwt, rpcn_license, and rpcn.license patterns so a
license file dropped at the repo root for benchmarking doesn't get
accidentally committed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
postgres_cdc unconditionally reads its `tls:` field and overwrites the
sslmode parsed from the DSN (see input_pg_stream.go:303). When the
scenario didn't set `tls:`, FieldTLS returned a disabled tls.Config, so
postgres_cdc connected without encryption and RDS rejected the
replication slot with 'no pg_hba.conf entry ... no encryption'.

Add tls: { enabled: true, skip_cert_verify: true } so the replication
stream is encrypted. skip_cert_verify is acceptable for this benchmark
because the connection stays inside the bench VPC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
postgres_cdc uses NewTLSField (not NewTLSToggledField), so there's no
'enabled' toggle — the presence of the tls: block implies TLS. Drop the
'enabled: true' line that caused lint failure at every sweep point.

Also: if the FIRST sweep point captures zero samples (Connect failed to
start or the connector errored for the whole window), bail out of the
sweep with a clear error rather than burning the remaining ~50min of
sweep windows watching the same failure repeat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the brief 30-line README with a complete guide covering first-time
setup, running benches, architecture, adding scenarios + connectors,
known limitations (SSM truncation, TLS field shape, RDS quirks, SSO
timeout, macOS TCC, clock skew), troubleshooting, and cost estimates.
Captures everything learned during the foundation's smoke runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add .claude/ (Claude Code harness state) and /runner (stray binary
sometimes left at the repo root by go-run when invoked from there) so
neither lands in commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The benchmark processor emits bytes/sec via humanize.Bytes which uses
SI suffixes (B, kB, MB, GB). The previous regex matched only MB/sec, so
any sweep point under ~1 MB/sec was silently dropped to zero samples.
Normalise every unit to MB/sec so percentiles across a sweep stay
unit-consistent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SSM GetCommandInvocation caps StandardOutputContent at ~24KB, which the
1s rolling-stats lines overflow within ~2 minutes. Sweep summaries were
landing with 1-2 samples and meaningless percentiles.

Connect now writes to /tmp/bench-<vcpu>.log on the runner; the script
uploads to s3://<results-bucket>/runs/<session>/sweep-<vcpu>.log after
clean termination, and MatrixRunner fetches+parses it via a new
LogFetcher interface. SSM stdout only carries framework status echos
plus a per-minute heartbeat (the latest rolling-stats line), well
under the SSM cap so the operator still sees live throughput.

On the early-abort path (0 samples on the first point), the log tail
is dumped to stdout so Connect's underlying error is visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous workload (5K writes/sec, single goroutine) capped the
sweep at exactly the producer's ceiling (6 MB/s) and Connect had spare
CPU at every point — the sweep measured the producer, not Connect.

- Scenario: write_rate_per_sec 5000 -> 80000 (~96 MB/s target) so the
  producer is no longer the binding constraint at low vCPU counts.
- Seeder workload(): single goroutine + single connection capped around
  30-40K inserts/sec on c8g.large regardless of what was asked. Spread
  across 8 workers with pgxpool MaxConns=8, each pushing a smaller batch
  per 100ms tick. RDS can now actually receive at the requested rate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First AWS run with the bucket-1 framework fixes — 899 samples per
sweep point captured cleanly through S3, percentiles real, no
SSM truncation.

Findings:
- 1 vCPU is CPU-bound at ~76 MB/s — the postgres_cdc read ceiling for
  this row shape and batch size.
- 2-4 vCPU is producer-bound at ~95-99 MB/s — Connect has headroom;
  the load-gen + RDS write path caps at the workload's 80K writes/sec.
  A larger workload (or per-point table reset) is needed to find the
  multi-vCPU ceiling.
- 8 vCPU is omitted, with rationale. By sweep point 4 the orders table
  had grown to ~305M rows during the earlier points; per-insert latency
  on the b-tree stretched until the producer effectively stalled and
  Connect read 0 msg/sec for the full window.

Cost: ~$6 across two retries (one diagnostic, one publishable).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers orphan-cleanup Lambda (TTL-based, auto-destroy via AWS API),
cost-check subcommand (Cost Explorer, last-7d + MTD + per-instance-type),
Prometheus snapshot capture (continuous 10s scrape, curated subset in
result JSON, anomaly correlation), and SUMMARY.md auto-refresh
(marker-delimited section, regen subcommand for offline use).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each plan is independent and subagent-executable:
- cost-check (smallest, no infra, CE auth path)
- SUMMARY.md auto-refresh (pure code, marker-bounded section)
- Prom snapshot capture (sidecar scrape + curated parse + anomaly context)
- Orphan-cleanup Lambda (biggest — Lambda code + IAM + EventBridge + SNS)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds SummariseCosts (renamed from plan's Summarise to avoid collision with
stats.go's existing Summarise([]Sample)) together with cost_test.go covering
aggregate, empty, and error-propagation cases. Last7Days boundary is exclusive
of the 7-days-ago anchor (matching the "14..20" intent in test comments).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
prakhargarg105 and others added 16 commits June 5, 2026 13:40
…ursty sinks)

Iceberg sink throughput is the table's committed-bytes growth; KC's Tabular
sink commits in bursts so median-of-per-interval-rates reads 0 despite
committing all data. Add Summary.MeanMBPerSec (mean of per-interval rates
= total committed / window) and a 3-decimal 'mean MB/s' markdown column so
bursty committers compare fairly with steady ones (Connect).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion-independent

Committed-bytes/sec confounded throughput with each engine's Parquet
compression. Poll Iceberg total-records too and report records/sec (mean),
the fair compression-independent sink throughput for Connect-vs-KC.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The full iceberg-sink sweep stranded at the kafka_connect points: the
bench script spawns its own taskset-pinned connect-distributed JVM on
:8083 after only 'systemctl stop' + 'sleep 2', while the systemd unit
(Restart=on-failure) and a slow-to-release JVM under load contend for
the same port. The loser crash-loops on BindException (600+ restarts
observed) until the other JVM exits, so the connector submit hits a
worker that never stably binds -> HTTP 500.

- kcscript.go: before spawning, stop the unit, pkill any lingering
  connect-distributed, and poll until :8083 is actually free (re-issuing
  stop each iteration to defeat on-failure relaunch). After SIGTERMing
  our JVM, poll :8083 free again before handing the port back to systemd.
- runner-user-data.tftpl: RestartSec=10 + TimeoutStopSec=120 so a
  transient collision backs off instead of tight-looping, and a clean
  stop waits for the JVM to flush state + release the socket.
- kcscript_test.go: regression test asserts both port-free waits + the
  straggler kill, in the correct order relative to spawn/hand-back.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The json-orders seeder sets ProducerBatchMaxBytes(16 MiB) but created the
topic with the broker-default max.message.bytes (~1 MiB). Redpanda
validates each produce batch against the topic limit, so batches that
fill toward 16 MiB are rejected with MESSAGE_TOO_LARGE. This is a rare
spike at low volume (the 12M smoke passed) but a hard failure seeding
160M rows (200k/160M failed). Create the topic with max.message.bytes=
64 MiB so the 16 MiB batches always clear, with headroom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Connect's iceberg output is commit-latency-bound: serialized ~320ms Glue
snapshot commits with no commit-linger, so under default config it commits
~300 records at a time at ~0.03 CPU cores and is flat across vCPU (~412
rec/s). The redpanda input alone sustains ~57k rec/s into a drop output, so
the input is not the limiter — the small per-commit record count is.

Thread a top-level `buffer` from the scenario pipeline block to the Connect
config root (mirrors the existing cache_resources passthrough) so a fast
input can be decoupled from the commit-bound output. With a memory buffer +
larger batches, each commit carries ~50k records instead of ~300.

Validated (connect-only 1-vCPU smoke): mean 412 -> 10,984 rec/s (~27x),
peak ~35k rec/s, and CPU 0.03 -> 0.99 cores (now CPU-bound, so it scales
with vCPU instead of being flat). orders-sink-smoke.yaml carries the tuned
config (buffer 512MiB, max_in_flight 8, count 50000, period 10s).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Smoke ran Connect-only at 1 vCPU for 15min: median 5.7 MB/s, 3865
msg/sec, 0 anomalies. Producer-bound — load-gen sustained ~4K
PutItems/sec out of 5K target before hitting BatchWriteItem
InternalServerError near end. Connector ceiling is well above this;
needs higher PutItem rate to probe.

Also fixes scenario YAML: `billing: PROVISIONED` was being forwarded to
`terraform apply` by translateInfraSource and rejected as an undeclared
variable. The TF module hardcodes PROVISIONED anyway, so the key is
removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Auto-generated results appended across the iceberg-sink bench work:
1-vCPU smokes, the full [1,2,4,8] both-engine sweep, and the buffered
Connect runs. See results/iceberg/orders-sink*/ for the raw JSON.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match Connect's commit cadence to KC's Tabular coordinator (10s) using
input-side batching, so the comparison is tuned-vs-tuned rather than
default-vs-tuned. Add a pipeline.input_options passthrough in
sinkTopology.Pipeline that merges scenario-supplied redpanda-input tuning
(e.g. unordered_processing) into the input, protecting the bench-managed
connection fields (brokers/topic/group) from being clobbered.

orders-sink.yaml now configures input_options.unordered_processing
(enabled, checkpoint_limit 100000, batching count 50000 / period 10s) +
output max_in_flight 8 / batching 50000 / 10s. Input batching adds the
read-ahead the default path lacks (measured ~110x over baseline,
output_sent ~46k rec/s at 1 vCPU), so each commit carries ~10s of records
like KC's coordinator. Trades cross-partition ordering (fine for a sink;
the buffer variant is the order-preserving equivalent).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The repetitive 'x' padding compressed ~150x in Parquet, making committed-
bytes/sec meaningless and giving KC an absurd ~7 B/record. Sample each
record's payload from a 16 MiB pool of random alphanumeric bytes (JSON-safe)
via a random window, and vary region/status across small sets. Payloads are
now distinct and barely compressible, so Parquet file sizes are
representative and the MB/s axis is symmetric across engines for a fair
head-to-head. Pool + window keeps seeding fast (no per-byte RNG on the hot
path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tic payload)

Tuned-vs-tuned head-to-head: Connect input batching at KC's 10s commit
cadence, high-entropy realistic payload. Both records/s and MB/s now agree.
Connect scales (15k->178k rec/s across 1->8 vCPU), KC plateaus (56k->88k);
KC leads at 1-2 vCPU, Connect crosses over by 4 vCPU and ends ~2x ahead at 8.
results/iceberg/orders-sink/2026-06-04T19-01-06Z.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…quota

The reset bash recreated the source table with hardcoded
WriteCapacityUnits=10000 — fine for the 1-vCPU smoke (reset doesn't
fire) but would tank the table mid-sweep. Wire read_capacity /
write_capacity through the dynamodb stack outputs and the
aws_dynamodb_cdc engineSpec's ExtraEnvVars, so the bash references
${READ_CAPACITY}/${WRITE_CAPACITY} and tracks scenario sizing
automatically.

Also resize the workload after the 120K WCU smoke hit AWS's default
per-table quota (47-min CreateTable hang) and the 10K PutItems/sec
smoke saturated 40K WCU late in the run. New sizing: 40K WCU
provisioned, 9K PutItems/sec sustained — 10% headroom under quota.

Smoke redpanda-data#3 result (10 MB/s p50 @ 1 vCPU) appended to dynamodb.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The drop+recreate-table-between-points pattern (mirroring postgres/mysql)
doesn't fit DDB. The aws_dynamodb_cdc connector reads from Streams, not
table items, so leftover rows can't leak between sweep points. All we
need to reset is the connector's CHECKPOINT table — dropping it forces
the next process to fall back to start_from:latest and skip to the
current stream tail.

The table-recreate path also kept hitting AWS's per-table teardown
latency (20-30min for a 40K WCU table), causing TF's default 10m
delete timeout to fire mid-destroy. Override to 30m so the final
teardown completes cleanly; this is the price of keeping the source
table around across sweep points (~150GB by end of full sweep,
negligible storage cost).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The seeder's writeBatch returned the first BatchWriteItem error
verbatim — including transient AWS errors like InternalServerError
and ProvisionedThroughputExceededException — which killed the worker
goroutine and cascaded to the whole workload exiting. This masqueraded
as a Connect-side throughput drop in earlier runs: the bench saw rates
collapse from 36 MB/s to 0 (workload fully died) or to ~11 MB/s
(workload partially died, some workers continued) after ~9 minutes,
and the connector was incorrectly suspected.

Two changes:
- writeBatch now retries up to 8 times with exponential backoff
  (50ms→2s, ~6s total) on any non-context-cancellation error, not just
  UnprocessedItems.
- After max retries, the batch is dropped rather than returned as
  an error. Sustaining the configured rate is more important than
  exact-once delivery for a bench — better to underdeliver one batch
  than terminate the load generator.

Verified at 1 vCPU: throughput now sustained 36 MB/s for 10 minutes
(was: 36 MB/s for 9 min, then collapse to 0 or ~11), 0 anomalies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the four-point sweep result (36→15→12→0 MB/s) with the
analysis showing it's an artefact of the bench reset strategy not
fitting DDB Streams' shard lifecycle, not a Connect anti-scaling
behaviour.

Lists falsified hypotheses (AWS GetRecords throttle, GOMAXPROCS lock
contention, downstream ShouldThrottle backpressure) and the
load-gen-side bugs found during the investigation (4K cap, transient
error worker death) which were fixed at d19c10b / 817a6a3 /
e2fb34e.

Identifies the open root cause hypothesis (shard rotation outpacing
the connector's 30s shard refresh interval) and three paths forward
for someone returning to this work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…iant

Switch orders-sink.yaml from input batching (unordered_processing) to a
top-level memory buffer (512 MiB) + output batching at KC's 10s cadence.
The buffer decouples the fast redpanda input from the commit-latency-bound
iceberg output the same way, but PRESERVES cross-partition ordering (no
unordered_processing). Measured equivalent throughput in isolation. Input
batching variant preserved in git history (a1d88d9).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ults

Add iceberg-findings.md — the full RPCN-vs-KC writeup: fair tuned-vs-tuned
comparison (Connect scales, crosses over KC at 4 vCPU; KC efficient at 1-2),
the commit-latency-bound default trap (~412 rec/s) and the buffer/input-batching
fixes, the buffer-vs-input-batching ordering/throughput tradeoff, negative
results (max_in_flight inert, coalescing doesn't fix low-core), and the
low-core per-record-CPU gap + recommendations. Plus the buffer-variant full
sweep results appended to iceberg.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
prakhargarg105 and others added 13 commits June 11, 2026 13:40
Single-table dynamodb_cdc is capped at ~36 MB/s by DynamoDB's per-table
40K WCU quota, which Connect already saturates at 1 vCPU — so the CPU
sweep showed a flat line, not a scaling curve. Create N source tables
via for_each so the producer can exceed the per-table quota: total
provisioned WCU = len(table_names) x write_capacity, while each table
stays within its own 40K quota.

- dynamodb-bench module: table_name (string) -> table_names (list),
  one table per name via for_each; outputs kept as joined strings.
- runner translateInfraSource: JSON-encode []any so YAML sequences
  forward as valid HCL list -vars (covers infra.source.table_names);
  unit test added.
- scenario cdc.yaml: 2 tables (76K total WCU, ~72 MB/s offered), total
  rate 18K/sec, account-level WCU quota pre-flight documented; stale
  drop+recreate comments removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2-table sweep on c8g.4xlarge (1/2/4/8 vCPU). Broker-derived p50:
36 -> 66 -> 73 -> 73 MB/s (rolling-stats p50: 40 -> 72 -> 81 -> 82).
Near-linear 1->2 vCPU (~1.8x), plateau by 4 vCPU at the source ceiling.

Confirms the CON-485 DescribeStream shard-pagination fix end-to-end:
the 8-vCPU point holds a steady 82 MB/s (p5 78, p95 84) where the
earlier single-table run collapsed to 0 MB/s from shard-rotation stall.
Connect is source-bound at 4+ vCPU; its own ceiling is higher and would
need 3+ tables (account WCU quota increase) to find. Raw JSON in S3
(results/*.json is gitignored).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Connect oracledb_cdc vs Debezium Oracle on RDS Oracle 19c, both mining
the same redo via LogMiner. Adds the rds-oracle TF module, oracle stack,
cdc-rows-oracle seeder (seed/workload/exec), and the orders-cdc scenario.

Smoke + full [1,2,4,8] sweep run clean end-to-end (both engines stream
Oracle CDC). Two non-obvious fixes baked in:

- Reset self-stages the seeder from S3 (stage/<seeder>) before TRUNCATE:
  reset runs on the runner host, but the seeder is only staged on the
  load-gen host during seed. Oracle has no psql/mysql CLI so it shells
  out to the seeder's exec subcommand; postgres/mysql dodge this via
  network CLIs. Affects any no-CLI-engine reset.
- stream_snapshot: true (not false). With an empty checkpoint cache,
  false falls through to FindStartPos -> oldest available redo SCN, so
  at low vCPU Connect grinds historical redo and never reaches the live
  workload (0 MB/s, no error). true snapshots the empty table instantly
  and streams from CURRENT_SCN, matching Debezium.

Known limitation: the bench is currently source-bound (~13 MB/s flat
across 1-8 vCPU; workload lands ~10K rows/sec vs 150K target), so it
does not yet expose Connect CPU scaling. IDENTITY sequence CACHE 100000
helped only at the margin; root cause (write commit/redo path vs Connect
LogMiner read cadence) still under investigation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document the finding from the oracledb_cdc vs Debezium AWS sweep: Connect
streams Oracle CDC at ~13 MB/s on a single LogMiner session, flat across
1-8 vCPU and flat across scn_window_size (20K-200K). Read-bound on LogMiner
fetch — write path proven to do 41 MB/s, CPU and SCN-window have no effect.
Matches Debezium and Joseph Woodward's independent local bench (redpanda-data#4082):
a LogMiner protocol limit, not a Connect limit.

Records why no CPU-scaling curve is produced and why multi-table splitting
(which scaled DynamoDB via real stream shards) is neither effective nor
representative for Oracle's single shared redo log.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-ran the full both-engines sweep on benchmarking @ 2c2341b (after
merging upstream/main with the oracledb_cdc perf work). Connect rose
13 -> 19 MB/s (+46%) from connector efficiency (redpanda-data#4533 scanner, redpanda-data#4531
session handling) — still flat across 1-8 vCPU, still single-session
LogMiner-bound. At sustained throughput Connect (19) edges Debezium (~17);
Debezium's high 1-2 vCPU figures are non-reproducible warm-up bursts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	benchmarking/aws/runner/kcconnectors.go
#	benchmarking/aws/runner/main.go
#	benchmarking/aws/runner/scenario.go
#	benchmarking/aws/scenarios/dynamodb/cdc.yaml
#	benchmarking/aws/terraform/modules/dynamodb-bench/main.tf
#	benchmarking/aws/terraform/modules/dynamodb-bench/outputs.tf
#	benchmarking/aws/terraform/modules/dynamodb-bench/variables.tf
#	benchmarking/aws/terraform/shared/runner-user-data.tftpl
#	benchmarking/aws/terraform/stacks/dynamodb/main.tf
#	benchmarking/aws/terraform/stacks/dynamodb/variables.tf
#	docs/benchmark-results/SUMMARY.md
#	docs/benchmark-results/dynamodb.md
- Rename seeders/cdc-rows -> cdc-rows-postgres so the four CDC seeders read
  consistently (cdc-rows-{postgres,mysql,oracle}, cdc-ddb). Updates the
  postgres scenario, runner tests + testdata, and doc references.
- Add runner/doc.go: a package reading guide mapping the ~45 files into
  logical groups (entry, specs, topology, render, metrics, infra, output).
- Refresh README: accurate Status table + file-reference tree for all five
  connectors (postgres/mysql/oracle/dynamodb/iceberg), drop done "future work".

No behaviour change. Builds, vets, runner tests pass, all 5 scenarios validate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	.gitignore
#	docs/benchmark-results/iceberg.md
Apply the upstream Iceberg tuning recipes (e49fdbf) to the sink bench
scenarios and record full-sweep results on the merged upstream code
(benthos v4.76.0, incl. shredder schema-field alloc caching).

Scenarios:
- orders-sink (Recipe A, order-preserving): memory buffer + batch_policy +
  output batching, max_in_flight=16. Restoring output-level batching is what
  un-starves the committer's coalescer (0.8 -> 12.7 MB/s at 1 vCPU smoke).
- orders-sink-recipe-b (new, Recipe B, unordered): unordered_processing input
  batching, max_in_flight=32, no buffer.
- orders-sink-smoke: mirror the order-preserving primary sweep.

Head-to-head (mean MB/s, 178 GB, c8g.4xlarge, connect vs kafka_connect):
  1v: A 15.9 / B 38.3 / KC 45    2v: A 69 / B 64 / KC 63
  4v: A 114 / B 99  / KC 71      8v: A 109 / B 122 / KC 74
B wins at 1 and 8 vCPU (2.4x at 1v); A edges 2-4v. Both beat KC at 2+ vCPU.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rite-up

Curated summary of the vCPU-scaling sweep: environment, the two Connect
tuning-recipe configs (order-preserving A, unordered B), mean-MB/s results
table, scaling analysis, and recommendation. Companion to the auto-appended
iceberg.md tables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant