Skip to content

Commit 68cd72f

Browse files
committed
fix(incrservice): preserve terminal uint64 allocation
1 parent ed57537 commit 68cd72f

4 files changed

Lines changed: 117 additions & 15 deletions

File tree

pkg/incrservice/column_cache.go

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,17 @@ var (
3939

4040
type columnCache struct {
4141
sync.RWMutex
42-
logger *log.MOLogger
43-
col AutoColumn
44-
cfg Config
45-
ranges *ranges
46-
allocator valueAllocator
47-
allocating bool
48-
allocatingC chan error
49-
overflow bool
42+
logger *log.MOLogger
43+
col AutoColumn
44+
cfg Config
45+
ranges *ranges
46+
allocator valueAllocator
47+
allocating bool
48+
allocatingC chan error
49+
overflow bool
50+
terminal bool
51+
terminalValue uint64
52+
terminalTS timestamp.Timestamp
5053
// For the load scenario, if the machine is good enough, there will be very many goroutines to
5154
// concurrently fetch the value of the self-increasing column, which will immediately trigger
5255
// the cache of the self-increasing column to be insufficient and thus go to the store to allocate
@@ -94,7 +97,13 @@ func (col *columnCache) current(ctx context.Context) (uint64, error) {
9497
if err := col.waitPrevAllocatingLocked(ctx); err != nil {
9598
return 0, err
9699
}
97-
return col.ranges.current(), nil
100+
if v := col.ranges.current(); v != 0 {
101+
return v, nil
102+
}
103+
if col.terminal {
104+
return col.terminalValue, nil
105+
}
106+
return 0, nil
98107
}
99108

100109
func (col *columnCache) insertAutoValues(
@@ -271,8 +280,18 @@ func (col *columnCache) updateTo(
271280
col.Lock()
272281

273282
contains := col.ranges.updateTo(manualValue)
283+
if col.terminal && manualValue >= col.terminalValue {
284+
col.terminal = false
285+
col.terminalValue = 0
286+
col.terminalTS = timestamp.Timestamp{}
287+
col.overflow = true
288+
contains = true
289+
}
274290
// mark col next() is overflow
275291
if manualValue == math.MaxUint64 {
292+
col.terminal = false
293+
col.terminalValue = 0
294+
col.terminalTS = timestamp.Timestamp{}
276295
col.overflow = true
277296
}
278297
col.Unlock()
@@ -311,7 +330,7 @@ func (col *columnCache) applyAutoValues(
311330
return true, nil
312331
}
313332

314-
if col.ranges.empty() {
333+
if col.ranges.empty() && !col.terminal {
315334
if err := col.allocateLocked(ctx, tableID, rows, cul, txnOp); err != nil {
316335
return false, err
317336
}
@@ -336,7 +355,15 @@ func (col *columnCache) applyAutoValues(
336355
if overflow {
337356
return apply(i, 0)
338357
}
339-
if err := apply(i, col.ranges.next()); err != nil {
358+
value := col.ranges.next()
359+
if value == 0 && col.terminal {
360+
value = col.terminalValue
361+
col.terminal = false
362+
col.terminalValue = 0
363+
col.terminalTS = timestamp.Timestamp{}
364+
col.overflow = true
365+
}
366+
if err := apply(i, value); err != nil {
340367
return err
341368
}
342369
}
@@ -354,7 +381,7 @@ func (col *columnCache) preAllocate(
354381
return
355382
}
356383

357-
if col.ranges.left() >= count {
384+
if col.ranges.left() >= count || col.terminal {
358385
return
359386
}
360387

@@ -435,7 +462,7 @@ func (col *columnCache) allocateLocked(
435462
func (col *columnCache) maybeAllocate(ctx context.Context, tableID uint64, txnOp client.TxnOperator) error {
436463
col.Lock()
437464
committed := col.committed
438-
low := col.ranges.left() <= col.cfg.LowCapacity
465+
low := col.ranges.left() <= col.cfg.LowCapacity && !col.terminal
439466
retired := col.retired
440467
col.Unlock()
441468
if low && committed && !retired {
@@ -480,13 +507,30 @@ func (col *columnCache) applyAllocateLocked(
480507
}
481508
}
482509

483-
if to > from {
510+
// A wrapped exclusive upper bound means the allocation reached the end of
511+
// uint64. Keep its final value separately because max+step is not representable.
512+
if to < from {
513+
terminalValue := to - col.col.Step
514+
if from < terminalValue {
515+
col.ranges.addWithTimestamp(from, terminalValue, allocateAt)
516+
}
517+
col.terminal = true
518+
col.terminalValue = terminalValue
519+
col.terminalTS = allocateAt
520+
} else if to > from {
484521
col.ranges.addWithTimestamp(from, to, allocateAt)
485522
}
486523
close(col.allocatingC)
487524
col.allocating = false
488525
}
489526

527+
func (col *columnCache) oldestAllocateAtLocked() timestamp.Timestamp {
528+
if !col.ranges.empty() {
529+
return col.ranges.oldestAllocateAt()
530+
}
531+
return col.terminalTS
532+
}
533+
490534
func (col *columnCache) waitPrevAllocatingLocked(ctx context.Context) error {
491535
for {
492536
if !col.allocating {

pkg/incrservice/column_cache_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,3 +625,32 @@ func TestLastAllocateAtEmptyInitial(t *testing.T) {
625625
},
626626
)
627627
}
628+
629+
func TestTerminalValueRetainsAllocateTimestamp(t *testing.T) {
630+
ts := timestamp.Timestamp{PhysicalTime: 1000, LogicalTime: 1}
631+
cc := &columnCache{
632+
col: AutoColumn{Step: 1},
633+
ranges: &ranges{step: 1},
634+
allocatingC: make(chan error, 1),
635+
}
636+
637+
cc.applyAllocateLocked(math.MaxUint64, 0, ts, nil)
638+
639+
require.True(t, cc.terminal)
640+
require.Equal(t, uint64(math.MaxUint64), cc.terminalValue)
641+
require.Equal(t, ts, cc.oldestAllocateAtLocked())
642+
}
643+
644+
func TestWrappedAllocationPreservesAllTerminalValues(t *testing.T) {
645+
cc := &columnCache{
646+
col: AutoColumn{Step: 1},
647+
ranges: &ranges{step: 1},
648+
allocatingC: make(chan error, 1),
649+
}
650+
651+
cc.applyAllocateLocked(math.MaxUint64-1, 0, timestamp.Timestamp{}, nil)
652+
653+
require.Equal(t, uint64(math.MaxUint64-1), cc.ranges.next())
654+
require.True(t, cc.terminal)
655+
require.Equal(t, uint64(math.MaxUint64), cc.terminalValue)
656+
}

pkg/incrservice/service_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,35 @@ func TestSetOffset(t *testing.T) {
403403
})
404404
}
405405

406+
func TestSetOffsetPreservesMaxUint64TerminalValue(t *testing.T) {
407+
runServiceTests(
408+
t,
409+
1,
410+
func(
411+
ctx context.Context,
412+
ss []*service,
413+
ops []client.TxnOperator,
414+
) {
415+
s := ss[0]
416+
def := newTestTableDef(1)
417+
require.NoError(t, s.Create(ctx, 0, def, ops[0]))
418+
require.NoError(t, ops[0].Commit(ctx))
419+
420+
require.NoError(t, s.SetOffset(ctx, 0, def[0].ColName, math.MaxUint64-1, nil))
421+
422+
input := newTestVector[uint64](1, types.New(types.T_uint64, 0, 0), nil, nil)
423+
last, err := s.InsertValues(ctx, 0, 0, nil, []*vector.Vector{input}, 1, 0)
424+
require.NoError(t, err)
425+
require.Equal(t, uint64(math.MaxUint64), last)
426+
require.Equal(t, uint64(math.MaxUint64), vector.MustFixedColWithTypeCheck[uint64](input)[0])
427+
428+
input = newTestVector[uint64](1, types.New(types.T_uint64, 0, 0), nil, nil)
429+
_, err = s.InsertValues(ctx, 0, 0, nil, []*vector.Vector{input}, 1, 0)
430+
require.Error(t, err)
431+
require.True(t, moerr.IsMoErrCode(err, moerr.ErrOutOfRange))
432+
})
433+
}
434+
406435
func TestSetOffsetDoesNotReadCommittedColumns(t *testing.T) {
407436
ctx := defines.AttachAccountId(context.Background(), catalog.System_Account)
408437
store := &setOffsetStore{t: t}

pkg/incrservice/table_cache.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func (c *tableCache) getLastAllocateTS(_ context.Context, colName string) (times
115115
panic("column cache should not be nil, " + colName)
116116
}
117117
cc.RLock()
118-
ts := cc.ranges.oldestAllocateAt()
118+
ts := cc.oldestAllocateAtLocked()
119119
cc.RUnlock()
120120
// Log a warning if the allocation timestamp is empty, which may cause PrimaryKeysMayBeUpserted
121121
// to scan a very large time range and impact performance.

0 commit comments

Comments
 (0)