Safe Haskell | Safe-Inferred |
---|---|
Language | GHC2021 |
ScheduledMerges
Contents
Description
A prototype of an LSM with explicitly scheduled incremental merges.
The scheduled incremental merges is about ensuring that the merging work (CPU and I/O) can be spread out over time evenly. This also means the LSM update operations have worst case complexity rather than amortised complexity, because they do a fixed amount of merging work each.
Another thing this prototype demonstrates is a design for duplicating tables and sharing ongoing incremental merges.
Finally, it demonstrates a design for table unions, including a representation for in-progress merging trees.
The merging policy that this prototype uses is power 4 "lazy levelling". Power 4 means each level is 4 times bigger than the previous level. Lazy levelling means we use tiering for every level except the last level which uses levelling. Though note that the first level always uses tiering, even if the first level is also the last level. This is to simplify flushing the write buffer: if we used levelling on the first level we would need a code path for merging the write buffer into the first level.
Synopsis
- data LSM s
- newtype Key = K Int
- newtype Value = V Int
- resolveValue :: Value -> Value -> Value
- newtype Blob = B Int
- new :: ST s (LSM s)
- data LookupResult v b
- lookup :: LSM s -> Key -> ST s (LookupResult Value Blob)
- lookups :: LSM s -> [Key] -> ST s [LookupResult Value Blob]
- type Op = Update Value Blob
- data Update v b
- update :: Tracer (ST s) Event -> LSM s -> Key -> Op -> ST s ()
- updates :: Tracer (ST s) Event -> LSM s -> [(Key, Op)] -> ST s ()
- insert :: Tracer (ST s) Event -> LSM s -> Key -> Value -> Maybe Blob -> ST s ()
- inserts :: Tracer (ST s) Event -> LSM s -> [(Key, Value, Maybe Blob)] -> ST s ()
- delete :: Tracer (ST s) Event -> LSM s -> Key -> ST s ()
- deletes :: Tracer (ST s) Event -> LSM s -> [Key] -> ST s ()
- mupsert :: Tracer (ST s) Event -> LSM s -> Key -> Value -> ST s ()
- mupserts :: Tracer (ST s) Event -> LSM s -> [(Key, Value)] -> ST s ()
- supplyMergeCredits :: LSM s -> NominalCredit -> ST s ()
- duplicate :: LSM s -> ST s (LSM s)
- unions :: [LSM s] -> ST s (LSM s)
- type Credit = Int
- type Debt = Int
- remainingUnionDebt :: LSM s -> ST s UnionDebt
- supplyUnionCredits :: LSM s -> UnionCredits -> ST s UnionCredits
- data MTree r
- = MLeaf r
- | MNode TreeMergeType [MTree r]
- logicalValue :: LSM s -> ST s (Map Key (Value, Maybe Blob))
- type Representation = (Run, [LevelRepresentation], Maybe (MTree Run))
- dumpRepresentation :: LSM s -> ST s Representation
- representationShape :: Representation -> (Int, [([Int], [Int])], Maybe (MTree Int))
- type Event = EventAt EventDetail
- data EventAt e = EventAt {
- eventAtStep :: Counter
- eventAtLevel :: Int
- eventDetail :: e
- data EventDetail
- = AddLevelEvent
- | AddRunEvent {
- runsAtLevel :: Int
- | MergeStartedEvent { }
- | MergeCompletedEvent { }
- newtype MergingTree s = MergingTree (STRef s (MergingTreeState s))
- data MergingTreeState s
- data PendingMerge s
- = PendingLevelMerge ![PreExistingRun s] !(Maybe (MergingTree s))
- | PendingUnionMerge ![MergingTree s]
- data PreExistingRun s
- data MergingRun t s = MergingRun !t !MergeDebt !(STRef s MergingRunState)
- data MergingRunState
- = CompletedMerge !Run
- | OngoingMerge !MergeCredit ![Run] Run
- data MergePolicy
- class Show t => IsMergeType t where
- isLastLevel :: t -> Bool
- isUnion :: t -> Bool
- data TreeMergeType
- data LevelMergeType
- data MergeCredit = MergeCredit {
- spentCredits :: !Credit
- unspentCredits :: !Credit
- newtype MergeDebt = MergeDebt {}
- newtype NominalCredit = NominalCredit Credit
- newtype NominalDebt = NominalDebt Credit
- type Run = Map Key Op
- runSize :: Run -> Int
- newtype UnionCredits = UnionCredits Credit
- supplyCreditsMergingTree :: Credit -> MergingTree s -> ST s Credit
- newtype UnionDebt = UnionDebt Debt
- remainingDebtMergingTree :: MergingTree s -> ST s (Debt, Size)
- mergek :: IsMergeType t => t -> [Run] -> Run
- mergeBatchSize :: Int
- type Invariant s = ExceptT String (ST s)
- evalInvariant :: Invariant s a -> ST s (Either String a)
- treeInvariant :: MergingTree s -> Invariant s ()
- mergeDebtInvariant :: MergeDebt -> MergeCredit -> Bool
Main API
data LookupResult v b Source #
Instances
(Show v, Show b) => Show (LookupResult v b) Source # | |
Defined in ScheduledMerges Methods showsPrec :: Int -> LookupResult v b -> ShowS # show :: LookupResult v b -> String # showList :: [LookupResult v b] -> ShowS # | |
(Eq v, Eq b) => Eq (LookupResult v b) Source # | |
Defined in ScheduledMerges Methods (==) :: LookupResult v b -> LookupResult v b -> Bool # (/=) :: LookupResult v b -> LookupResult v b -> Bool # |
supplyMergeCredits :: LSM s -> NominalCredit -> ST s () Source #
unions :: [LSM s] -> ST s (LSM s) Source #
Similar to Data.Map.unionWith
.
A call to union
itself is not expensive, as the input tables are not
immediately merged. Instead, it creates a representation of an in-progress
merge that can be performed incrementally (somewhat similar to a thunk).
The more merge work remains, the more expensive are lookups on the table.
Credits for keeping track of merge progress. These credits correspond directly to merge steps performed.
We also call these "physical" credits (since they correspond to steps
done), and as opposed to "nominal" credits in NominalCredit
and
NominalDebt
.
remainingUnionDebt :: LSM s -> ST s UnionDebt Source #
Return the current union debt. This debt can be reduced until it is paid
off using supplyUnionCredits
.
supplyUnionCredits :: LSM s -> UnionCredits -> ST s UnionCredits Source #
Supply union credits to reduce union debt.
Supplying union credits leads to union merging work being performed in
batches. This reduces the union debt returned by remainingUnionDebt
. Union
debt will be reduced by at least the number of supplied union credits. It
is therefore advisable to query remainingUnionDebt
every once in a while to
see what the current debt is.
This function returns any surplus of union credits as leftover credits when a union has finished. In particular, if the returned number of credits is non-negative, then the union is finished.
Test and trace
Constructors
MLeaf r | |
MNode TreeMergeType [MTree r] |
Instances
Foldable MTree Source # | |
Defined in ScheduledMerges Methods fold :: Monoid m => MTree m -> m # foldMap :: Monoid m => (a -> m) -> MTree a -> m # foldMap' :: Monoid m => (a -> m) -> MTree a -> m # foldr :: (a -> b -> b) -> b -> MTree a -> b # foldr' :: (a -> b -> b) -> b -> MTree a -> b # foldl :: (b -> a -> b) -> b -> MTree a -> b # foldl' :: (b -> a -> b) -> b -> MTree a -> b # foldr1 :: (a -> a -> a) -> MTree a -> a # foldl1 :: (a -> a -> a) -> MTree a -> a # elem :: Eq a => a -> MTree a -> Bool # maximum :: Ord a => MTree a -> a # minimum :: Ord a => MTree a -> a # | |
Functor MTree Source # | |
Show r => Show (MTree r) Source # | |
Eq r => Eq (MTree r) Source # | |
dumpRepresentation :: LSM s -> ST s Representation Source #
representationShape :: Representation -> (Int, [([Int], [Int])], Maybe (MTree Int)) Source #
type Event = EventAt EventDetail Source #
Constructors
EventAt | |
Fields
|
data EventDetail Source #
Constructors
AddLevelEvent | |
AddRunEvent | |
Fields
| |
MergeStartedEvent | |
Fields
| |
MergeCompletedEvent | |
Fields |
Instances
Show EventDetail Source # | |
Defined in ScheduledMerges Methods showsPrec :: Int -> EventDetail -> ShowS # show :: EventDetail -> String # showList :: [EventDetail] -> ShowS # |
newtype MergingTree s Source #
A "merging tree" is a mutable representation of an incremental tree-shaped nested merge. This allows to represent union merges of entire tables, each of which itself first need to be merged to become a single run.
Trees have to support arbitrarily deep nesting, since each input to union
might already contain an in-progress merging tree (which then becomes shared
between multiple tables).
See Note [Table Unions].
Constructors
MergingTree (STRef s (MergingTreeState s)) |
data MergingTreeState s Source #
Constructors
CompletedTreeMerge !Run | |
OngoingTreeMerge !(MergingRun TreeMergeType s) | Reuses MergingRun (with its STRef) to allow sharing existing merges. |
PendingTreeMerge !(PendingMerge s) |
data PendingMerge s Source #
A merge that is waiting for its inputs to complete.
The inputs can themselves be MergingTree
s (with its STRef) to allow sharing
existing unions.
Constructors
PendingLevelMerge ![PreExistingRun s] !(Maybe (MergingTree s)) | The inputs are entire content of a table, i.e. its (merging) runs and finally a union merge (if that table already contained a union). |
PendingUnionMerge ![MergingTree s] | Each input is a level merge of the entire content of a table. |
data PreExistingRun s Source #
This is much like an IncomingRun
, and are created from them, but contain
only the essential information needed in a PendingLevelMerge
.
Constructors
PreExistingRun !Run | |
PreExistingMergingRun !(MergingRun LevelMergeType s) |
data MergingRun t s Source #
A "merging run" is a mutable representation of an incremental merge. It is also a unit of sharing between duplicated tables.
Constructors
MergingRun !t !MergeDebt !(STRef s MergingRunState) |
data MergingRunState Source #
Constructors
CompletedMerge !Run | |
OngoingMerge | |
Fields
|
data MergePolicy Source #
The merge policy for a LSM level can be either tiering or levelling.
In this design we use levelling for the last level, and tiering for
all other levels. The first level always uses tiering however, even if
it's also the last level. So MergePolicy
and LevelMergeType
are
orthogonal, all combinations are possible.
Constructors
MergePolicyTiering | |
MergePolicyLevelling |
Instances
Show MergePolicy Source # | |
Defined in ScheduledMerges Methods showsPrec :: Int -> MergePolicy -> ShowS # show :: MergePolicy -> String # showList :: [MergePolicy] -> ShowS # | |
Eq MergePolicy Source # | |
Defined in ScheduledMerges |
class Show t => IsMergeType t where Source #
Merges can exist in different parts of the LSM, each with different options for the exact merge operation performed.
Instances
IsMergeType LevelMergeType Source # | |
Defined in ScheduledMerges | |
IsMergeType TreeMergeType Source # | |
Defined in ScheduledMerges |
data TreeMergeType Source #
Different types of merges created as part of the merging tree.
Union merges follow the semantics of Data.Map.unionWith (<>)
. Since
the input runs are semantically treated like Data.Map
s, deletes are ignored
and inserts act like mupserts, so they need to be merged monoidally using
resolveValue
.
Trees can only exist on the union level, which is the last. Therefore, node merges can always drop deletes.
Constructors
MergeLevel | |
MergeUnion |
Instances
Arbitrary TreeMergeType Source # | |
Defined in ScheduledMerges | |
Show TreeMergeType Source # | |
Defined in ScheduledMerges Methods showsPrec :: Int -> TreeMergeType -> ShowS # show :: TreeMergeType -> String # showList :: [TreeMergeType] -> ShowS # | |
Eq TreeMergeType Source # | |
Defined in ScheduledMerges Methods (==) :: TreeMergeType -> TreeMergeType -> Bool # (/=) :: TreeMergeType -> TreeMergeType -> Bool # | |
IsMergeType TreeMergeType Source # | |
Defined in ScheduledMerges |
data LevelMergeType Source #
Different types of merges created as part of a regular (non-union) level.
A last level merge behaves differently from a mid-level merge: last level
merges can actually remove delete operations, whereas mid-level merges must
preserve them. This is orthogonal to the MergePolicy
.
Constructors
MergeMidLevel | |
MergeLastLevel |
Instances
Arbitrary LevelMergeType Source # | |
Defined in ScheduledMerges Methods arbitrary :: Gen LevelMergeType Source # shrink :: LevelMergeType -> [LevelMergeType] Source # | |
Show LevelMergeType Source # | |
Defined in ScheduledMerges Methods showsPrec :: Int -> LevelMergeType -> ShowS # show :: LevelMergeType -> String # showList :: [LevelMergeType] -> ShowS # | |
Eq LevelMergeType Source # | |
Defined in ScheduledMerges Methods (==) :: LevelMergeType -> LevelMergeType -> Bool # (/=) :: LevelMergeType -> LevelMergeType -> Bool # | |
IsMergeType LevelMergeType Source # | |
Defined in ScheduledMerges |
data MergeCredit Source #
Constructors
MergeCredit | |
Fields
|
Instances
Show MergeCredit Source # | |
Defined in ScheduledMerges Methods showsPrec :: Int -> MergeCredit -> ShowS # show :: MergeCredit -> String # showList :: [MergeCredit] -> ShowS # |
newtype NominalCredit Source #
Nominal credit is the credit supplied to each level as we insert update operations, one credit per update operation inserted.
Nominal credit must be supplied up to the NominalDebt
to ensure the merge
is complete.
Nominal credits are a similar order of magnitude to physical credits (see
Credit
) but not the same, and we have to scale linearly to convert between
them. Physical credits are the actual number of inputs to the merge, which
may be somewhat more or somewhat less than the number of update operations
we will insert before we need the merge to be complete.
Constructors
NominalCredit Credit |
Instances
Show NominalCredit Source # | |
Defined in ScheduledMerges Methods showsPrec :: Int -> NominalCredit -> ShowS # show :: NominalCredit -> String # showList :: [NominalCredit] -> ShowS # |
newtype NominalDebt Source #
The nominal debt for a merging run is the worst case (minimum) number of update operations we expect to insert before we expect the merge to be complete.
We require that an equal amount of nominal credit is supplied before we can expect a merge to be complete.
We scale linearly to convert nominal credits to physical credits, such that the nominal debt and physical debt are both considered "100%", and so that both debts are paid off at exactly the same time.
Constructors
NominalDebt Credit |
Instances
Show NominalDebt Source # | |
Defined in ScheduledMerges Methods showsPrec :: Int -> NominalDebt -> ShowS # show :: NominalDebt -> String # showList :: [NominalDebt] -> ShowS # |
newtype UnionCredits Source #
Credits are used to pay off UnionDebt
, completing a union
in the
process.
A union credit corresponds to a single merging step being performed.
Constructors
UnionCredits Credit |
Instances
supplyCreditsMergingTree :: Credit -> MergingTree s -> ST s Credit Source #
The current upper bound on the number of UnionCredits
that have to be
supplied before a union
is completed.
The union debt is the number of merging steps that need to be performed /at
most/ until the delayed work of performing a union
is completed. This
includes the cost of completing merges that were part of the union's input
tables.
Instances
Num UnionDebt Source # | |
Defined in ScheduledMerges | |
Show UnionDebt Source # | |
Eq UnionDebt Source # | |
Ord UnionDebt Source # | |
remainingDebtMergingTree :: MergingTree s -> ST s (Debt, Size) Source #
mergeBatchSize :: Int Source #
Invariants
treeInvariant :: MergingTree s -> Invariant s () Source #
mergeDebtInvariant :: MergeDebt -> MergeCredit -> Bool Source #