You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
WN returns error_locked; CN calls lock_resolver->resolveLocks(...) and retries EstablishDisaggTask.
TiKV begins resolve_lock committing transaction for the same start_ts with commit_ts=468120244616167447 (commit_ts < query start_ts, same physical ms).
TiFlash keeps retrying MEET_LOCK for ~2s (wait_cost=0ms, identical lock fields) until EstablishDisaggTask finally succeeds.
Relevant CN / WN code paths:
// StorageDisaggregatedRemote.cpp — CN resolve then always retry Establishauto before_expired = cluster->lock_resolver->resolveLocks(
bo,
sender_target_mpp_task_id.gather_id.query_id.start_ts,
locks,
pushed);
// TODO: Use `pushed` to bypass large txn.throwException(error_msg, ErrorCodes::DISAGG_ESTABLISH_RETRYABLE_ERROR);
// DAGStorageInterpreter.cpp — no local bypass retry for disagg taskcatch (const LockException & e)
{
if (context.getDAGContext()->is_disaggregated_task)
throw; // let CN retry
...
}
2. What did you expect to see? (Required)
Once CN has resolved the lock status and TiKV has determined the write txn is already committed with commit_ts < query start_ts (i.e. the committed data is visible to this snapshot), the disagg read path should not keep failing EstablishDisaggTask on the same stale local LockCF entry for seconds.
Expected behavior options:
After CN resolveLocks confirms the txn is committed / cleanup is in progress, the next EstablishDisaggTask (or WN local retry) should wait for a fresh read index that covers the resolve/commit raft logs, so WN no longer sees the old LockCF entry.
Or, once the lock's final status is known, propagate enough information (bypass_lock_ts / resolved txn id, or equivalent) so WN can skip the already-handled lock while secondary resolve is still applying.
Avoid a long CN↔WN EstablishDisaggTask retry loop with wait_cost=0ms that re-observes an unchanged local lock.
In short: after “txn already committed + resolve_lock committing secondary”, disagg read should converge on learner apply / correct bypass, not spin on the same MEET_LOCK.
⇒ commit_ts < start_ts ⇒ committed data is visible to this query
So this is not primarily “alive large txn waiting for TTL / pushed bypass”.
It is: txn already committed; secondary locks are being committed by resolve_lock; TiFlash learner still observes the old LockCF and keeps retrying.
Why MVCC still reports the lock on WN (correct locally)
On WN LockCF check (DecodedLockCFValue::getLockInfoPtr):
lock_version < start_ts
min_commit_ts < start_ts
lock type is Put (not skippable PessimisticLock/SharedLock)
Until the resolve/commit raft log is applied on the TiFlash learner (or the lock is explicitly bypassed), WN must treat it as blocking.
Why the retry is slow / inefficient (problem)
CN resolveLocks triggers TiKV secondary commit (request_source=external_Select), but EstablishDisaggTask retries do not effectively wait for learner to catch the new resolve/commit index (wait_cost=0ms repeatedly).
Disagg WN path always rethrows LockException to CN; no local bypass / short-wait retry like non-disagg MPP/batch-cop (Optimize lock handling in MPP local read #10992 does not apply to is_disaggregated_task).
Large txn (43320 keys) means secondary resolve/apply can take noticeable time; during that window CN↔WN spins on the same local lock for ~2s.
Notes on related PRs:
#10986: invalidate stale read-index cache after local lock is found. Relevant to “resolve already committing on TiKV, but learner reused old read index and did not wait for resolve logs”. Worth verifying whether this branch includes it; it addresses part of this failure mode.
#10992: local bypass-lock retry for MPP/batch-cop. Explicitly rethrows on is_disaggregated_task, so this CN↔WN EstablishDisaggTask loop is unchanged.
CN TODO: Use pushed to bypass large txn helps the “txn still alive / not expired” case; for this incident (already committed, commit_ts < start_ts), the missing piece is mainly wait for resolve apply / avoid stale read-index reuse, not pushed TTL wait.
Bug Report
Please answer these questions before submitting your issue. Thanks!
1. Minimal reproduce step (Required)
Environment:
ENABLE_NEXT_GEN=ON,ENABLE_NEXT_GEN_COLUMNAR=OFFStorageDisaggregated::readThroughTiFlashWriteEstablishDisaggTaskand performs learner read (wait index+ resolve locks)txn_info!resolve_lock logs)Observed production-like case (TiFlash logs in
investigate.md, plustidb.log/tikv.log):[BIG_TXN],txnStartTS=468120244537524231,keys=43320,size≈5.6MB).start_ts=468120244616167462.EstablishDisaggTaskto WN for table scan regions.91537:lock_version=468120244537524231(= write txn start_ts)min_commit_ts=468120244603060470(min_commit_ts < start_ts)txn_size=2166,lock_ttl≈20126error_locked; CN callslock_resolver->resolveLocks(...)and retries EstablishDisaggTask.resolve_lock committing transactionfor the samestart_tswithcommit_ts=468120244616167447(commit_ts < query start_ts, same physical ms).MEET_LOCKfor ~2s (wait_cost=0ms, identical lock fields) until EstablishDisaggTask finally succeeds.Relevant CN / WN code paths:
2. What did you expect to see? (Required)
Once CN has resolved the lock status and TiKV has determined the write txn is already committed with
commit_ts < query start_ts(i.e. the committed data is visible to this snapshot), the disagg read path should not keep failing EstablishDisaggTask on the same stale local LockCF entry for seconds.Expected behavior options:
resolveLocksconfirms the txn is committed / cleanup is in progress, the next EstablishDisaggTask (or WN local retry) should wait for a fresh read index that covers the resolve/commit raft logs, so WN no longer sees the old LockCF entry.bypass_lock_ts/ resolved txn id, or equivalent) so WN can skip the already-handled lock while secondary resolve is still applying.wait_cost=0msthat re-observes an unchanged local lock.In short: after “txn already committed + resolve_lock committing secondary”, disagg read should converge on learner apply / correct bypass, not spin on the same
MEET_LOCK.3. What did you see instead (Required)
Timeline (UTC, 2026-08-03)
[BIG_TXN]start 2PC:txnStartTS=468120244537524231,keys=43320,puts=43320,size=5678162start_ts=468120244616167462MEET_LOCKon region91537; same lock fields as belowerror_locked→resolveLocks→ retry EstablishDisaggTaskresolve_lock committing transactionforstart_ts=468120244537524231,commit_ts=468120244616167447,request_source=external_Select(and empty)wait_cost=0msand identical lock infoerror=false)Evidence from
tidb.log/tikv.logtidb.log(client-gotxnkv/transaction/2pc.go, thresholdkeys>10000orsize>4MB):→ Confirms the blocking lock belongs to a very large 2PC write (tens of thousands of secondary locks).
tikv.log(next-gen / cloud-storage-engineresolve_lock.rsviatxn_info!):Important ts relation (same physical ms):
commit_tslogical = 23start_tslogical = 38commit_ts < start_ts⇒ committed data is visible to this querySo this is not primarily “alive large txn waiting for TTL / pushed bypass”.
It is: txn already committed; secondary locks are being committed by resolve_lock; TiFlash learner still observes the old LockCF and keeps retrying.
Why MVCC still reports the lock on WN (correct locally)
On WN LockCF check (
DecodedLockCFValue::getLockInfoPtr):lock_version < start_tsmin_commit_ts < start_tsUntil the resolve/commit raft log is applied on the TiFlash learner (or the lock is explicitly bypassed), WN must treat it as blocking.
Why the retry is slow / inefficient (problem)
resolveLockstriggers TiKV secondary commit (request_source=external_Select), but EstablishDisaggTask retries do not effectively wait for learner to catch the new resolve/commit index (wait_cost=0msrepeatedly).LockExceptionto CN; no local bypass / short-wait retry like non-disagg MPP/batch-cop (Optimize lock handling in MPP local read #10992 does not apply tois_disaggregated_task).43320keys) means secondary resolve/apply can take noticeable time; during that window CN↔WN spins on the same local lock for ~2s.Notes on related PRs:
is_disaggregated_task, so this CN↔WN EstablishDisaggTask loop is unchanged.TODO: Use pushed to bypass large txnhelps the “txn still alive / not expired” case; for this incident (already committed,commit_ts < start_ts), the missing piece is mainly wait for resolve apply / avoid stale read-index reuse, not pushed TTL wait.4. What is your TiFlash version? (Required)
tidb: v8.5.4-nextgen.202510.24
tikv: v8.5.4-nextgen.202510.31
tiflash: v8.5.4-nextgen.202510.13
ENABLE_NEXT_GEN=ONENABLE_NEXT_GEN_COLUMNAR=OFF(reads usereadThroughTiFlashWrite, not columnar path)