Skip to content

Conversation

@shalnisundram
Copy link

Add bespoke API endpoint for nodetool compaction stop.

This implementation carries out functionality currently performed when execution command nodetool stop, which is a short-running operation that requests ongoing compactions of a specified type or id to be stopped.

See CASSANDRA-20021 JIRA ticket - more details can be found on ongoing effort to build APIs for nodetool C* cluster management operations.

patch by Shalni Sundram

else
{
// Handler guarantees compactionType is non-null/non-empty
proxy.stopCompaction(compactionType);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should add a @NotNull annotation to compactionType?

Copy link
Author

Choose a reason for hiding this comment

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

See change on CassandraCompactionManagerOperations line 77 . Not exactly an annotation but a requireNonNull check. Let me know if that's acceptable.

@JsonInclude(JsonInclude.Include.NON_NULL)
public class CompactionStopRequestPayload
{
private final String compactionType;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be an enum instead of a free string?

Copy link
Author

Choose a reason for hiding this comment

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

Chose deliberately to use String, as I figured this would encourage forward compatibility with future C* versions if more compactionTypes are added later. i.e., would want to avoid sidecar forcing all C* versions into a single enum definition, and would rather leave this responsibility to cassandra itself. Would appreciate @arjunashok's input for this though.

Copy link
Contributor

Choose a reason for hiding this comment

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

You are already defining the compaction types as a set in AbstractHandler.SUPPORTED_COMPACTION_TYPES. Better to make it an enum instead of a set.

Copy link
Author

Choose a reason for hiding this comment

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

Done

Comment on lines 93 to 96
@APIResponse(description = "Compaction stop operation completed",
responseCode = "200",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = CompactionStopResponse.class)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add the @apiresponse for the error codes as well

Copy link
Author

@shalnisundram shalnisundram Oct 31, 2025

Choose a reason for hiding this comment

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

See lines 149-189 of CompactionStopHandler.java - I do some custom error handling here, as particular api response codes will have different messages thrown depending on the input. Let me know if I should still edit this section in CassandraOperationsModule.

Copy link
Contributor

@sklaha sklaha left a comment

Choose a reason for hiding this comment

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

Thank you for the PR. Looks good at a high level. Please run ./gradlew clean; ./gradlew build which will show you check style issues. I am leaving my other comments inline.

}

@Test
void testStopCompactionByIdSuccess()
Copy link
Contributor

Choose a reason for hiding this comment

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

In the current implementation, a request with a non-existent compaction ID silently succeeds. The JMX call simply succeeds without any action being taken. It would be more informative to update the Cassandra JMX implementation to return a boolean value or some other indicator. I will seek input from others on this.

https://github.pie.apple.com/slaha/aci-cassandra/blob/38ab7d02e0979074f3de1c7f90ce70668d2492b9/src/java/org/apache/cassandra/db/compaction/CompactionManager.java#L2306

Comment on lines 34 to 35
public static final String STATUS_KEY = "status";
public static final String ERROR_CODE_KEY = "error_code";
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's evaluate the intended usage of the error_code field in determining the values. Based on the current name, it seems to suggest this is a status code (currently returning 200 OK)

The 200 status code would be redundant in this case, as it is implied with the HTTP response code.

The other option is to do away with this field since we have a reason field to denote an arbitrary string reason when there is an error (or a non-200 response).

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, can status be an enum here? Seems like it can be one of 3 or 4 values - SUBMITTED, PENDING or FAILED

Copy link
Author

@shalnisundram shalnisundram Nov 19, 2025

Choose a reason for hiding this comment

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

Original status options for the command were just two - "PENDING" and "FAILED". For this API it's "SUBMITTED" (more clear I think) and "FAILED".
Status changed to enum - see CompactionStopStatus.java for new enum class

@shalnisundram shalnisundram force-pushed the CASSSIDECAR-360 branch 4 times, most recently from fd5e624 to a6b5497 Compare November 19, 2025 19:06
Copy link
Contributor

@arjunashok arjunashok left a comment

Choose a reason for hiding this comment

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

Looking good for the most part. Added some minor comments.

assertThat(stopResponse).isNotNull();
assertThat(stopResponse.status()).isEqualTo(CompactionStopStatus.SUBMITTED);
assertThat(stopResponse.compactionType()).isEqualTo("COMPACTION");
assertThat(stopResponse.reason()).isEqualTo("Operation Succeeded");
Copy link
Contributor

Choose a reason for hiding this comment

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

No longer applicable, correct?

Copy link
Author

Choose a reason for hiding this comment

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

Omitted the reason() field - the status , type, and id fields still stand in the response so left those in

@shalnisundram shalnisundram force-pushed the CASSSIDECAR-360 branch 5 times, most recently from def6e6f to f404939 Compare December 3, 2025 21:05
public static final String STREAM_STATS_ROUTE = API_V1 + CASSANDRA + "/stats/streams";
public static final String TABLE_STATS_ROUTE = API_V1 + CASSANDRA + PER_KEYSPACE + PER_TABLE + "/stats";

public static final String COMPACTION_STOP_ROUTE = API_V1 + CASSANDRA + "/operations/compaction/stop";
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I see the operations part of the path in several static variables here. Let's extract it to its own private static

Copy link
Author

Choose a reason for hiding this comment

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

Done. Added the private static, but only used it for the node stop route rather than node drain and node decommission routes which also uses this path, as those changes would not be directly related to this PR.


// Attempt to stop the compaction
// If compactionId is provided, use it (takes precedence over type)
if (compactionId != null && !compactionId.trim().isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I see this pattern of "if compactionID then do otherwise do by type" in several different places. What do you think about abstracting that inside operations, with a generic method that does stopCompaction(@Nullable String compactionId, @Nullable String compactionType), and performs that if internally?

Sorry if this has already been discussed.

Copy link
Author

Choose a reason for hiding this comment

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

Yeah in the latter case we would have a single method that accepts two params but only uses one, which others expressed was not preferable. Thus, preserved the stopByID() and stopCompaction() structure already used by node stop


// Validate that at least one field is provided
if (payload.compactionType() == null &&
(payload.compactionId() == null || payload.compactionId().trim().isEmpty()))
Copy link
Contributor

Choose a reason for hiding this comment

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

I also see this pattern of == null or == null or empty across different places. What about adding boolean public methods on payload that return if they are valid? Something like isCompactionTypeValid() and isCompactionIdValid?

Copy link
Author

Choose a reason for hiding this comment

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

Done - added those functions in CompactionStopRequestPayload.java, although didn't use those functions for the pattern present in CassandraCompactionManagerOperations.java, as those methods don't work with the payload object.

@jyothsnakonisa
Copy link
Contributor

Looks good overall, few minor comments

Copy link
Contributor

@sarankk sarankk left a comment

Choose a reason for hiding this comment

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

+1 Thanks Shalni, overall looks great to me. Left some comments, I am +1 once the comments are addressed.

Copy link
Contributor

@sarankk sarankk left a comment

Choose a reason for hiding this comment

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

Changes look great to me, left final set of comments.

@Override
public List<String> supportedCompactionTypes()
{
return Arrays.stream(CompactionType.values())
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of creating a stream of values each time, can you compute it once and return it?


public CassandraFactory(DnsResolver dnsResolver, DriverUtils driverUtils, TableSchemaFetcher tableSchemaFetcher)
{
this.dnsResolver = Objects.requireNonNull(dnsResolver, "dnsResolver is required");
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you revert these changes?


public Cassandra41Factory(DnsResolver dnsResolver, DriverUtils driverUtils, TableSchemaFetcher tableSchemaFetcher)
{
this.dnsResolver = Objects.requireNonNull(dnsResolver, "dnsResolver is required");
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you revert these changes, they seem to be unrelated to PR

@Override
@NotNull
protected StorageOperations createStorageOperations(DnsResolver dnsResolver, JmxClient jmxClient)
public StorageOperations storageOperations()
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you revert this change too

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.

6 participants