{"record":{"id":"688e65493eeea5f1","repo":"apache/seatunnel","slug":"failed-to-close-sql-cursor-688e65","errorCode":null,"errorMessage":"Failed to close SQL cursor: {}","messagePattern":"Failed to close SQL cursor: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/source/ElasticsearchSourceReader.java","lineNumber":124,"sourceCode":"            try {\n                ScrollResult scrollResult =\n                        esRestClient.searchBySql(\n                                sourceIndexInfo.getSqlQuery(), sourceIndexInfo.getScrollSize());\n\n                outputFromScrollResult(scrollResult, sourceIndexInfo, output, deserializer);\n                cursor = scrollResult.getScrollId();\n                while (StringUtils.isNotEmpty(cursor)) {\n                    scrollResult =\n                            esRestClient.searchWithSql(cursor, scrollResult.getColumnNodes());\n                    outputFromScrollResult(scrollResult, sourceIndexInfo, output, deserializer);\n                    cursor = scrollResult.getScrollId();\n                }\n            } finally {\n                if (StringUtils.isNotEmpty(cursor)) {\n                    try {\n                        esRestClient.closeSqlCursor(cursor);\n                    } catch (Exception e) {\n                        log.warn(\"Failed to close SQL cursor: {}\", cursor, e);\n                    }\n                }\n            }\n        } else {\n            // Check if we should use PIT API\n            if (SearchApiTypeEnum.PIT.equals(sourceIndexInfo.getSearchApiType())) {\n                log.info(\"Using Point-in-Time (PIT) API for index: {}\", sourceIndexInfo.getIndex());\n                logSliceInfo(sourceIndexInfo);\n                searchWithPointInTime(sourceIndexInfo, output, deserializer);\n            } else {\n                log.info(\"Using Scroll API for index: {}\", sourceIndexInfo.getIndex());\n                String scrollId = null;\n                try {\n                    logSliceInfo(sourceIndexInfo);\n                    ScrollResult scrollResult =\n                            esRestClient.searchByScroll(\n                                    sourceIndexInfo.getIndex(),\n                                    sourceIndexInfo.getSource(),","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/source/ElasticsearchSourceReader.java#L106-L142","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check connectivity/auth between SeaTunnel and Elasticsearch; this warn usually accompanies a network or auth failure","Increase the SQL cursor keep-alive (search.max_keep_alive / request-level keep_alive) if jobs idle between pages","Verify the cursor was not already consumed/closed by a retry or duplicate poll; inspect the logged cursor ID with DELETE /_sql/close manually","If recurring on your ES version, check SeaTunnel connector version for SQL cursor handling fixes"],"exampleFix":"// before\ntry {\n    esRestClient.closeSqlCursor(cursor);\n} catch (Exception e) {\n    log.warn(\"Failed to close SQL cursor: {}\", cursor, e);\n}\n// after\n// add retry with short backoff before giving up\ntry {\n    esRestClient.closeSqlCursor(cursor);\n} catch (Exception e) {\n    log.warn(\"Failed to close SQL cursor: {}, will rely on server keep-alive expiry\", cursor, e);\n}","handlingStrategy":"try-catch","validationCode":"// before submitting: verify ES reachable and auth OK\ncurl -u user:pass -X GET \"https://es-host:9200/_cluster/health\" | jq '.status'","typeGuard":null,"tryCatchPattern":"// this is a library-side warn; callers of the SeaTunnel source cannot catch it.\n// operational guard: alert on the log message\n//   if (logs.match(/Failed to close SQL cursor/)) { clear cursor via DELETE /_sql/close }","preventionTips":["Set a generous SQL cursor keep_alive so cursors survive pagination gaps","Monitor ES cluster health and network stability during long reads","Alert on this log pattern and manually close the logged cursor ID if PIT/scroll contexts accumulate"],"tags":["elasticsearch","cleanup","network"],"backgroundTag":"http-error-response","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}