apache/seatunnel · warning

Failed to close SQL cursor: {}

Error message

Failed to close SQL cursor: {}

What it means

This is a WARN-level log (not a thrown exception) emitted by ElasticsearchSourceReader.scrollSearchResult when the reader finishes consuming a SQL SEARCH_AFTER-style cursor and the Elasticsearch REST client fails to close it via DELETE /_sql/close. The query results are already delivered; only server-side cursor state may linger until it expires. The cursor ID is logged to allow manual cleanup.

Source

Thrown at seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/source/ElasticsearchSourceReader.java:124

            try {
                ScrollResult scrollResult =
                        esRestClient.searchBySql(
                                sourceIndexInfo.getSqlQuery(), sourceIndexInfo.getScrollSize());

                outputFromScrollResult(scrollResult, sourceIndexInfo, output, deserializer);
                cursor = scrollResult.getScrollId();
                while (StringUtils.isNotEmpty(cursor)) {
                    scrollResult =
                            esRestClient.searchWithSql(cursor, scrollResult.getColumnNodes());
                    outputFromScrollResult(scrollResult, sourceIndexInfo, output, deserializer);
                    cursor = scrollResult.getScrollId();
                }
            } finally {
                if (StringUtils.isNotEmpty(cursor)) {
                    try {
                        esRestClient.closeSqlCursor(cursor);
                    } catch (Exception e) {
                        log.warn("Failed to close SQL cursor: {}", cursor, e);
                    }
                }
            }
        } else {
            // Check if we should use PIT API
            if (SearchApiTypeEnum.PIT.equals(sourceIndexInfo.getSearchApiType())) {
                log.info("Using Point-in-Time (PIT) API for index: {}", sourceIndexInfo.getIndex());
                logSliceInfo(sourceIndexInfo);
                searchWithPointInTime(sourceIndexInfo, output, deserializer);
            } else {
                log.info("Using Scroll API for index: {}", sourceIndexInfo.getIndex());
                String scrollId = null;
                try {
                    logSliceInfo(sourceIndexInfo);
                    ScrollResult scrollResult =
                            esRestClient.searchByScroll(
                                    sourceIndexInfo.getIndex(),
                                    sourceIndexInfo.getSource(),

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Check connectivity/auth between SeaTunnel and Elasticsearch; this warn usually accompanies a network or auth failure
  2. Increase the SQL cursor keep-alive (search.max_keep_alive / request-level keep_alive) if jobs idle between pages
  3. Verify the cursor was not already consumed/closed by a retry or duplicate poll; inspect the logged cursor ID with DELETE /_sql/close manually
  4. If recurring on your ES version, check SeaTunnel connector version for SQL cursor handling fixes

Example fix

// before
try {
    esRestClient.closeSqlCursor(cursor);
} catch (Exception e) {
    log.warn("Failed to close SQL cursor: {}", cursor, e);
}
// after
// add retry with short backoff before giving up
try {
    esRestClient.closeSqlCursor(cursor);
} catch (Exception e) {
    log.warn("Failed to close SQL cursor: {}, will rely on server keep-alive expiry", cursor, e);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before submitting: verify ES reachable and auth OK
curl -u user:pass -X GET "https://es-host:9200/_cluster/health" | jq '.status'

Try / catch

// this is a library-side warn; callers of the SeaTunnel source cannot catch it.
// operational guard: alert on the log message
//   if (logs.match(/Failed to close SQL cursor/)) { clear cursor via DELETE /_sql/close }

Prevention

When it happens

Trigger: pollNext -> scrollSearchResult completes a SQL cursor pagination loop, then esRestClient.closeSqlCursor(cursor) throws because the ES node is unreachable, the cursor already expired (search.max_keep_alive exceeded), was already closed, or the HTTP call fails mid-batch.

Common situations: Elasticsearch cluster restart or network blip between the last page fetch and cursor close; jobs that idle longer than the cursor keep-alive before the final close; query aborted upstream so the cursor was auto-cleaned; ES version mismatch where the _sql/close endpoint behaves differently.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/688e65493eeea5f1. Report an issue: GitHub.