Skip to content

Comments

KAFKA-19434: Startup state stores initialization#20749

Merged
bbejeck merged 27 commits intoapache:trunkfrom
eduwercamacaro:refactor-startup-store-initialization
Feb 20, 2026
Merged

KAFKA-19434: Startup state stores initialization#20749
bbejeck merged 27 commits intoapache:trunkfrom
eduwercamacaro:refactor-startup-store-initialization

Conversation

@eduwercamacaro
Copy link
Contributor

@eduwercamacaro eduwercamacaro commented Oct 22, 2025

Instead of creating Standby tasks from the state directory, we open the
local stores that exists in the state directory.

This resolves the issue raised in #KAFKA-19434
, where
store-specific metrics were being duplicated due to tasks being created
in the main thread and then assigned to a StreamThread.

Additionally, since we can now read the offsets from the store during
instance initialization, this clears the way for the implementation of
KIP-1035. As of now, the stores are loading the offsets from the
checkpoint file, but in a later PR, we will read these offsets from the
state store itself.

This PR modifies the behavior of Kafka Streams when initializing. Now
for each pre-existing store on the state directory: We open the store,
read offsets from the checkpoint file and then close it again. The
reason why we open the store is because the store will be responsible
for tracking the offsets and we will deprecate the checkpoint file.

Reviewers: Nikiita Shuplestovnshupletsov@confluent.io, Bill
Bejeckbbejeck@apache.org

@github-actions github-actions bot added triage PRs from the community streams labels Oct 22, 2025
Copy link
Contributor

@Nikita-Shupletsov Nikita-Shupletsov left a comment

Choose a reason for hiding this comment

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

Thanks a lot for putting it together!
A much better idea than what I had in my PR

@github-actions github-actions bot removed the triage PRs from the community label Oct 23, 2025
@eduwercamacaro eduwercamacaro changed the title REFACTOR: Startup state stores initialization KAFKA-19434: Startup state stores initialization Nov 21, 2025
@eduwercamacaro eduwercamacaro marked this pull request as ready for review November 21, 2025 16:20
Copy link
Member

@bbejeck bbejeck left a comment

Choose a reason for hiding this comment

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

Thanks @eduwercamacaro! I made an initial pass

this.checkpointFile = new OffsetCheckpoint(stateDirectory.checkpointFileFor(taskId));

log.debug("Created state store manager for task {}", taskId);
this.startupState = new AtomicBoolean(startupState);
Copy link
Member

Choose a reason for hiding this comment

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

same would apply to the AtomicBoolean variable

Copy link
Member

@bbejeck bbejeck left a comment

Choose a reason for hiding this comment

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

@eduwercamacaro I've made another pass and confirmed the fix by running a streams application and stopping/starting several times with existing state. Overall this lgtm
Can you rebase this PR?

Also I think we could use a new test in KafkaStreamsTelemetryIntegrationTest that start/stops/restarts a single instance and confirms the state metrics getting emitted.

@eduwercamacaro
Copy link
Contributor Author

@bbejeck Thanks for your time reviewing this. I already rebased this branch.

I believe this integration test is already covering that case. what do you think?

Copy link
Member

@bbejeck bbejeck left a comment

Choose a reason for hiding this comment

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

Thanks again for the PR @eduwercamacaro!
I have one additional question I missed from the last review.

Also can you remove the ProcessorStateManager.assignToStreamThread method? it's not used anymore.

The ProcessorStateManagerTest could use better coverage of registerStartupStateStores and initializeStoreOffsetsFromCheckpoint

@bbejeck
Copy link
Member

bbejeck commented Jan 9, 2026

@eduwercamacaro - you can ignore my comments on testing - we can do as a follow-up PR

@bbejeck
Copy link
Member

bbejeck commented Jan 13, 2026

@eduwercamacaro the failures are related - I think addressing this comment should fix the integration tests.

@eduwercamacaro eduwercamacaro force-pushed the refactor-startup-store-initialization branch from 3cacb12 to 4451626 Compare February 2, 2026 15:47
@eduwercamacaro eduwercamacaro force-pushed the refactor-startup-store-initialization branch from 4451626 to 6078803 Compare February 2, 2026 16:11
@eduwercamacaro
Copy link
Contributor Author

@bbejeck I modified this PR according to our last discussion about the solution for KAFKA-19434

We basically open the stores found in the state directory during initialization and then close them.

cc: @mjsax

Copy link
Member

@bbejeck bbejeck left a comment

Choose a reason for hiding this comment

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

Thanks for the PR, @eduwercamacaro! Overall, it looks good with few comments.

Also, ProcessorStateManager.assignToStreamThread on line 237 is unused; can we get rid of it?

} finally {
// Make sure the state manager writes the local checkpoint file before closing the stores
// This will be replaced in the future when removing the checkpoint file dependency.
temporaryStateManager.checkpoint();
Copy link
Member

Choose a reason for hiding this comment

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

Same as above temporaryStateManager.checkpoint() can throw a ProcessorStateException but we don't handle it here, meaning close() is not called. So do we consider this a fatal exception and allow it to bubble up and kill the main thread? If not, I'm guessing we'll need to catch this exception and do something. Either way let's add a couple of comments on the handling

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved this logic inside the try/catch block to ensure that we always call the close() method. Any exception from the close() method is considered fatal and will break the main thread.

task.closeDirty();
StateManagerUtil.registerStateStores(log, threadLogPrefix, subTopology, temporaryStateManager, this, initContext);
} catch (final TaskCorruptedException tce) {
log.warn("Failed to register startup state stores for task {}: {}", id, tce.getMessage());
Copy link
Member

Choose a reason for hiding this comment

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

registerStateStores also can throw a StreamsException, LockException , or a ProcessorStateException should we handle these exceptions as well? If not add some comments on the reasoning

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point.
I added some comments and JavaDoc for this method.
Those exceptions are considered fatal and will break the StreamThread. We only handle TaskCorruptedException by logging and continuing with the initialization as this exception will be handled by the StreamThread later in the first assignment.

// Make sure the state manager writes the local checkpoint file before closing the stores
// This will be replaced in the future when removing the checkpoint file dependency.
temporaryStateManager.checkpoint();
temporaryStateManager.close();
Copy link
Member

Choose a reason for hiding this comment

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

I think we also need to add StateDirectory.unlock here too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, it is not needed because we keep the lock on the main thread until it is assigned to a SthreadThread. TaskManager changes the ownership of the Task using 'StateDirectory.removeStartupState'.

temporaryStateManager.checkpoint();
temporaryStateManager.close();
}
tasksForLocalState.put(id, new StartupState(id, subTopology, temporaryStateManager));
Copy link
Member

Choose a reason for hiding this comment

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

temporaryStateManager is closed above line 264 but it's passed here to StartupState.
It's never accessed from StartupState . With the new work is StartupState required or can we get away with Set<TaskId> ? Or is it going to be needed for subsequent work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. Excellent observation.
You are right, we can use Set<TaskId>`` instead. I completely removed the StartupState` class and the process to close the stores because we are immediately closing them.

- track startup task ids via a simple Set and unlock them on close
- remove startup-task handoff/close APIs and related tests
- drop unused standby-thread assignment helper in
Copy link
Contributor Author

@eduwercamacaro eduwercamacaro left a comment

Choose a reason for hiding this comment

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

@bbejeck Thanks for your comments
I pushed a new commit with the suggestions.

// Make sure the state manager writes the local checkpoint file before closing the stores
// This will be replaced in the future when removing the checkpoint file dependency.
temporaryStateManager.checkpoint();
temporaryStateManager.close();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently, it is not needed because we keep the lock on the main thread until it is assigned to a SthreadThread. TaskManager changes the ownership of the Task using 'StateDirectory.removeStartupState'.

task.closeDirty();
StateManagerUtil.registerStateStores(log, threadLogPrefix, subTopology, temporaryStateManager, this, initContext);
} catch (final TaskCorruptedException tce) {
log.warn("Failed to register startup state stores for task {}: {}", id, tce.getMessage());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point.
I added some comments and JavaDoc for this method.
Those exceptions are considered fatal and will break the StreamThread. We only handle TaskCorruptedException by logging and continuing with the initialization as this exception will be handled by the StreamThread later in the first assignment.

temporaryStateManager.checkpoint();
temporaryStateManager.close();
}
tasksForLocalState.put(id, new StartupState(id, subTopology, temporaryStateManager));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. Excellent observation.
You are right, we can use Set<TaskId>`` instead. I completely removed the StartupState` class and the process to close the stores because we are immediately closing them.

} finally {
// Make sure the state manager writes the local checkpoint file before closing the stores
// This will be replaced in the future when removing the checkpoint file dependency.
temporaryStateManager.checkpoint();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved this logic inside the try/catch block to ensure that we always call the close() method. Any exception from the close() method is considered fatal and will break the main thread.

Copy link
Member

@bbejeck bbejeck left a comment

Choose a reason for hiding this comment

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

Thanks @eduwercamacaro! LGTM just waiting for the build to complete

@bbejeck bbejeck merged commit 8fa7e09 into apache:trunk Feb 20, 2026
22 checks passed
@bbejeck
Copy link
Member

bbejeck commented Feb 20, 2026

Merged #20749 into trunk

@omkreddy
Copy link
Contributor

looks like build is failing after this commit

@bbejeck
Copy link
Member

bbejeck commented Feb 20, 2026

looks like build is failing after this commit

Thanks @omkreddy! I've pushed a fix we should be good now

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants