Skip to content

[samza][producer] Isolate STREAM writes with shared workers - #3000

Open
kvargha wants to merge 3 commits into
linkedin:mainfrom
kvargha:kvargha/maintainable-producer-dispatch
Open

[samza][producer] Isolate STREAM writes with shared workers#3000
kvargha wants to merge 3 commits into
linkedin:mainfrom
kvargha:kvargha/maintainable-producer-dispatch

Conversation

@kvargha

@kvargha kvargha commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Problem Statement

VeniceSystemProducer STREAM writes currently invoke the core VeniceWriter on the caller thread. Lazy START_OF_SEGMENT production can wait for a PubSub acknowledgement during Kafka leader movement. For Flink callers, this blocks the single-threaded mailbox from processing its checkpoint barrier; one delayed subtask then prevents the global checkpoint from completing.

Online Producer already uses partition-based workers, but its executor implementation was producer-specific and could not be reused safely by VeniceSystemProducer.

Solution

Add a minimal PartitionStripedExecutor in venice-common and use it from both producer APIs. The shared class owns only bounded blocking admission, deterministic partition-to-stripe routing, per-stripe FIFO workers, queue measurements, and primitive shutdown. Futures, callbacks, metrics, flush policy, writer ownership, and failure handling remain producer-specific.

Online Producer retains its existing API, configuration, defaults, callback executor, thread and metric names, inline mode, rejection fallback, and close behavior. Its adapter now composes the shared executor instead of maintaining duplicate worker-array and striping logic.

For VeniceSystemProducer STREAM writes:

  • Schema validation, schema lookup, logical-timestamp checks, write-compute conversion, and serialization remain on the caller.
  • Immutable commands route by the actual Venice writer partition and enter one of four bounded FIFO stripes by default.
  • The protected send(Object, Object) path used by Flink returns after queue admission without waiting for VeniceWriter.put, delete, or update to return.
  • Public put, delete, and Samza envelope send still wait through writer submission, preserving their existing compatibility contract.
  • A partition blocked by leader movement stalls only partitions sharing its stripe during normal dispatch; other stripes continue. flush() remains a global pre-fence durability boundary.
  • Full queues block the caller rather than dropping writes or running them inline.
  • Synchronous and asynchronous writer failures become sticky and surface through the durable future and flush().
  • Worker count 0, BATCH, and STREAM_REPROCESSING retain the inline path.

Defaults match Online Producer:

  • venice.system.producer.worker.count=4
  • venice.system.producer.worker.queue.capacity=100000 per worker stripe

BatchingVeniceWriter only gains partition-routing delegation. Its buffering, checker, flush, close, and ownership behavior are unchanged. Future Online Producer VeniceWriterHook integrations remain writer-construction concerns and require no shared-executor changes.

The VSP-owned completion handoff is used only when a writer callback or failure completes synchronously on a stripe worker. It prevents user continuations from self-waiting on that worker. Normal Kafka callbacks continue completing directly. The handoff uses a fixed number of lazily started daemon threads; its ordinary queue is intentionally not awaited during stop because completions may execute arbitrary user continuations.

Code changes

  • Added new code behind a config. Config names and defaults are listed above.
  • Introduced new log lines.
    • Confirmed if logs need to be rate limited to avoid excessive logging. Queue-full warnings retain the existing Online Producer behavior: one warning when each admission encounters a full stripe; no per-record success logging was added.

Concurrency-Specific Checks

Both reviewer and PR author to verify

  • Code has no race conditions or thread safety issues.
  • Proper synchronization mechanisms (e.g., synchronized, RWLock) are used where needed.
  • No blocking calls inside critical sections that could lead to deadlocks or performance degradation.
    • Queue admission intentionally blocks under bounded-capacity backpressure. Flush marker placement intentionally fences admission long enough to order all pre-fence writes; marker waiting and writer flush occur outside that lock.
  • Verified thread-safe collections are used (e.g., ConcurrentHashMap, CopyOnWriteArrayList).
  • Validated proper exception handling in multi-threaded code to avoid silent thread termination.

How was this PR tested?

  • New unit tests added.
  • New integration tests added.
  • Modified or extended existing tests.
  • Verified backward compatibility (if applicable).
  • Local code review completed.

Coverage includes:

  • A real VeniceWriter blocked on START_OF_SEGMENT, proving protected send returns while public compatibility wrappers still wait for submission.
  • Same-partition FIFO and progress on a different stripe while one partition is blocked.
  • Bounded queue admission, no caller-runs fallback, interruption, graceful and forced shutdown, and queued-task ownership.
  • Submission-versus-durability ordering, synchronous and asynchronous sticky failures, fatal Error identity, and uninterruptible ownership after admission.
  • Flush fencing, callback-driven retries, lossless stop, interrupt restoration, inline modes, protected overrides, and foreign futures.
  • Real VeniceWriter partition routing and BatchingVeniceWriter delegation.
  • Existing Online Producer ordering, configuration, callback, metric, rejection, and close behavior.

Validation results:

  • Full affected matrix: 474 tests passed across JDK 8, 11, and 17 with test retries disabled.
  • Final ownership regression: 110/110 across ten independent JDK 8 processes, plus 64/64 targeted tests on JDK 11/17.
  • Six affected SpotBugs main/test tasks passed.
  • spotlessCheck and git diff --check passed.
  • Independent Claude, Codex, lifecycle, and maintainability reviews found no remaining blocking issues.

Does this PR introduce any user-facing or breaking changes?

  • No. You can skip the rest of this section.
  • Yes. Clearly explain the behavior change and its impact.

STREAM-mode protected VeniceSystemProducer.send(Object, Object) now uses four partition-striped workers by default and returns after bounded admission. Writer-level failures after admission are reported through the returned future and sticky flush() failure. Public put, delete, and Samza envelope send retain their writer-submission wait. Set venice.system.producer.worker.count=0 to restore fully inline execution.

🤖 Generated with GitHub Copilot CLI

kvargha and others added 3 commits August 28, 2026 14:45
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 28, 2026 22:26

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.

Pull request overview

Introduces a shared, bounded partition-striped executor in venice-common and applies it to isolate STREAM-mode write submission off the caller thread (notably for VeniceSystemProducer), while preserving FIFO ordering per partition/stripe and keeping producer-specific semantics (futures, sticky failure handling, flush fences, shutdown behavior) in the producer layers.

Changes:

  • Added PartitionStripedExecutor (bounded, blocking admission; deterministic partition→stripe routing; per-stripe FIFO workers) and a dedicated unit test suite for its core guarantees.
  • Added partition-routing helpers (getPartitionId) to AbstractVeniceWriter and implementations/delegation in VeniceWriter and BatchingVeniceWriter to enable stable upstream striping.
  • Added VeniceSystemProducer async STREAM write dispatcher/command plumbing plus extensive deterministic tests; refactored PartitionedProducerExecutor to compose the shared striped executor.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
internal/venice-common/src/main/java/com/linkedin/venice/utils/concurrent/PartitionStripedExecutor.java New shared bounded striped executor kernel used by producers.
internal/venice-common/src/test/java/com/linkedin/venice/utils/concurrent/PartitionStripedExecutorTest.java Deterministic tests for striping, FIFO, backpressure, interrupt/shutdown semantics, shared await deadline.
internal/venice-common/src/main/java/com/linkedin/venice/writer/AbstractVeniceWriter.java Adds default getPartitionId API (legacy routes to 0).
internal/venice-common/src/main/java/com/linkedin/venice/writer/VeniceWriter.java Implements getPartitionId via key serialization + configured partitioner.
internal/venice-common/src/main/java/com/linkedin/venice/writer/BatchingVeniceWriter.java Delegates getPartitionId to internal writer using serialized key bytes.
internal/venice-common/src/test/java/com/linkedin/venice/writer/VeniceWriterUnitTest.java Adds routing determinism + delegation coverage for new getPartitionId.
internal/venice-common/src/main/java/com/linkedin/venice/ConfigKeys.java Adds VSP STREAM worker/queue configs (count + per-stripe capacity).
integrations/venice-samza/src/main/java/com/linkedin/venice/samza/VeniceSystemProducerWriteCommand.java New immutable write command + submission/durable future state machine + awaitSubmission helper.
integrations/venice-samza/src/main/java/com/linkedin/venice/samza/VeniceSystemProducerWriteDispatcher.java New STREAM async dispatcher using partition-striped workers, flush fencing, sticky failure, drain semantics.
integrations/venice-samza/src/main/java/com/linkedin/venice/samza/VeniceSystemProducer.java Wires async dispatch into STREAM send/put/delete/envelope send; validates configs; adjusts stop/flush behavior.
integrations/venice-samza/src/test/java/com/linkedin/venice/samza/VeniceSystemProducerWriteCommandTest.java Tests command lifecycle (submission vs durable), interrupt behavior, error identity propagation.
integrations/venice-samza/src/test/java/com/linkedin/venice/samza/VeniceSystemProducerWriteDispatcherTest.java Deterministic tests for dispatch contract, FIFO/isolation, sticky failures, flush fence, stop drain, deadlock regressions.
integrations/venice-samza/src/test/java/com/linkedin/venice/samza/VeniceSystemProducerTest.java Updates/extends producer tests for async submission semantics, config validation, kill switch, batching path.
clients/venice-producer/src/main/java/com/linkedin/venice/producer/PartitionedProducerExecutor.java Refactors Online Producer executor to compose PartitionStripedExecutor; adjusts awaitTermination interrupt-drain semantics.
clients/venice-producer/src/test/java/com/linkedin/venice/producer/PartitionedProducerExecutorTest.java Adds regression test for interrupt-resistant draining behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 279 to +283
if (callbackExecutor != null) {
long remainingNanos = deadlineNanos - System.nanoTime();
if (remainingNanos <= 0) {
return false;
try {
callbackTerminated = callbackExecutor.awaitTermination(Math.max(0, remainingNanos), TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
Comment on lines +793 to +796
if (workerCount == 0) {
// Kill switch: leave validatedWorkerCount at 0 so no dispatcher is created (every write runs inline).
return;
}
Comment on lines +85 to +89
/**
* Routes {@code command} to the stripe owning its Venice partition and returns its durable future after
* bounded admission. Never waits for the writer. A rejected admission (dispatcher stopped or kernel shutdown)
* fails the command's submission and records a sticky failure.
*/

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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Comment on lines +100 to +106
int partition = writer.getPartitionId(command.getKey());
try {
kernel.submit(partition, () -> execute(command));
} catch (RuntimeException e) {
recordSticky(e);
runDurableCompletion(command.finishSubmission(e));
}
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.

2 participants