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 2 times, most recently from 4b0287f to c97daba Compare November 19, 2025 18:07
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 4 times, most recently from 5013920 to def6e6f Compare December 3, 2025 21:03
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.

/**
* Supported compaction types based on Cassandra's OperationType enum
*/
public enum CompactionType
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not see MAJOR_COMPACTION type here from OperationType class, is that intentional?

Copy link
Author

Choose a reason for hiding this comment

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

Thanks for the catch - I may have been looking at another C* version. Just changed it to reflect this list: https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/db/compaction/OperationType.java

@@ -0,0 +1,17 @@
package org.apache.cassandra.sidecar.common.data;
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing license for file

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