Skip to content

Commit c8e3af6

Browse files
authored
Merge pull request #600 from IntersectMBO/jdral/test-union-credit-debt
QLS: add `RemainingUnionDebt` and `SupplyUnionCredits` actions
2 parents 73106af + 9b71bab commit c8e3af6

File tree

8 files changed

+375
-101
lines changed

8 files changed

+375
-101
lines changed

src/Database/LSMTree/Common.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,11 @@ instance Show (BlobRef m b) where
275275
-- includes the cost of completing merges that were part of the union's input
276276
-- tables.
277277
newtype UnionDebt = UnionDebt Int
278-
deriving stock (Show, Eq)
278+
deriving stock (Show, Eq, Ord)
279279

280280
-- | Credits are used to pay off 'UnionDebt', completing a @union@ in the
281281
-- process.
282282
--
283283
-- A union credit corresponds to a single merging step being performed.
284284
newtype UnionCredits = UnionCredits Int
285-
deriving stock (Show, Eq)
285+
deriving stock (Show, Eq, Ord)

src/Database/LSMTree/Internal.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,7 +1549,7 @@ matchSessions = \(t :| ts) ->
15491549

15501550
-- | See 'Database.LSMTree.Normal.UnionDebt'.
15511551
newtype UnionDebt = UnionDebt Int
1552-
deriving newtype (Show, Eq)
1552+
deriving newtype (Show, Eq, Ord, Num)
15531553

15541554
{-# SPECIALISE remainingUnionDebt :: Table IO h -> IO UnionDebt #-}
15551555
-- | See 'Database.LSMTree.Normal.remainingUnionDebt'.
@@ -1562,7 +1562,7 @@ remainingUnionDebt t = do
15621562

15631563
-- | See 'Database.LSMTree.Normal.UnionCredits'.
15641564
newtype UnionCredits = UnionCredits Int
1565-
deriving newtype (Show, Eq)
1565+
deriving newtype (Show, Eq, Ord, Num)
15661566

15671567
{-# SPECIALISE supplyUnionCredits :: Table IO h -> UnionCredits -> IO UnionCredits #-}
15681568
-- | See 'Database.LSMTree.Normal.supplyUnionCredits'.

test/Database/LSMTree/Class.hs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,21 @@ class (IsSession (Session h)) => IsTable h where
185185
=> NonEmpty (h m k v b)
186186
-> m (h m k v b)
187187

188+
remainingUnionDebt ::
189+
( IOLike m
190+
, C k v b
191+
)
192+
=> h m k v b
193+
-> m UnionDebt
194+
195+
supplyUnionCredits ::
196+
( IOLike m
197+
, C k v b
198+
)
199+
=> h m k v b
200+
-> UnionCredits
201+
-> m UnionCredits
202+
188203
withTableNew :: forall h m k v b a.
189204
(IOLike m, IsTable h, C k v b)
190205
=> Session h m
@@ -277,5 +292,8 @@ instance IsTable R.Table where
277292
openSnapshot sesh snap = R.openSnapshot sesh R.configNoOverride snap
278293

279294
duplicate = R.duplicate
295+
280296
union = R.union
281297
unions = R.unions
298+
remainingUnionDebt = R.remainingUnionDebt
299+
supplyUnionCredits = R.supplyUnionCredits

test/Database/LSMTree/Class/Common.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Data.Typeable (Typeable)
1515
import Database.LSMTree (ResolveValue)
1616
import Database.LSMTree.Common as Types (IOLike, Range (..),
1717
SerialiseKey, SerialiseValue, SnapshotLabel (..),
18-
SnapshotName)
18+
SnapshotName, UnionCredits (..), UnionDebt (..))
1919
import qualified Database.LSMTree.Common as R
2020
import System.FS.API (FsPath, HasFS)
2121
import System.FS.BlockIO.API (HasBlockIO)

test/Database/LSMTree/Model/IO.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ instance Class.IsTable Table where
9292

9393
union (Table s1 t1) (Table _s2 t2) =
9494
Table s1 <$> runInOpenSession s1 (Model.union Model.getResolve t1 t2)
95-
9695
unions ts =
9796
Table s <$> runInOpenSession s (Model.unions Model.getResolve (fmap thTable ts))
9897
where
9998
Table s _ = NE.head ts
99+
remainingUnionDebt (Table s t) = runInOpenSession s (Model.remainingUnionDebt t)
100+
supplyUnionCredits (Table s t) credits = runInOpenSession s (Model.supplyUnionCredits t credits)

test/Database/LSMTree/Model/Session.hs

Lines changed: 108 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module Database.LSMTree.Model.Session (
1919
, withSomeTable
2020
, TableID
2121
, tableID
22+
, isUnionDescendant
2223
, Model.size
2324
-- ** Constraints
2425
, C
@@ -76,8 +77,13 @@ module Database.LSMTree.Model.Session (
7677
-- * Multiple writable tables
7778
, duplicate
7879
-- * Table union
80+
, IsUnionDescendant (..)
7981
, union
8082
, unions
83+
, UnionDebt (..)
84+
, remainingUnionDebt
85+
, UnionCredits (..)
86+
, supplyUnionCredits
8187
) where
8288

8389
import Control.Monad (forM, when)
@@ -95,7 +101,8 @@ import Data.Maybe (fromJust)
95101
import qualified Data.Vector as V
96102
import Data.Word
97103
import Database.LSMTree.Common (SerialiseKey (..),
98-
SerialiseValue (..), SnapshotLabel, SnapshotName)
104+
SerialiseValue (..), SnapshotLabel, SnapshotName,
105+
UnionCredits (..), UnionDebt (..))
99106
import Database.LSMTree.Model.Table (LookupResult (..),
100107
QueryResult (..), Range (..), ResolveSerialisedValue (..),
101108
Update (..), getResolve, noResolve)
@@ -125,6 +132,10 @@ initModel = Model {
125132
-- | We conservatively model blob reference invalidation: each update after
126133
-- acquiring a blob reference will invalidate it. We use 'UpdateCounter' to
127134
-- track updates.
135+
--
136+
-- Supplying union credits is also considered an update, though this can only
137+
-- invalidate a blob reference that is associated with a (descendant of a) union
138+
-- table.
128139
newtype UpdateCounter = UpdateCounter Word64
129140
deriving stock (Show, Eq, Ord)
130141
deriving newtype (Num)
@@ -300,8 +311,9 @@ type TableID = Int
300311
--
301312

302313
data Table k v b = Table {
303-
tableID :: TableID
304-
, config :: TableConfig
314+
tableID :: TableID
315+
, config :: TableConfig
316+
, isUnionDescendant :: IsUnionDescendant
305317
}
306318
deriving stock Show
307319

@@ -319,7 +331,7 @@ new ::
319331
forall k v b m. (MonadState Model m, C k v b)
320332
=> TableConfig
321333
-> m (Table k v b)
322-
new config = newTableWith config Model.empty
334+
new config = newTableWith config IsNotUnionDescendant Model.empty
323335

324336
-- |
325337
--
@@ -354,12 +366,14 @@ guardTableIsOpen Table{..} =
354366
newTableWith ::
355367
(MonadState Model m, C k v b)
356368
=> TableConfig
369+
-> IsUnionDescendant
357370
-> Model.Table k v b
358371
-> m (Table k v b)
359-
newTableWith config tbl = state $ \Model{..} ->
372+
newTableWith config isUnionDescendant tbl = state $ \Model{..} ->
360373
let table = Table {
361374
tableID = nextID
362375
, config
376+
, isUnionDescendant
363377
}
364378
someTable = toSomeTable tbl
365379
tables' = Map.insert nextID (0, someTable) tables
@@ -549,10 +563,11 @@ invalidateBlobRefs Table{..} = do
549563
-------------------------------------------------------------------------------}
550564

551565
data Snapshot = Snapshot
552-
{ snapshotConfig :: TableConfig
553-
, snapshotLabel :: SnapshotLabel
554-
, snapshotTable :: SomeTable
555-
, snapshotCorrupted :: Bool
566+
{ snapshotConfig :: TableConfig
567+
, snapshotLabel :: SnapshotLabel
568+
, snapshotTable :: SomeTable
569+
, snapshotIsUnionDescendant :: IsUnionDescendant
570+
, snapshotCorrupted :: Bool
556571
}
557572
deriving stock Show
558573

@@ -570,8 +585,13 @@ createSnapshot label name t@Table{..} = do
570585
snaps <- gets snapshots
571586
when (Map.member name snaps) $
572587
throwError ErrSnapshotExists
588+
let snap =
589+
Snapshot
590+
config label
591+
(toSomeTable $ Model.snapshot table)
592+
isUnionDescendant False
573593
modify (\m -> m {
574-
snapshots = Map.insert name (Snapshot config label (toSomeTable $ Model.snapshot table) False) (snapshots m)
594+
snapshots = Map.insert name snap (snapshots m)
575595
})
576596

577597
openSnapshot ::
@@ -588,7 +608,7 @@ openSnapshot label name = do
588608
case Map.lookup name snaps of
589609
Nothing ->
590610
throwError ErrSnapshotDoesNotExist
591-
Just (Snapshot conf label' tbl corrupted) -> do
611+
Just (Snapshot conf label' tbl snapshotIsUnion corrupted) -> do
592612
when corrupted $
593613
throwError DefaultErrSnapshotCorrupted
594614
when (label /= label') $
@@ -603,7 +623,7 @@ openSnapshot label name = do
603623
-- test setup, and so we use @error@ instead of @throwError@.
604624
error "openSnapshot: snapshot opened at wrong type"
605625
Just table' ->
606-
newTableWith conf table'
626+
newTableWith conf snapshotIsUnion table'
607627

608628
-- To match the implementation of the real table, this should not corrupt the
609629
-- snapshot if there are _no non-empty files_; however, since there are no such
@@ -618,7 +638,7 @@ corruptSnapshot name = do
618638
then throwError ErrSnapshotDoesNotExist
619639
else modify $ \m -> m {snapshots = Map.adjust corruptSnapshotEntry name snapshots}
620640
where
621-
corruptSnapshotEntry (Snapshot c l t _) = Snapshot c l t True
641+
corruptSnapshotEntry (Snapshot c l t u _) = Snapshot c l t u True
622642

623643
deleteSnapshot ::
624644
(MonadState Model m, MonadError Err m)
@@ -652,7 +672,7 @@ duplicate ::
652672
-> m (Table k v b)
653673
duplicate t@Table{..} = do
654674
table <- snd <$> guardTableIsOpen t
655-
newTableWith config $ Model.duplicate table
675+
newTableWith config isUnionDescendant $ Model.duplicate table
656676

657677
{-------------------------------------------------------------------------------
658678
Cursor
@@ -733,6 +753,14 @@ guardCursorIsOpen Cursor{..} =
733753
Table union
734754
-------------------------------------------------------------------------------}
735755

756+
-- Is this a (descendant of a) union table?
757+
--
758+
-- This is important for invalidating blob references: if a table is a
759+
-- (descendant of a) union table, then 'supplyUnionCredits' can invalidate blob
760+
-- references.
761+
data IsUnionDescendant = IsUnionDescendant | IsNotUnionDescendant
762+
deriving stock (Show, Eq)
763+
736764
union ::
737765
( MonadState Model m
738766
, MonadError Err m
@@ -745,7 +773,7 @@ union ::
745773
union r th1 th2 = do
746774
(_, t1) <- guardTableIsOpen th1
747775
(_, t2) <- guardTableIsOpen th2
748-
newTableWith TableConfig $ Model.union r t1 t2
776+
newTableWith TableConfig IsUnionDescendant $ Model.union r t1 t2
749777

750778
unions ::
751779
( MonadState Model m
@@ -759,4 +787,68 @@ unions r tables = do
759787
tables' <- forM tables $ \table -> do
760788
(_, table') <- guardTableIsOpen table
761789
pure table'
762-
newTableWith TableConfig $ Model.unions r tables'
790+
newTableWith TableConfig IsUnionDescendant $ Model.unions r tables'
791+
792+
-- | The model can not accurately predict union debt without considerable
793+
-- knowledge about the implementation of /real/ tables. Therefore the model
794+
-- considers unions to be finished right away, and the resulting debt will
795+
-- always be 0.
796+
remainingUnionDebt ::
797+
( MonadState Model m
798+
, MonadError Err m
799+
, C k v b
800+
)
801+
=> Table k v b
802+
-> m UnionDebt
803+
remainingUnionDebt t = do
804+
(_updc, _table) <- guardTableIsOpen t
805+
pure (UnionDebt 0)
806+
807+
-- | The union debt is always 0, so supplying union credits has no effect on the
808+
-- tables, except for invalidating its blob references in some cases.
809+
--
810+
-- In the /real/ implementation, blob references can be associated with a run in
811+
-- a regular level, or in a union level. In the former case, only updates can
812+
-- invalidate the blob reference. In the latter case, only supplying union
813+
-- credits can invalidate the blob reference.
814+
--
815+
-- Without considerable knowledge about the /real/ implementation, the model can
816+
-- not /always/ accurately predict which of two cases a blob reference belongs
817+
-- to. However, there is one case where a table is guaranteed not to contain a
818+
-- union level: if the table is /not/ a (descendant of a) union table.
819+
--
820+
-- There is another caveat: without considerable knowledge about the real
821+
-- implementation, the model can not accurately predict after how many supplied
822+
-- union credits /real/ blob references are invalidated. Therefore, we model
823+
-- invalidation conservatively in a similar way to 'updates': any supply of
824+
-- @>=1@ union credits is enough to invalidate union blob references.
825+
--
826+
-- To summarise, 'supplyUnionCredits' will invalidate blob references associated
827+
-- with the input table if:
828+
--
829+
-- * The table is a (descendant of a) union table
830+
--
831+
-- * The number of supplied union credits is at least 1.
832+
--
833+
-- TODO: in the real implementation, supplying union credits can invalidate blob
834+
-- references for other tables if they share merging runs in their union levels.
835+
-- For example, supplying union credits to a duplicate of a (descendant of a)
836+
-- union table can invalidate blob references for both the original and
837+
-- duplicate table.
838+
supplyUnionCredits ::
839+
( MonadState Model m
840+
, MonadError Err m
841+
, C k v b
842+
)
843+
=> Table k v b
844+
-> UnionCredits
845+
-> m UnionCredits
846+
supplyUnionCredits t@Table{..} c@(UnionCredits credits)
847+
| credits <= 0 = pure c
848+
| otherwise = do
849+
(updc, table) <- guardTableIsOpen t
850+
when (isUnionDescendant == IsUnionDescendant) $
851+
modify (\m -> m {
852+
tables = Map.insert tableID (updc + 1, toSomeTable table) (tables m)
853+
})
854+
pure c

0 commit comments

Comments
 (0)