Skip to content

CASSSIDECAR-377: Implement job coordination for cluster-wide operations#360

Open
andresbeckruiz wants to merge 6 commits into
apache:trunkfrom
andresbeckruiz:CASSSIDECAR-377
Open

CASSSIDECAR-377: Implement job coordination for cluster-wide operations#360
andresbeckruiz wants to merge 6 commits into
apache:trunkfrom
andresbeckruiz:CASSSIDECAR-377

Conversation

@andresbeckruiz

Copy link
Copy Markdown
Contributor

CASSSIDECAR-377

Original PR made against CASSSIDECAR-373 branch with review comments: andresbeckruiz#2.

Changes

  • OperationalJobCoordinator interface
  • StorageOperationalJobCoordinator: Implementation that delegates to StorageProvider's compare and set based methods to acquire a lock for an active operation
  • OperationalJob.operationType(): New abstract method returning OperationType enum, implemented by all concrete jobs
  • OperationalJobManager integration: If a job requires coordination, the manager acquires the active lock via the coordinator before execution; throws OperationalJobConflictException if rejected
  • Unit tests for StorageOperationalJobCoordinator and coordinator integration in OperationalJobManagerTest

This ticket only covers the job activation path when a job is submitted to be executed immediately. Clearing locks after completion and status update activation will be implemented in later tickets (CASSSIDECAR-378, CASSSIDECAR-379).

Future Work

  • CASSSIDECAR-378: Add support for creation and coordination of local Sidecar Jobs
  • CASSSIDECAR-379: Add PATCH operational-jobs/{id} API to update status of existing operational jobs

@andresbeckruiz
andresbeckruiz force-pushed the CASSSIDECAR-377 branch 2 times, most recently from 0e36849 to 817bf51 Compare June 12, 2026 19:22

// New job is submitted for all cases when we do not have a corresponding downstream job
jobTracker.computeIfAbsent(job.jobId(), jobId -> {
OperationalJob tracked = jobTracker.computeIfAbsent(job.jobId(), jobId -> job);

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.

do we need to remove from jobTracker if there's a coordination conflict ? Or perhaps should we just add to jobTracker in case the tryCoordination was successful, so tryCoordination should come before jobtracker.computeIfAbsent ?

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.

This leaves stale job entry in CREATED state if the coordination fails.

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.

Would it make sense to mark the job as FAILED if coordination fails? And set the failureReason to indicate that the job was not able to start due to coordination failure?

@andresbeckruiz andresbeckruiz Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Coordination now happens before tracking and jobs that fail coordination are now marked as FAILED, addressed in 3661c4a

{
if (job.requiresCoordination())
{
Preconditions.checkState(coordinator != null,

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.

Preconditions throws illegal state exception, which is not handled in the caller

@andresbeckruiz andresbeckruiz Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No more unhandled IllegalStateException, addressed in 3661c4a

};

manager.trySubmitJob(job, onComplete, executorPool.service(), SecondBoundConfiguration.parse("5s"));
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();

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.

Also verify that tracker doesn't have entry for this job after conflict detected.

@andresbeckruiz andresbeckruiz Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Jobs are now marked as FAILED if coordination fails, so no conflict will happen as inflightJobsByOperation does not return completed jobs. Added

assertThat(tracker.inflightJobsByOperation(job.name())).doesNotContain(job);

in 3661c4a to confirm

{
Preconditions.checkState(coordinator != null,
"Job requires coordination but no OperationalJobCoordinator is configured");
boolean activated = coordinator.trySetActive(job.operationType(), job.jobId());

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.

I am afraid to merge this PR without calling clearActive. If we delay adding that in a subsequent PR and if someone enables requiresCoordination() for any job meanwhile, then no more further operations will be allowed. I would recommend implementing calling clearActive in the same PR.

@andresbeckruiz andresbeckruiz Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in 097a9af

@andresbeckruiz

Copy link
Copy Markdown
Contributor Author

Comment on lines +46 to +47
@Nullable
private final OperationalJobCoordinator coordinator;

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.

The nullable OperationalJobCoordinator feels like a bit of a smell to me. It seems like the goal here is to support backwards compatibility for the OperationalJobManager for users of the OperationalJobManager to not have to include coordination (e.g., for existing decommission jobs), am I right about that?

If so, I'd suggest that instead of having a null OperationalJobCoordinator, we have a second implementation, DisabledOperationalJobCoordinator or something similar, that fails all coordination tasks with an error saying that coordination is not currently supported by this sidecar instance (and maybe how to configure coordination if the user wants to have coordinated jobs).

This way we make the coordination enabled / disabled logic part of the behavior of the coordinator itself, as opposed to having a null dependency here. It can also clean up all accesses to the OperationalJobCoordinator as we no longer have to worry about it being null.

@andresbeckruiz andresbeckruiz Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea. Replaced the @Nullable OperationalJobCoordinator with a DisabledOperationalJobCoordinator in 405a93d

To clarify, backwards compatibility for existing uncoordinated jobs (eg: decommission) is already handled by requiresCoordination() == false, so they short circuit before touching the coordinator. Therefore, the DisabledOperationalJobCoordinator only matters for a job requiring coordination submitted to an instance without coordination support.

Also updated this patch to bind the OperationalJobCoordinator to the DisabledOperationalJobCoordinator. Configuration to enable this class (as well as durable tracking) will be added in CASSSIDECAR-378.

@andresbeckruiz
andresbeckruiz force-pushed the CASSSIDECAR-377 branch 4 times, most recently from e4bf6fa to 509b15b Compare July 9, 2026 16:15
@andresbeckruiz
andresbeckruiz requested a review from isaacreath July 9, 2026 16:31

@isaacreath isaacreath 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.

+1 (nb)

{
if (ar.succeeded() && Boolean.TRUE.equals(ar.result()))
{
job.asyncResult().onComplete(result -> releaseActiveOperationLock(job));

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.

requiresCoordination() is a clean job-level flag the manager just reads.

The release side isn't symmetric though, as trySubmitJob always auto-releases on local completion, with no way for a job to opt out. That's right for today's single-node jobs, but doesn't match clearActive's javadoc (orchestration-layer clearing for jobs that finish elsewhere).

Rather than just documenting that gap, could we add a parallel hook for release?
e.g. releasesOnCompletion(), default true; the manager just reads it instead of special-casing job types, so a future distributed job can skip the automatic release without any change to OperationalJobManager.

@andresbeckruiz andresbeckruiz Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch-- addressed in 5d5afc6

@andresbeckruiz
andresbeckruiz requested a review from arjunashok July 9, 2026 21:21
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.

5 participants