Repository navigation
Open
Description
What happened?
There is report that HBaseIO read get stuck intermittently, possible due to failed connection never recovered
At here:
Operation ongoing in bundle process_bundle-8636262876883182297-1613 for PTransform{id=Read-from-HBase-Read-HBaseSource--ParDo-BoundedSourceAsSDFWrapper--ParMultiDo-BoundedSourceAsSDFWrap/ProcessElementAndRestrictionWithSizing-ptransform-66, name=Read-from-HBase-Read-HBaseSource--ParDo-BoundedSourceAsSDFWrapper--ParMultiDo-BoundedSourceAsSDFWrap/ProcessElementAndRestrictionWithSizing-ptransform-66, state=process} for at
least 01h15m05s without outputting or completing: at
java.lang.Object.wait(Native Method) at
java.lang.Object.wait(Object.java:460) at
java.util.concurrent.TimeUnit.timedWait(TimeUnit.java:348) at
org.apache.hadoop.hbase.client.ResultBoundedCompletionService.poll(ResultBoundedCompletionService.java:159) at
org.apache.hadoop.hbase.client.ScannerCallableWithReplicas.call(ScannerCallableWithReplicas.java:198) at
org.apache.hadoop.hbase.client.ScannerCallableWithReplicas.call(ScannerCallableWithReplicas.java:60) at
org.apache.hadoop.hbase.client.RpcRetryingCaller.callWithoutRetries(RpcRetryingCaller.java:200) at
org.apache.hadoop.hbase.client.ClientScanner.call(ClientScanner.java:320) at
org.apache.hadoop.hbase.client.ClientScanner.loadCache(ClientScanner.java:403) at
org.apache.hadoop.hbase.client.ClientScanner.next(ClientScanner.java:364) at
org.apache.hadoop.hbase.client.AbstractClientScanner$1.hasNext(AbstractClientScanner.java:94) at
org.apache.beam.sdk.io.hbase.HBaseIO$HBaseReader.advance(HBaseIO.java:514) at
org.apache.beam.sdk.io.Read$BoundedSourceAsSDFWrapperFn$BoundedSourceAsSDFRestrictionTracker.tryClaim(Read.java:366) at
...
java.lang.Thread.run(Thread.java:750)
Shall we add a timeout to advance?
Issue Priority
Priority: 2 (default / most bugs should be filed as P2)
Issue Components
- Component: Python SDK
- Component: Java SDK
- Component: Go SDK
- Component: Typescript SDK
- Component: IO connector
- Component: Beam examples
- Component: Beam playground
- Component: Beam katas
- Component: Website
- Component: Spark Runner
- Component: Flink Runner
- Component: Samza Runner
- Component: Twister2 Runner
- Component: Hazelcast Jet Runner
- Component: Google Cloud Dataflow Runner
Activity
github-actions commentedon Aug 23, 2026
This issue has been marked as stale due to 150 days of inactivity. It will be closed in 30 days if no further activity occurs. If you think that’s incorrect or this issue still needs to be addressed, please simply write any comment. If closed, you can reopen the issue at any time. Thank you for your contributions.
mxtymoshyk commentedon Aug 31, 2026
Still open, and I went digging into it. I think the diagnosis is a little different from "failed connection never recovered" -- the connection may well be healthy and the scan may well be making progress. The problem is that Beam has no way to tell.
advance()(HBaseIO.java:562) blocks insideiter.hasNext(), which lands in HBase'sClientScanner.loadCache(). Reading that method in 2.6.5, the versionsdks/java/io/hbase/build.gradlepins today:The individual RPC is bounded.
loadCacheissues each request throughcall(callable, caller, scannerTimeout, true)(ClientScanner.java:461), which goes on tocallWithoutRetrieswithhbase.client.scanner.timeout.period, 60s by default. Both polls insideScannerCallableWithReplicas.callare bounded too, and the second one throws"Failed to get result within timeout"rather than waiting. So no single request can sit there forever.What is unbounded is the loop around it.
loadCacheis a barefor (;;)atClientScanner.java:453, andretriesLeftonly decrements on theDoNotRetryIOExceptionpath at:474. The way through it without touching any counter is the heartbeat path at:515. When the region server is still scanning but has not matched a row yet, it returns a heartbeat message; if the client-side cache is empty, the loop falls through to the bottom and goes around again.countdownis untouched because no rows arrived,remainingResultSizeis untouched for the same reason, andretriesLeftnever came into play because nothing threw. A scan with a selective filter over a large region can stay in there for as long as the server keeps looking, with every RPC completing successfully inside its timeout the whole time. The stack in the description is a single sample, so landing in the poll is consistent with going around that loop repeatedly rather than with one call hanging.The awkward consequence is that no HBase client setting bounds this. HBaseIO hands the user's
Configurationstraight toConnectionFactory.createConnection(HBaseIO.java:546) and the module sets no timeouts of its own anywhere, sohbase.client.scanner.timeout.periodandhbase.client.retries.numberare already reachable by users. They just do not apply to this case. Worth knowing before anyone offers them as the workaround.From HBase's side nothing is going wrong here. Heartbeats exist precisely so that a slow server-side scan does not trip the scanner timeout. The problem sits on Beam's side of the boundary: while
hasNext()is in that loop the reader cannot report progress, cannot be split, and cannot be cancelled, which is what the "operation ongoing ... without outputting or completing" message is really describing.On the fix itself, a timeout in
advance()is possible but more constrained than it first looks.advance()returning false means end of input, so a timeout has no way to say "still working" -- it would have to throw and fail the bundle. That is a real behaviour change, since a genuinely slow but healthy scan would start failing, and it needs a thread per reader to interrupt. HBase RPC threads do not reliably honour interrupts, so cancelled work can be left running behind it.There is a second option I would put ahead of that, because HBase already has an answer for this exact situation.
Scan.setNeedCursorResult(true)makes the server return a cursorResulton heartbeat instead of nothing, and the client then breaks out of the loop atClientScanner.java:528-531instead of spinning.advance()would need to recogniseResult.isCursor(), skip it as a non-row, and usegetCursor().getRow()to move the range tracker forward. Beam gets control back on every heartbeat, which restores progress reporting and cancellation without changing when a read is allowed to fail. I checked and all three of those APIs are present in the shaded client we pin today. The catch is that it needs an HBase 2.0+ server, so it would have to be opt-in and would quietly do nothing against an older one.Those two are not the same fix and I do not want to guess which you want: one bounds how long a read may take, the other bounds how long Beam is blind for. They compose fine if you want the cursor for control and a timeout as a backstop, but that is a bigger change than either alone.
Testing looks cheap whichever way it goes.
HBaseIOCloseTestalready mocksResultScannerandConnection, so a blocking mock iterator would give a deterministic test without needing the mini cluster thatHBaseIOTestspins up.Happy to write whichever you prefer. Not claiming the issue in the meantime.