Skip to content

Commit be6062f

Browse files
authored
Merge pull request #763 from IntersectMBO/jdral/issue755
`ScheduledMerges`: fix a bug in `maxPhysicalDebt` and `invariant`
2 parents 6276783 + 184809a commit be6062f

File tree

2 files changed

+54
-24
lines changed

2 files changed

+54
-24
lines changed

src-prototypes/ScheduledMerges.hs

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ invariant conf@LSMConfig{..} (LSMContent _ levels ul) = do
353353
-- levelling run becomes too large and is promoted, in that case
354354
-- initially there's no merge, but it is still represented as an
355355
-- 'IncomingRun', using 'Single'. Thus there are no other resident runs.
356-
MergePolicyLevelling -> assertST $ null rs
356+
MergePolicyLevelling -> assertST $ null rs && null ls
357357
-- Runs in tiering levels usually fit that size, but they can be one
358358
-- larger, if a run has been held back (creating a (T+1)-way merge).
359359
MergePolicyTiering -> assertST $ all (\r -> runToLevelNumber MergePolicyTiering conf r `elem` [ln, ln+1]) rs
@@ -383,18 +383,19 @@ invariant conf@LSMConfig{..} (LSMContent _ levels ul) = do
383383
(_, CompletedMerge r) ->
384384
assertST $ runToLevelNumber MergePolicyLevelling conf r <= ln+1
385385

386-
-- An ongoing merge for levelling should have T incoming runs of
387-
-- the right size for the level below (or slightly larger due to
388-
-- holding back underfull runs), and 1 run from this level,
389-
-- but the run from this level can be of almost any size for the
390-
-- same reasons as above. Although if this is the first merge for
391-
-- a new level, it'll have only T runs.
386+
-- An ongoing merge for levelling should have T incoming runs of the
387+
-- right size for the level below (or slightly larger due to holding
388+
-- back underfull runs), and at most 1 run from this level. The run
389+
-- from this level can be of almost any size for the same reasons as
390+
-- above. Although if this is the first merge for a new level, it'll
391+
-- have only T runs.
392392
(_, OngoingMerge _ rs _) -> do
393393
assertST $ length rs `elem` [configSizeRatio, configSizeRatio + 1]
394394
assertST $ all (\r -> runSize r > 0) rs -- don't merge empty runs
395395
let incoming = take configSizeRatio rs
396396
let resident = drop configSizeRatio rs
397397
assertST $ all (\r -> runToLevelNumber MergePolicyTiering conf r `elem` [ln-1, ln]) incoming
398+
assertST $ length resident `elem` [0, 1]
398399
assertST $ all (\r -> runToLevelNumber MergePolicyLevelling conf r <= ln+1) resident
399400

400401
MergePolicyTiering ->
@@ -421,13 +422,19 @@ invariant conf@LSMConfig{..} (LSMContent _ levels ul) = do
421422
(_, CompletedMerge r, MergeMidLevel) ->
422423
assertST $ runToLevelNumber MergePolicyTiering conf r `elem` [ln-1, ln, ln+1]
423424

424-
-- An ongoing merge for tiering should have T incoming runs of
425-
-- the right size for the level below, and at most 1 run held back
426-
-- due to being too small (which would thus also be of the size of
427-
-- the level below).
425+
-- An ongoing merge for tiering should have T incoming runs of the
426+
-- right size for the level below (or slightly larger due to holding
427+
-- back underfull runs), and at most 1 run held back due to being
428+
-- too small (which would thus also be of the size of the level
429+
-- below).
428430
(_, OngoingMerge _ rs _, _) -> do
429-
assertST $ length rs == configSizeRatio || length rs == configSizeRatio + 1
430-
assertST $ all (\r -> runToLevelNumber MergePolicyTiering conf r == ln-1) rs
431+
assertST $ length rs `elem` [configSizeRatio, configSizeRatio + 1]
432+
assertST $ all (\r -> runSize r > 0) rs -- don't merge empty runs
433+
let incoming = take configSizeRatio rs
434+
let heldBack = drop configSizeRatio rs
435+
assertST $ all (\r -> runToLevelNumber MergePolicyTiering conf r `elem` [ln-1, ln]) incoming
436+
assertST $ length heldBack `elem` [0, 1]
437+
assertST $ all (\r -> runToLevelNumber MergePolicyTiering conf r == ln-1) heldBack
431438

432439
-- We don't make many assumptions apart from what the types already enforce.
433440
-- In particular, there are no invariants on the progress of the merges,
@@ -526,6 +533,21 @@ assert p x = Exc.assert p (const x callStack)
526533
assertST :: HasCallStack => Bool -> ST s ()
527534
assertST p = assert p $ pure ()
528535

536+
assertWithMsg :: HasCallStack => Maybe String -> a -> a
537+
assertWithMsg = assert . p
538+
where
539+
p Nothing = True
540+
p (Just msg) = error $ "Assertion failed: " <> msg
541+
542+
assertWithMsgM :: (HasCallStack, Monad m) => Maybe String -> m ()
543+
assertWithMsgM mmsg = assertWithMsg mmsg $ pure ()
544+
545+
leq :: (Show a, Ord a) => a -> a -> Maybe String
546+
leq x y = if x <= y then Nothing else Just $
547+
printf "Expected x <= y, but got %s > %s"
548+
(show x)
549+
(show y)
550+
529551
-------------------------------------------------------------------------------
530552
-- Run sizes
531553
--
@@ -1461,7 +1483,7 @@ newLevelMerge _ _ _ _ _ [r] = pure (Single r)
14611483
newLevelMerge tr conf@LSMConfig{..} level mergePolicy mergeType rs = do
14621484
assertST (length rs `elem` [configSizeRatio, configSizeRatio + 1])
14631485
mergingRun@(MergingRun _ physicalDebt _) <- newMergingRun mergeType rs
1464-
assertST (totalDebt physicalDebt <= maxPhysicalDebt)
1486+
assertWithMsgM $ leq (totalDebt physicalDebt) maxPhysicalDebt
14651487
traceWith tr MergeStartedEvent {
14661488
mergePolicy,
14671489
mergeType,
@@ -1484,15 +1506,24 @@ newLevelMerge tr conf@LSMConfig{..} level mergePolicy mergeType rs = do
14841506
-- such that we pay off the physical and nominal debts at the same time.
14851507
--
14861508
-- We can bound the worst case physical debt: this is the maximum amount of
1487-
-- steps a merge at this level could need. Note that for levelling this is
1488-
-- includes the single run in the current level.
1509+
-- steps a merge at this level could need. See the
1510+
-- 'expectedMergingRunLengths' where-clause of the 'invariant' function for
1511+
-- the full reasoning.
14891512
maxPhysicalDebt =
14901513
case mergePolicy of
14911514
MergePolicyLevelling ->
1492-
configSizeRatio * levelNumberToMaxRunSize MergePolicyTiering conf (level-1)
1515+
-- Incoming runs, which may be slightly overfull with respect to the
1516+
-- previous level
1517+
configSizeRatio * levelNumberToMaxRunSize MergePolicyTiering conf level
1518+
-- The single run that was already on this level
14931519
+ levelNumberToMaxRunSize MergePolicyLevelling conf level
14941520
MergePolicyTiering ->
1495-
length rs * levelNumberToMaxRunSize MergePolicyTiering conf (level-1)
1521+
-- Incoming runs, which may be slightly overfull with respect to the
1522+
-- previous level
1523+
configSizeRatio * levelNumberToMaxRunSize MergePolicyTiering conf level
1524+
-- Held back run that is underfull with respect to the current
1525+
-- level
1526+
+ levelNumberToMaxRunSize MergePolicyTiering conf (level - 1)
14961527

14971528
-------------------------------------------------------------------------------
14981529
-- MergingTree abstraction

src/Database/LSMTree/Internal/MergeSchedule.hs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,8 @@ supplyCredits conf deposit levels =
968968
-- supplyCreditsIncomingRun could easily return the supplied credits
969969
-- before & after, which may be useful for tracing.
970970

971+
-- | See 'maxPhysicalDebt' in 'newLevelMerge' in the 'ScheduledMerges'
972+
-- prototype.
971973
maxMergeDebt :: TableConfig -> MergePolicyForLevel -> LevelNo -> MergeDebt
972974
maxMergeDebt TableConfig {
973975
confWriteBufferAlloc = AllocNumEntries (NumEntries -> bufferSize),
@@ -977,16 +979,13 @@ maxMergeDebt TableConfig {
977979
case mergePolicy of
978980
LevelLevelling ->
979981
MergeDebt . MergeCredits $
980-
sizeRatio * maxRunSizeTiering sizeRatio bufferSize (pred ln)
982+
sizeRatio * maxRunSizeTiering sizeRatio bufferSize ln
981983
+ maxRunSizeLevelling sizeRatio bufferSize ln
982984

983985
LevelTiering ->
984986
MergeDebt . MergeCredits $
985-
maxRuns * maxRunSizeTiering sizeRatio bufferSize (pred ln)
986-
where
987-
-- We can hold back underfull runs, so sometimes the are n+1 runs,
988-
-- rather than the typical n at a tiering level (n = LSM size ratio).
989-
maxRuns = sizeRatio + 1
987+
sizeRatio * maxRunSizeTiering sizeRatio bufferSize ln
988+
+ maxRunSizeTiering sizeRatio bufferSize (pred ln)
990989

991990
-- | The nominal debt equals the minimum of credits we will supply before we
992991
-- expect the merge to complete. This is the same as the number of updates

0 commit comments

Comments
 (0)