Query Cancellation
The Query Engine supports cancelling both running and queued queries. Cancellation is important for controlling resource consumption, aborting expensive queries, and providing users with the ability to stop long-running operations.
Endpoint
POST /v1/queries/{executionId}/cancelRequired Headers
| Header | Type | Description |
|---|---|---|
X-Tenant-ID | UUID | The tenant identifier |
Authorization | String | Bearer JWT token |
Cancellation Flow
The QueryController delegates to the QueryExecutionService.cancelQuery() method:
@PostMapping("/{executionId}/cancel")
public ResponseEntity<Map<String, Object>> cancelQuery(
@RequestHeader("X-Tenant-ID") UUID tenantId,
@PathVariable UUID executionId) {
boolean cancelled = executionService.cancelQuery(tenantId, executionId);
return ResponseEntity.ok(Map.of(
"executionId", executionId,
"cancelled", cancelled,
"message", cancelled ? "Query cancelled" : "Query could not be cancelled"
));
}The cancellation implementation uses a database-level update to atomically transition queries in PENDING, QUEUED, or RUNNING states to CANCELLED:
@Transactional
public boolean cancelQuery(UUID tenantId, UUID executionId) {
int updated = executionRepository.cancelQuery(executionId);
if (updated > 0) {
log.info("Query cancelled: {}", executionId);
meterRegistry.counter("query.cancelled", "tenant", tenantId.toString()).increment();
return true;
}
return false;
}curl Example
# Cancel a running query
curl -X POST http://query-engine:8080/v1/queries/d290f1ee-6c54-4b01-90e6-d701748f0851/cancel \
-H "X-Tenant-ID: 550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer $JWT_TOKEN"Success Response
{
"executionId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"cancelled": true,
"message": "Query cancelled"
}Failure Response (Query Already Completed)
{
"executionId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"cancelled": false,
"message": "Query could not be cancelled"
}Cancellation via Workload Manager
For queries managed through the workload system, cancellation also releases resource reservations:
// WorkloadController
@PostMapping("/cancel/{queryId}")
public ResponseEntity<Map<String, Object>> cancelQuery(
@PathVariable String queryId,
@RequestParam(required = false) String reservationId) {
boolean cancelled = workloadManager.cancelQuery(queryId, reservationId);
return ResponseEntity.ok(Map.of(
"queryId", queryId,
"cancelled", cancelled
));
}The WorkloadManager.cancelQuery() method removes the query from the queue (if queued) and releases any resource reservations (if executing):
public boolean cancelQuery(String queryId, String reservationId) {
boolean removed = queryQueue.remove(queryId);
if (reservationId != null) {
resourcePool.release(reservationId);
}
if (removed) {
meterRegistry.counter("query.workload.cancelled").increment();
}
return removed || reservationId != null;
}States That Can Be Cancelled
| Current State | Cancellable | Effect |
|---|---|---|
PENDING | Yes | Record updated to CANCELLED |
QUEUED | Yes | Removed from queue, record updated |
RUNNING | Yes | Engine-level cancellation attempted, record updated |
COMPLETED | No | Returns cancelled: false |
FAILED | No | Returns cancelled: false |
CANCELLED | No | Already cancelled, returns cancelled: false |
TIMEOUT | No | Already terminated, returns cancelled: false |
Engine-Level Cancellation
For Trino queries, cancellation triggers a JDBC statement cancellation that propagates to the Trino coordinator, which then cancels all running tasks across workers. The TrinoQueryStrategy detects cancellation via a SQLException containing the word "cancelled":
} catch (SQLException e) {
if (e.getMessage() != null && e.getMessage().contains("cancelled")) {
throw new QueryCancelledException("Query was cancelled");
}
throw new QueryExecutionException("Trino query failed: " + e.getMessage(), e);
}Metrics
| Metric | Type | Description |
|---|---|---|
query.cancelled | Counter | Total queries cancelled, tagged by tenant |
query.workload.cancelled | Counter | Queries cancelled via workload manager |