-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
storage/concurrency: don't push reservation transactions #45420
storage/concurrency: don't push reservation transactions #45420
Conversation
45946b1
to
f2b0bb7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed 2 of 2 files at r1, 1 of 1 files at r2.
Reviewable status: complete! 1 of 0 LGTMs obtained (waiting on @nvanbenschoten)
pkg/storage/concurrency/lock_table_waiter.go, line 88 at r2 (raw file):
// conflict. // NOTE: we could pull this logic into pushTxn, but then we'd be // creating useless timers.
The "therefore not appropriate" sentence followed by the "This is not true" sentence were a bit hard to read, so I tried to rephrase in a simpler form below, and added some further detail, in case you can use that to adjust the comment.
// If the lock is not held and instead has a reservation, we don't
// want to push the reservation transaction. A transaction push will
// block until the pushee transaction has either committed or
// aborted or pushed or rolled back savepoints, i.e., there is some state
// change that has happened to the transaction record that unblocks the
// pusher. It will not unblock merely because a request issued by the
// pushee transaction has completed and released a reservation. Note
// that:
// - reservations are not a guarantee that the lock will be acquired.
// - the two reasons to push do not apply to requests holding reservations:
// - competing requests compete at exactly one lock table, so there is no
// possibility of distributed deadlock due to reservations.
// - the lock table can prioritize requests based on transaction priorities (TODO(sbhola) ...)
pkg/storage/concurrency/lock_table_waiter.go, line 96 at r2 (raw file):
// reservations? // TODO(sbhola): does this allow us to get rid of the concept of a // reservaton entirely? Is there a reason to keep it?
it is likely that we can eliminate reservations from the lock table.
This commit updates the lockTableWaiter to no longer push reservation transactions. The waiter now only pushes the holders of conflicting locks that are held by other transactions. This is because a transaction push will block until the pushee transaction has either committed or aborted. It is therefore not appropriate for cases where there is still a chance for the pusher to make progress without the pushee completing first. This is not true when the waiter is waiting on a held lock - the lock must be released before the waiter may proceed. However, it is true when the waiter is waiting on a reservation - the reservation holder may release the reservation without acquiring a lock, allowing the waiter to proceed without conflict. This was the cause of deadlocks in some of the experiments I was running. The exact sequence I was running into was that: 1. key K had a lock wait-queue 2. txn A acquired a reservation for key K 3. txn B queued behind txn A, becoming the distinguished waiter 4. txn B began pushing txn A 5. txn A hit a WriteTooOld error and did not acquire a lock 6. txn A exited the lock wait-queue 7. txn A handled the WriteTooOld error, returned to key K, and queued behind txn B 8. txn B waited on txn A indefinitely in the txnWaitQueue 9. deadlock!
…ests The test was taking 55s. This drops it down to a more reasonable 4s.
f2b0bb7
to
ff898bd
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bors r+
Reviewable status: complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @sumeerbhola)
pkg/storage/concurrency/lock_table_waiter.go, line 88 at r2 (raw file):
Previously, sumeerbhola wrote…
The "therefore not appropriate" sentence followed by the "This is not true" sentence were a bit hard to read, so I tried to rephrase in a simpler form below, and added some further detail, in case you can use that to adjust the comment.
// If the lock is not held and instead has a reservation, we don't
// want to push the reservation transaction. A transaction push will
// block until the pushee transaction has either committed or
// aborted or pushed or rolled back savepoints, i.e., there is some state
// change that has happened to the transaction record that unblocks the
// pusher. It will not unblock merely because a request issued by the
// pushee transaction has completed and released a reservation. Note
// that:
// - reservations are not a guarantee that the lock will be acquired.
// - the two reasons to push do not apply to requests holding reservations:
// - competing requests compete at exactly one lock table, so there is no
// possibility of distributed deadlock due to reservations.
// - the lock table can prioritize requests based on transaction priorities (TODO(sbhola) ...)
Thanks for the suggestion. Done.
pkg/storage/concurrency/lock_table_waiter.go, line 96 at r2 (raw file):
Previously, sumeerbhola wrote…
it is likely that we can eliminate reservations from the lock table.
Ack. That will be a nice simplification.
Build succeeded |
This is a partial reversion of cockroachdb#45420. It turns out that there are cases where a reservation holder is a link in a dependency cycle. This can happen when a reservation holder txn is holding on to one reservation while blocking on a lock on another key. If any txns queued up behind the reservation did not push ~someone~, they wouldn't detect the deadlock. ``` range A . range B . txnB . txnC txnA | . | ^________ | v . v \ v [lock X: (txnA)] . [lock Y: (txnB)] [res Z: (txnC)] ``` It turns out that this segment of the dependency cycle is always local to a single concurrency manager, so it could potentially forward the push through the reservation links to shorten the cycle and prevent non-lock holders from ever being the victim of a deadlock abort. This is tricky though, so for now, we just push. To address the issue that motivated cockroachdb#45420, we perform this form of push asynchronously while continuing to listen to state transitions in the lockTable. If the pusher is unblocked (see cockroachdb#45420 for an example of when that can happen), it simply cancels the push and proceeds with navigating the lockTable.
This is a partial reversion of cockroachdb#45420. It turns out that there are cases where a reservation holder is a link in a dependency cycle. This can happen when a reservation holder txn is holding on to one reservation while blocking on a lock on another key. If any txns queued up behind the reservation did not push ~someone~, they wouldn't detect the deadlock. ``` range A . range B . txnB . txnC txnA | . | ^________ | v . v \ v [lock X: (txnA)] . [lock Y: (txnB)] [res Z: (txnC)] ``` It turns out that this segment of the dependency cycle is always local to a single concurrency manager, so it could potentially forward the push through the reservation links to shorten the cycle and prevent non-lock holders from ever being the victim of a deadlock abort. This is tricky though, so for now, we just push. To address the issue that motivated cockroachdb#45420, we perform this form of push asynchronously while continuing to listen to state transitions in the lockTable. If the pusher is unblocked (see cockroachdb#45420 for an example of when that can happen), it simply cancels the push and proceeds with navigating the lockTable.
45551: ui: standardize the display of node names in node lists r=dhartunian a=petermattis Standardize the display of node names in node lists. The nodes overview list was displaying nodes as `N<id> <ip-address>` while graphs display `<ip-address> (n<id>)`. Standardize on the latter format. A similar problem existed on the statement details page which was displaying `N<id> <ip-address> (n<id>)`. The node id was being displayed twice! Release note: None 45567: storage/concurrency: push reservation holders to detect deadlocks r=nvanbenschoten a=nvanbenschoten This is a partial reversion of #45420. It turns out that there are cases where a reservation holder is a link in a dependency cycle. This can happen when a reservation holder txn is holding on to one reservation while blocking on a lock on another key. If any txns queued up behind the reservation did not push _someone_, they wouldn't detect the deadlock. ``` range A . range B . txnB . txnC txnA | . | ^________ | v . v \ v [lock X: (txnA)] . [lock Y: (txnB)] [res Z: (txnC)] ``` It turns out that this segment of the dependency cycle is always local to a single concurrency manager, so it could potentially forward the push through the reservation links to shorten the cycle and prevent non-lock holders from ever being the victim of a deadlock abort. This is tricky though, so for now, we just push. To address the issue that motivated #45420, we perform this form of push asynchronously while continuing to listen to state transitions in the lockTable. If the pusher is unblocked (see #45420 for an example of when that can happen), it simply cancels the push and proceeds with navigating the lockTable. This PR also adds a set of datadriven deadlock tests to the concurrency manager test suite: [`testdata/concurrency_manager/deadlocks`](https://github.com/cockroachdb/cockroach/pull/45567/files#diff-5934754d5a8f1086698cdbab628ee1b5). These tests construct deadlocks due to lock ordering and request ordering and demonstrate how the deadlocks are resolved. 45568: roachtest: enable txn heartbeat loops for 1PC txns in kv/contention/nodes=4 r=nvanbenschoten a=nvanbenschoten I noticed when debugging issues in #45482 that unhandled deadlocks occasionally resolved themselves because txns would eventually time out. This was because we don't start the txn heartbeat loop for 1PC txns. In this kind of test, we want any unhandled deadlocks to be as loud as possible, so just like we set a very long txn expiration, we also enable the txn heartbeat loop for all txns, even those that we expect will be 1PC. This commit also drops the kv.lock_table.deadlock_detection_push_delay down for the test, since it's already touching this code. Co-authored-by: Peter Mattis <petermattis@gmail.com> Co-authored-by: Nathan VanBenschoten <nvanbenschoten@gmail.com>
This commit updates the lockTableWaiter to no longer push reservation transactions. The waiter now only pushes the holders of conflicting locks that are held by other transactions.
This is because a transaction push will block until the pushee transaction has either committed or aborted. It is therefore not appropriate for cases where there is still a chance for the pusher to make progress without the pushee completing first. This is not true when the waiter is waiting on a held lock - the lock must be released before the waiter may proceed. However, it is true when the waiter is waiting on a reservation - the reservation holder may release the reservation without acquiring a lock, allowing the waiter to proceed without conflict.
This was the cause of deadlocks in some of the experiments I was running. The exact sequence I was running into was that: