Guide · AWS MSK Producer Configuration

MSK Producer Configuration for MCP Servers — acks, Compression, batch.size, linger.ms, and Idempotence

The Kafka producer's default settings are tuned for low latency at the expense of throughput and durability: acks=1 (only leader acknowledges), compression.type=none, batch.size=16384 (16 KB), and linger.ms=0 (send immediately, never wait to fill a batch). For an MCP probe pipeline writing thousands of probe results per minute to MSK, those defaults leave significant efficiency gains on the table. Compressing probe result JSON with SNAPPY or LZ4 typically achieves 3–5× size reduction on structured JSON payloads, cutting MSK data-in charges and broker storage. Setting linger.ms=5 with a larger batch.size batches multiple probe results into one Kafka request, reducing per-message overhead from the SASL/TLS connection round-trip. Together these changes can halve MSK data transfer costs and double producer throughput on the same broker fleet.

TL;DR

For high-volume probe result ingestion: use acks=all, compression.type=lz4, batch.size=65536 (64 KB), linger.ms=5, and enable.idempotence=true. Enable idempotence only if you also set acks=all, retries>0, and max.in.flight.requests.per.connection=5 — these are enforced automatically when idempotence is enabled. For probe command topics (low volume, latency-sensitive), use acks=1, no compression, default batch.size, and linger.ms=0. Monitor RecordSendRate and RecordQueueTimeMax in CloudWatch to tune further.

acks: durability vs latency

The acks config controls how many broker acknowledgements the producer waits for before returning success from send(). It interacts directly with min.insync.replicas on the cluster (see MSK Replication guide for the full durability matrix). For probe pipelines:

Compression: codec comparison for probe JSON

Probe result messages are structured JSON with predictable field names (endpointId, status, responseTimeMs, probedAt, etc.) — highly compressible. The producer compresses at batch level: all messages in a batch are compressed together, which is more efficient than per-message compression.

CodecCompression ratio (JSON)CPU costDecompression latencyRecommendation
none1× (no compression)NoneNoneOnly for tiny messages or very low volume
gzip5–8×High (CPU-bound)ModerateAvoid on high-throughput producers — CPU bound
snappy3–4×Low-mediumVery fastGood general-purpose choice
lz43–5×LowFastestBest for high-throughput probe result ingestion
zstd5–9×MediumFastBest ratio/CPU trade-off for storage-bound workloads

LZ4 is the preferred codec for AliveMCP's probe result producer: it gives 3–5× compression at very low CPU overhead, keeping Lambda and ECS probe worker CPU free for the actual probe HTTP requests. ZSTD is worth evaluating if broker storage cost is the binding constraint and producer CPU is not.

The compression codec is a producer-side config; the broker stores messages in compressed form and decompresses only when needed (consumer fetch). Setting a different codec on the broker-side (compression.type=producer, the default) tells the broker to respect whatever the producer chose — do not set a specific codec on the broker unless you want to override producers.

batch.size and linger.ms: throughput tuning

The producer accumulates messages into a batch before sending to the broker. Two configs control when the batch is flushed:

Profilebatch.sizelinger.msBehaviourUse for…
Low latency16384 (default)0 (default)Send every message immediately — one Kafka request per message unless system-limitedProbe commands, alerts (where 1ms matters)
Balanced327685Accumulate up to 32 KB or wait 5ms — one request per batchModerate-volume probe results
High throughput6553610Accumulate up to 64 KB or wait 10ms — fewer, larger requests to brokerHigh-volume probe result ingestion

At 2,000 probe completions/second with 2 KB average message size, the default settings generate 2,000 Kafka requests/second to the broker. With batch.size=65536 and linger.ms=10, the same volume generates roughly 60 requests/second (each containing ~32 messages). Fewer, larger requests reduce broker CPU for request handling and SASL/TLS connection overhead.

// Node.js kafkajs producer config for high-throughput probe results
const producer = kafka.producer({
  // Idempotence requires acks=-1, retries>0, max.in.flight.per.connection<=5
  idempotent: true,             // sets acks=-1 and retries=Integer.MAX_VALUE automatically
  transactionTimeout: 30000,
  // kafkajs-specific batching equivalent
  // (kafkajs does not expose batch.size/linger.ms directly;
  //  use sendBatch() for explicit control, or a bridge via node-rdkafka)
});

// For fine-grained producer config, use node-rdkafka (librdkafka bindings):
// librdkafka exposes Kafka's full producer config namespace
const rdkafka = require('node-rdkafka');

const highThroughputProducer = new rdkafka.Producer({
  'metadata.broker.list': bootstrapBrokers,
  'security.protocol': 'SASL_SSL',
  'sasl.mechanisms': 'OAUTHBEARER',
  // credential refresh callback for MSK IAM (see MSK IAM auth guide)
  'acks': '-1',              // acks=all
  'compression.codec': 'lz4',
  'batch.size': 65536,
  'linger.ms': 10,
  'enable.idempotence': 'true',
  'max.in.flight.requests.per.connection': '5',
  'retries': '2147483647',   // INT_MAX
  'message.send.max.retries': '2147483647',
});

Idempotent producer: exactly-once send semantics

With retries enabled (retries>0), a network timeout between the producer and broker can cause a message to be written to the broker and acknowledged but the ACK to be lost — the producer retries and the broker receives a duplicate. The idempotent producer prevents this: each producer instance is assigned a producer ID (PID) and the broker tracks a sequence number per PID per partition. Duplicate sends (same PID, same sequence number) are silently deduplicated at the broker.

Enable idempotence with enable.idempotence=true (librdkafka) or idempotent: true (kafkajs). This automatically enforces:

Idempotence is per-session (producer instance). If the producer process restarts, the PID changes and the deduplication window resets — a message in-flight at crash time may be re-sent after restart and the broker will not deduplicate it. For cross-session exactly-once semantics, you need the transactional producer (transactional.id set). For probe result ingestion, idempotent-but-not-transactional is almost always the right choice: probe results are idempotent at the consumer side (upsert by probe run ID), so the rare duplicate on producer restart is harmless.

SASL_SSL security protocol configuration

MSK on provisioned clusters supports multiple authentication modes; IAM (SASL_SSL with AWS_MSK_IAM mechanism) is the recommended mode. The full producer security config for MSK IAM on librdkafka:

const mskProducerConfig = {
  'metadata.broker.list': bootstrapBrokers,    // port 9098 for SASL_SSL
  'security.protocol': 'SASL_SSL',
  'sasl.mechanisms': 'OAUTHBEARER',
  // MSK IAM uses OAUTHBEARER with a token provider that calls STS
  'oauthbearer_token_refresh_cb': async (kafkaHandle, oauthConfig) => {
    const provider = new MSKAuthProvider({ region: 'us-east-1' });
    const token = await provider.generateAuthToken();
    kafkaHandle.setOauthBearerToken(token, /* expiry */ Date.now() + 900000);
  },
  'ssl.ca.location': '/etc/ssl/certs/ca-certificates.crt',
  // TLS is mandatory for MSK; do NOT set ssl.endpoint.identification.algorithm=none
};

Port 9098 is the MSK port for SASL_SSL (IAM auth). Port 9092 is plaintext (no auth — disabled by default on provisioned clusters), 9094 is TLS with client certificate auth (mTLS). Always use port 9098 for IAM-authenticated clients.

CloudWatch producer metrics

MetricNamespaceWhat it tells you
BytesInPerSecAWS/KafkaTotal ingest throughput across all producers — watch for quota approach
RecordSendRateProducer-side (client metrics via JMX or custom)Records/second from each producer instance — baseline for capacity planning
RecordErrorRateProducer-side> 0 indicates retriable or non-retriable producer errors — investigate immediately
ProduceTotalTimeMsMeanAWS/KafkaMean produce request latency — >100ms sustained warrants investigation (likely ISR pressure)
NetworkProcessorAvgIdlePercentAWS/Kafka<30% means broker network threads saturated — scale broker count or reduce request rate

Common failure modes

SymptomCauseFix
High producer latency (>100ms) despite low throughputacks=all with ISR pressure — one broker is slow to acknowledgeCheck UnderReplicatedPartitions and ProduceTotalTimeMsMean; if ISR < RF, investigate slow broker
Producer throws BufferExhaustedExceptionbuffer.memory (default 32MB) full — broker is slow and batches are accumulatingIncrease buffer.memory or decrease max.block.ms to fail fast; check broker health
Idempotence config rejected: Cannot set enable.idempotence=true without acks=allExplicit acks=1 or acks=0 set alongside idempotenceRemove the explicit acks config (idempotence sets it to all automatically) or align both settings
Duplicate probe results in downstream despite idempotent producerProducer restart — new PID, idempotence deduplication window reset; a message in-flight at restart is re-sentMake consumers idempotent: upsert probe results by probeRunId. This is the correct defense — idempotent producer is session-scoped, not persistent.
Compression not taking effect — message sizes unchangedbatch.size is larger than any single message but linger.ms=0 — each message is sent alone and compressed individually instead of batchedSet linger.ms > 0 (e.g., 5ms) so multiple messages accumulate before compression — batch compression gives much better ratio than per-message