logical: add the txnscheduler package#164544
logical: add the txnscheduler package#164544trunk-io[bot] merged 1 commit intocockroachdb:masterfrom
Conversation
|
😎 Merged successfully - details. |
629217d to
a5c6a74
Compare
msbutler
left a comment
There was a problem hiding this comment.
Looks great! left mostly clarifying questions
| // Schedule requires the lock hashes within each transaction to be unique. | ||
| // Duplicates must be filtered out at the lock synthesis phase. | ||
| func (s *Scheduler) Schedule( | ||
| transaction Transaction, scratch []hlc.Timestamp, |
There was a problem hiding this comment.
nit: rename scratch to dependenciesBuffer so the caller understands its purpose?
| for _, lock := range txn.Locks { | ||
| locks, exists := s.lockMap[lock.Hash] | ||
| if !exists { | ||
| continue |
There was a problem hiding this comment.
you could add a little docstring here to explain why this is ok, or you could leave it as an exercise for the reader if you want.
This can happen if a subsequent write overwrote the lock table entry and committed before the earlier txn. For example:
- t3 depends on t1 for key A
- t2 depends on t1 for Key A
and t3 applies before t2, which will clean up the lock table for Key A before t2 applies.
There was a problem hiding this comment.
I added a test only assertion here. This branch should never be taken because we don't remove applied transactions from the scheduler, we only ever advance the whole frontier as we need to reclaim memory.
There was a problem hiding this comment.
ah, right. got my wires crossed here.
| } | ||
| if head.readLocks == 0 { | ||
| nextHead := head.next | ||
| lt.locks[nextHead].writeLock = head.writeLock |
There was a problem hiding this comment.
i may be misreading things, but i don't understand how head could have a writeLock. Suppose we're removing read locks for txn1, we know:
- txn1 is the oldest timestamp in the lock table
- we know that newer write locks clear out older read locks for a given key when recorded
Therefore, when removing old read locks on a given key, we should not encounter any write locks, right?
There was a problem hiding this comment.
Removed this and added a note.
| func BenchmarkScheduler_Schedule(b *testing.B) { | ||
| for _, txnSize := range []int{1, 10, 50, 1000} { | ||
| b.Run(fmt.Sprintf("size=%d", txnSize), func(b *testing.B) { | ||
| transactions := createRandomTransactions(b.N/txnSize, txnSize, 10000, 0.8) |
There was a problem hiding this comment.
what have you been setting N to in your benchmarks? I hope is much larger than 10,000 the size of the keyspace, to ensure the benchmark exercises key collisions/long read lock chains.
There was a problem hiding this comment.
I let go pick this, but it usually ends up being several million.
There was a problem hiding this comment.
It ends up being several million.
| time int | ||
| readLocks []int | ||
| writeLocks []int | ||
| dependsOn []int |
There was a problem hiding this comment.
nit: rename to expectedDependencies and expectedHorizon to make it clearer these validation fields.
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestScheduler(t *testing.T) { |
There was a problem hiding this comment.
it would be nice to build some sort of randomized scheduler test based on some DAG with read/write dependencies. This would ensure we have good coverage over lock_list.go. Not necessary for this PR.
There was a problem hiding this comment.
Sorry, not a DAG: you could construct a 2D grid of discrete times and keys. Each entry in the grid is read lock, write lock or empty. You Could easily validate the schedule output with this I think.
There was a problem hiding this comment.
I added a random test that uses a 2d oracle.
There was a problem hiding this comment.
test looks great! If you haven't already done so, it could be worth sanity checking that the oracle deps look reasonable when table size is much lower.
| for range rng.Intn(10) + 1 { | ||
| txn := makeTxn(false) | ||
|
|
||
| locks, horizon := scheduler.Schedule(txn, nil) |
There was a problem hiding this comment.
nit: rename locks, dependencies
d217687 to
be7dd80
Compare
msbutler
left a comment
There was a problem hiding this comment.
LGTM! just a few tiny nits
| "github.com/cockroachdb/cockroach/pkg/util/ring" | ||
| ) | ||
|
|
||
| // NewScheduler constructs a scheduler that can track at least `size` locks. |
There was a problem hiding this comment.
I updated this comment:
-// NewScheduler constructs a scheduler that can track at least `size` locks.
+// NewScheduler constructs a scheduler that can track at most `size` lock table
+// entries. Each lock table entry can track one write lock and up to 8 read
+// locks.
| // | ||
| // | | Read Lock | Write Lock | | ||
| // |------------|-------------|------------| | ||
| // | Read Lock | Dependency | Dependency | |
There was a problem hiding this comment.
no dependency with read x read
| for _, lock := range txn.Locks { | ||
| locks, exists := s.lockMap[lock.Hash] | ||
| if !exists { | ||
| continue |
There was a problem hiding this comment.
ah, right. got my wires crossed here.
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestScheduler(t *testing.T) { |
There was a problem hiding this comment.
test looks great! If you haven't already done so, it could be worth sanity checking that the oracle deps look reasonable when table size is much lower.
be7dd80 to
ff7b772
Compare
The txnscheduler takes transactions with replication locks and transforms them into a sequence of transactions with explicit cross-transaction dependency information. Release note: none Epic: CRDB-57649
|
Here's an example of what a small oracle table looks like: I spot checked a few of these and they look correct. |
ff7b772 to
4bd3f09
Compare
|
Thanks for the review! /trunk merge |
The txnscheduler takes transactions with replication locks and transforms them into a sequence of transactions with explicit cross-transaction dependency information.
Release note: none
Epic: CRDB-57649