lsm-tree-1.0.0.0: src-core/Database/LSMTree/Internal/Snapshot.hs
{-# OPTIONS_HADDOCK not-home #-}
module Database.LSMTree.Internal.Snapshot (
-- * Snapshot metadata
SnapshotLabel (..)
, SnapshotMetaData (..)
-- * Levels snapshot format
, SnapLevels (..)
, SnapLevel (..)
, SnapIncomingRun (..)
, SnapMergingRun (..)
-- * MergeTree snapshot format
, SnapMergingTree(..)
, SnapMergingTreeState(..)
, SnapPendingMerge(..)
, SnapPreExistingRun(..)
-- * Conversion to levels snapshot format
, toSnapLevels
-- * Conversion to merging tree snapshot format
, toSnapMergingTree
-- * Write buffer
, snapshotWriteBuffer
, openWriteBuffer
-- * Run
, SnapshotRun (..)
, snapshotRun
, openRun
-- * Opening snapshot formats
-- ** Levels format
, fromSnapLevels
-- ** Merging Tree format
, fromSnapMergingTree
-- * Hard links
, hardLinkRunFiles
) where
import Control.ActionRegistry
import Control.Concurrent.Class.MonadMVar.Strict
import Control.Concurrent.Class.MonadSTM (MonadSTM)
import Control.DeepSeq (NFData (..))
import Control.Monad (void)
import Control.Monad.Class.MonadST (MonadST)
import Control.Monad.Class.MonadThrow (MonadMask, bracket,
bracketOnError)
import Control.Monad.Primitive (PrimMonad)
import Control.RefCount
import Data.Foldable (sequenceA_, traverse_)
import Data.String (IsString)
import Data.Text (Text)
import qualified Data.Vector as V
import qualified Database.LSMTree.Internal.BloomFilter as Bloom
import Database.LSMTree.Internal.Config
import Database.LSMTree.Internal.CRC32C (checkCRC)
import qualified Database.LSMTree.Internal.CRC32C as CRC
import Database.LSMTree.Internal.IncomingRun
import qualified Database.LSMTree.Internal.Merge as Merge
import Database.LSMTree.Internal.MergeSchedule
import qualified Database.LSMTree.Internal.MergingRun as MR
import qualified Database.LSMTree.Internal.MergingTree as MT
import Database.LSMTree.Internal.Paths (ActiveDir (..), ForBlob (..),
ForKOps (..), NamedSnapshotDir (..), RunFsPaths (..),
WriteBufferFsPaths (..),
fromChecksumsFileForWriteBufferFiles, pathsForRunFiles,
runChecksumsPath, runPath, writeBufferBlobPath,
writeBufferChecksumsPath, writeBufferKOpsPath)
import Database.LSMTree.Internal.Run (Run, RunParams)
import qualified Database.LSMTree.Internal.Run as Run
import Database.LSMTree.Internal.RunNumber
import Database.LSMTree.Internal.Serialise (ResolveSerialisedValue)
import Database.LSMTree.Internal.UniqCounter (UniqCounter,
incrUniqCounter, uniqueToInt, uniqueToRunNumber)
import Database.LSMTree.Internal.WriteBuffer (WriteBuffer)
import Database.LSMTree.Internal.WriteBufferBlobs (WriteBufferBlobs)
import qualified Database.LSMTree.Internal.WriteBufferBlobs as WBB
import qualified Database.LSMTree.Internal.WriteBufferReader as WBR
import qualified Database.LSMTree.Internal.WriteBufferWriter as WBW
import qualified System.FS.API as FS
import System.FS.API (HasFS, (<.>), (</>))
import qualified System.FS.API.Lazy as FSL
import qualified System.FS.BlockIO.API as FS
import System.FS.BlockIO.API (HasBlockIO)
{-------------------------------------------------------------------------------
Snapshot metadata
-------------------------------------------------------------------------------}
-- | Custom, user-supplied text that is included in the metadata.
--
-- The main use case for a 'SnapshotLabel' is for the user to supply textual
-- information about the key\/value\/blob type for the table that corresponds to
-- the snapshot. This information is used to dynamically check that a snapshot
-- is opened at the correct key\/value\/blob type.
newtype SnapshotLabel = SnapshotLabel Text
deriving stock (Show, Eq)
deriving newtype (NFData, IsString)
data SnapshotMetaData = SnapshotMetaData {
-- | See 'SnapshotLabel'.
--
-- One could argue that the 'SnapshotName' could be used to to hold this
-- type information, but the file name of snapshot metadata is not guarded
-- by a checksum, whereas the contents of the file are. Therefore using the
-- 'SnapshotLabel' is safer.
snapMetaLabel :: !SnapshotLabel
-- | The 'TableConfig' for the snapshotted table.
, snapMetaConfig :: !TableConfig
-- | The write buffer.
, snapWriteBuffer :: !RunNumber
-- | The shape of the levels of the LSM tree.
, snapMetaLevels :: !(SnapLevels SnapshotRun)
-- | The state of tree merging of the LSM tree.
, snapMergingTree :: !(Maybe (SnapMergingTree SnapshotRun))
}
deriving stock Eq
instance NFData SnapshotMetaData where
rnf (SnapshotMetaData a b c d e) =
rnf a `seq` rnf b `seq` rnf c `seq`
rnf d `seq` rnf e
{-------------------------------------------------------------------------------
Levels snapshot format
-------------------------------------------------------------------------------}
newtype SnapLevels r = SnapLevels { getSnapLevels :: V.Vector (SnapLevel r) }
deriving stock (Eq, Functor, Foldable, Traversable)
deriving newtype NFData
data SnapLevel r = SnapLevel {
snapIncoming :: !(SnapIncomingRun r)
, snapResidentRuns :: !(V.Vector r)
}
deriving stock (Eq, Functor, Foldable, Traversable)
instance NFData r => NFData (SnapLevel r) where
rnf (SnapLevel a b) = rnf a `seq` rnf b
-- | Note that for snapshots of incoming runs, we store only the merge debt and
-- nominal credits, not the nominal debt or the merge credits. The rationale is
-- a bit subtle.
--
-- The nominal debt does not need to be stored because it can be derived based
-- on the table's write buffer capacity (which is stored in the snapshot's
-- TableConfig), and on the level number that the merge is at (which also known
-- from the snapshot structure).
--
-- The merge credits can be recalculated from the combination of the nominal debt,
-- nominal credits and merge debt.
--
-- The merge debt is always the sum of the size of the input runs, so at first
-- glance this seems redundant. However for completed merges we no longer have
-- the input runs, so we must store the merge debt if we are to perfectly round
-- trip the snapshot. This is a nice simple property to have though it is
-- probably not 100% essential. We could weaken the round trip property to
-- allow forgetting the merge debt and credit of completed merges (and set them
-- both to zero).
--
data SnapIncomingRun r =
SnapIncomingMergingRun
!MergePolicyForLevel
!NominalDebt
!NominalCredits -- ^ The nominal credits supplied, and that
-- need to be supplied on snapshot open.
!(SnapMergingRun MR.LevelMergeType r)
| SnapIncomingSingleRun !r
deriving stock (Eq, Functor, Foldable, Traversable)
instance NFData r => NFData (SnapIncomingRun r) where
rnf (SnapIncomingMergingRun a b c d) =
rnf a `seq` rnf b `seq` rnf c `seq` rnf d
rnf (SnapIncomingSingleRun a) = rnf a
-- | The total number of supplied credits. This total is used on snapshot load
-- to restore merging work that was lost when the snapshot was created.
newtype SuppliedCredits = SuppliedCredits { getSuppliedCredits :: Int }
deriving stock (Eq, Read)
deriving newtype NFData
data SnapMergingRun t r =
SnapCompletedMerge !MergeDebt !r
| SnapOngoingMerge !RunParams !MergeCredits !(V.Vector r) !t
deriving stock (Eq, Functor, Foldable, Traversable)
instance (NFData t, NFData r) => NFData (SnapMergingRun t r) where
rnf (SnapCompletedMerge a b) = rnf a `seq` rnf b
rnf (SnapOngoingMerge a b c d) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d
{-------------------------------------------------------------------------------
Snapshot MergingTree
-------------------------------------------------------------------------------}
newtype SnapMergingTree r = SnapMergingTree (SnapMergingTreeState r)
deriving stock (Eq, Functor, Foldable, Traversable)
deriving newtype NFData
data SnapMergingTreeState r =
SnapCompletedTreeMerge !r
| SnapPendingTreeMerge !(SnapPendingMerge r)
| SnapOngoingTreeMerge !(SnapMergingRun MR.TreeMergeType r)
deriving stock (Eq, Functor, Foldable, Traversable)
instance NFData r => NFData (SnapMergingTreeState r) where
rnf (SnapCompletedTreeMerge a) = rnf a
rnf (SnapPendingTreeMerge a) = rnf a
rnf (SnapOngoingTreeMerge a) = rnf a
data SnapPendingMerge r =
SnapPendingLevelMerge
![SnapPreExistingRun r]
!(Maybe (SnapMergingTree r))
| SnapPendingUnionMerge
![SnapMergingTree r]
deriving stock (Eq, Functor, Foldable, Traversable)
instance NFData r => NFData (SnapPendingMerge r) where
rnf (SnapPendingLevelMerge a b) = rnf a `seq` rnf b
rnf (SnapPendingUnionMerge a) = rnf a
data SnapPreExistingRun r =
SnapPreExistingRun !r
| SnapPreExistingMergingRun !(SnapMergingRun MR.LevelMergeType r)
deriving stock (Eq, Functor, Foldable, Traversable)
instance NFData r => NFData (SnapPreExistingRun r) where
rnf (SnapPreExistingRun a) = rnf a
rnf (SnapPreExistingMergingRun a) = rnf a
{-------------------------------------------------------------------------------
Opening from merging tree snapshot format
-------------------------------------------------------------------------------}
{-# SPECIALISE fromSnapMergingTree ::
HasFS IO h
-> HasBlockIO IO h
-> Bloom.Salt
-> UniqCounter IO
-> ResolveSerialisedValue
-> ActiveDir
-> ActionRegistry IO
-> SnapMergingTree (Ref (Run IO h))
-> IO (Ref (MT.MergingTree IO h))
#-}
-- | Converts a snapshot of a merging tree of runs to a real merging tree.
--
-- Returns a new reference. Input runs remain owned by the caller.
fromSnapMergingTree ::
forall m h. (MonadMask m, MonadMVar m, MonadSTM m, MonadST m)
=> HasFS m h
-> HasBlockIO m h
-> Bloom.Salt
-> UniqCounter m
-> ResolveSerialisedValue
-> ActiveDir
-> ActionRegistry m
-> SnapMergingTree (Ref (Run m h))
-> m (Ref (MT.MergingTree m h))
fromSnapMergingTree hfs hbio salt uc resolve dir =
go
where
-- Reference strategy:
-- * go returns a fresh reference
-- * go ensures the returned reference will be cleaned up on failure,
-- using withRollback
-- * All results from recursive calls must be released locally on the
-- happy path.
go :: ActionRegistry m
-> SnapMergingTree (Ref (Run m h))
-> m (Ref (MT.MergingTree m h))
go reg (SnapMergingTree (SnapCompletedTreeMerge run)) =
withRollback reg
(MT.newCompletedMerge run)
releaseRef
go reg (SnapMergingTree (SnapPendingTreeMerge
(SnapPendingLevelMerge prs mmt))) = do
prs' <- traverse (fromSnapPreExistingRun reg) prs
mmt' <- traverse (go reg) mmt
mt <- withRollback reg
(MT.newPendingLevelMerge prs' mmt')
releaseRef
traverse_ (delayedCommit reg . releasePER) prs'
traverse_ (delayedCommit reg . releaseRef) mmt'
pure mt
go reg (SnapMergingTree (SnapPendingTreeMerge
(SnapPendingUnionMerge mts))) = do
mts' <- traverse (go reg) mts
mt <- withRollback reg
(MT.newPendingUnionMerge mts')
releaseRef
traverse_ (delayedCommit reg . releaseRef) mts'
pure mt
go reg (SnapMergingTree (SnapOngoingTreeMerge smrs)) = do
mr <- withRollback reg
(fromSnapMergingRun hfs hbio salt uc resolve dir smrs)
releaseRef
mt <- withRollback reg
(MT.newOngoingMerge mr)
releaseRef
delayedCommit reg (releaseRef mr)
pure mt
-- Returns fresh refs, which must be released locally.
fromSnapPreExistingRun :: ActionRegistry m
-> SnapPreExistingRun (Ref (Run m h))
-> m (MT.PreExistingRun m h)
fromSnapPreExistingRun reg (SnapPreExistingRun run) =
MT.PreExistingRun <$>
withRollback reg (dupRef run) releaseRef
fromSnapPreExistingRun reg (SnapPreExistingMergingRun smrs) =
MT.PreExistingMergingRun <$>
withRollback reg
(fromSnapMergingRun hfs hbio salt uc resolve dir smrs)
releaseRef
releasePER (MT.PreExistingRun r) = releaseRef r
releasePER (MT.PreExistingMergingRun mr) = releaseRef mr
{-------------------------------------------------------------------------------
Conversion to merge tree snapshot format
-------------------------------------------------------------------------------}
{-# SPECIALISE toSnapMergingTree :: Ref (MT.MergingTree IO h) -> IO (SnapMergingTree (Ref (Run IO h))) #-}
toSnapMergingTree ::
(PrimMonad m, MonadMVar m)
=> Ref (MT.MergingTree m h)
-> m (SnapMergingTree (Ref (Run m h)))
toSnapMergingTree (DeRef (MT.MergingTree mStateVar _mCounter)) =
withMVar mStateVar $ \mState -> SnapMergingTree <$> toSnapMergingTreeState mState
{-# SPECIALISE toSnapMergingTreeState :: MT.MergingTreeState IO h -> IO (SnapMergingTreeState (Ref (Run IO h))) #-}
toSnapMergingTreeState ::
(PrimMonad m, MonadMVar m)
=> MT.MergingTreeState m h
-> m (SnapMergingTreeState (Ref (Run m h)))
toSnapMergingTreeState (MT.CompletedTreeMerge r) = pure $ SnapCompletedTreeMerge r
toSnapMergingTreeState (MT.PendingTreeMerge p) = SnapPendingTreeMerge <$> toSnapPendingMerge p
toSnapMergingTreeState (MT.OngoingTreeMerge mergingRun) =
SnapOngoingTreeMerge <$> toSnapMergingRun mergingRun
{-# SPECIALISE toSnapPendingMerge :: MT.PendingMerge IO h -> IO (SnapPendingMerge (Ref (Run IO h))) #-}
toSnapPendingMerge ::
(PrimMonad m, MonadMVar m)
=> MT.PendingMerge m h
-> m (SnapPendingMerge (Ref (Run m h)))
toSnapPendingMerge (MT.PendingUnionMerge mts) =
SnapPendingUnionMerge <$> traverse toSnapMergingTree (V.toList mts)
toSnapPendingMerge (MT.PendingLevelMerge pes mmt) = do
pes' <- traverse toSnapPreExistingRun pes
mmt' <- traverse toSnapMergingTree mmt
pure $ SnapPendingLevelMerge (V.toList pes') mmt'
{-# SPECIALISE toSnapPreExistingRun :: MT.PreExistingRun IO h -> IO (SnapPreExistingRun (Ref (Run IO h))) #-}
toSnapPreExistingRun ::
(PrimMonad m, MonadMVar m)
=> MT.PreExistingRun m h
-> m (SnapPreExistingRun (Ref (Run m h)))
toSnapPreExistingRun (MT.PreExistingRun run) = pure $ SnapPreExistingRun run
toSnapPreExistingRun (MT.PreExistingMergingRun peMergingRun) =
SnapPreExistingMergingRun <$> toSnapMergingRun peMergingRun
{-------------------------------------------------------------------------------
Conversion to levels snapshot format
-------------------------------------------------------------------------------}
--TODO: probably generally all the Ref (Run _) here ought to be fresh
-- references, created as we snapshot the levels, so that the runs don't
-- disappear under our feet during the process of making the snapshot durable.
-- At minimum the volatile runs are the inputs to merging runs, but it may be
-- simpler to duplicate them all, and release them all at the end.
{-# SPECIALISE toSnapLevels :: Levels IO h -> IO (SnapLevels (Ref (Run IO h))) #-}
toSnapLevels ::
(PrimMonad m, MonadMVar m)
=> Levels m h
-> m (SnapLevels (Ref (Run m h)))
toSnapLevels levels = SnapLevels <$> V.mapM toSnapLevel levels
{-# SPECIALISE toSnapLevel :: Level IO h -> IO (SnapLevel (Ref (Run IO h))) #-}
toSnapLevel ::
(PrimMonad m, MonadMVar m)
=> Level m h
-> m (SnapLevel (Ref (Run m h)))
toSnapLevel Level{..} = do
sir <- toSnapIncomingRun incomingRun
pure (SnapLevel sir residentRuns)
{-# SPECIALISE toSnapIncomingRun :: IncomingRun IO h -> IO (SnapIncomingRun (Ref (Run IO h))) #-}
toSnapIncomingRun ::
(PrimMonad m, MonadMVar m)
=> IncomingRun m h
-> m (SnapIncomingRun (Ref (Run m h)))
toSnapIncomingRun ir = do
s <- snapshotIncomingRun ir
case s of
Left r -> pure $! SnapIncomingSingleRun r
Right (mergePolicy,
nominalDebt,
nominalCredits,
mergingRun) -> do
-- We need to know how many credits were supplied so we can restore merge
-- work on snapshot load.
smrs <- toSnapMergingRun mergingRun
pure $! SnapIncomingMergingRun mergePolicy nominalDebt nominalCredits smrs
{-# SPECIALISE toSnapMergingRun ::
Ref (MR.MergingRun t IO h)
-> IO (SnapMergingRun t (Ref (Run IO h))) #-}
toSnapMergingRun ::
(PrimMonad m, MonadMVar m)
=> Ref (MR.MergingRun t m h)
-> m (SnapMergingRun t (Ref (Run m h)))
toSnapMergingRun !mr = do
-- TODO: MR.snapshot needs to return duplicated run references, and we
-- need to arrange to release them when the snapshotting is done.
( mergeDebt, mergeCredits, state) <- MR.snapshot mr
case state of
MR.CompletedMerge r ->
pure $! SnapCompletedMerge mergeDebt r
MR.OngoingMerge rs m ->
pure $! SnapOngoingMerge runParams mergeCredits rs mergeType
where
runParams = Merge.mergeRunParams m
mergeType = Merge.mergeType m
{-------------------------------------------------------------------------------
Write Buffer
-------------------------------------------------------------------------------}
{-# SPECIALISE
snapshotWriteBuffer ::
HasFS IO h
-> HasBlockIO IO h
-> UniqCounter IO
-> UniqCounter IO
-> ActionRegistry IO
-> ActiveDir
-> NamedSnapshotDir
-> WriteBuffer
-> Ref (WriteBufferBlobs IO h)
-> IO WriteBufferFsPaths
#-}
snapshotWriteBuffer ::
(MonadMVar m, MonadSTM m, MonadST m, MonadMask m)
=> HasFS m h
-> HasBlockIO m h
-> UniqCounter m
-> UniqCounter m
-> ActionRegistry m
-> ActiveDir
-> NamedSnapshotDir
-> WriteBuffer
-> Ref (WriteBufferBlobs m h)
-> m WriteBufferFsPaths
snapshotWriteBuffer hfs hbio activeUc snapUc reg activeDir snapDir wb wbb = do
-- Write the write buffer and write buffer blobs to the active directory.
activeWriteBufferNumber <- uniqueToRunNumber <$> incrUniqCounter activeUc
let activeWriteBufferPaths = WriteBufferFsPaths (getActiveDir activeDir) activeWriteBufferNumber
withRollback_ reg
(WBW.writeWriteBuffer hfs hbio activeWriteBufferPaths wb wbb)
-- TODO: it should probably be the responsibility of writeWriteBuffer to do
-- cleanup
$ do
-- TODO: check files exist before removing them
FS.removeFile hfs (writeBufferKOpsPath activeWriteBufferPaths)
FS.removeFile hfs (writeBufferBlobPath activeWriteBufferPaths)
-- Hard link the write buffer and write buffer blobs to the snapshot directory.
snapWriteBufferNumber <- uniqueToRunNumber <$> incrUniqCounter snapUc
let snapWriteBufferPaths = WriteBufferFsPaths (getNamedSnapshotDir snapDir) snapWriteBufferNumber
hardLink hfs hbio reg
(writeBufferKOpsPath activeWriteBufferPaths)
(writeBufferKOpsPath snapWriteBufferPaths)
hardLink hfs hbio reg
(writeBufferBlobPath activeWriteBufferPaths)
(writeBufferBlobPath snapWriteBufferPaths)
hardLink hfs hbio reg
(writeBufferChecksumsPath activeWriteBufferPaths)
(writeBufferChecksumsPath snapWriteBufferPaths)
pure snapWriteBufferPaths
{-# SPECIALISE
openWriteBuffer ::
ActionRegistry IO
-> ResolveSerialisedValue
-> HasFS IO h
-> HasBlockIO IO h
-> UniqCounter IO
-> ActiveDir
-> WriteBufferFsPaths
-> IO (WriteBuffer, Ref (WriteBufferBlobs IO h))
#-}
openWriteBuffer ::
(MonadMVar m, MonadMask m, MonadSTM m, MonadST m)
=> ActionRegistry m
-> ResolveSerialisedValue
-> HasFS m h
-> HasBlockIO m h
-> UniqCounter m
-> ActiveDir
-> WriteBufferFsPaths
-> m (WriteBuffer, Ref (WriteBufferBlobs m h))
openWriteBuffer reg resolve hfs hbio uc activeDir snapWriteBufferPaths = do
-- Check the checksums
-- TODO: This reads the blobfile twice: once to check the CRC and once more
-- to copy it from the snapshot directory to the active directory.
(expectedChecksumForKOps, expectedChecksumForBlob) <-
CRC.expectValidFile hfs (writeBufferChecksumsPath snapWriteBufferPaths) CRC.FormatWriteBufferFile
. fromChecksumsFileForWriteBufferFiles
=<< CRC.readChecksumsFile hfs (writeBufferChecksumsPath snapWriteBufferPaths)
checkCRC hfs hbio False (unForKOps expectedChecksumForKOps) (writeBufferKOpsPath snapWriteBufferPaths)
checkCRC hfs hbio False (unForBlob expectedChecksumForBlob) (writeBufferBlobPath snapWriteBufferPaths)
-- Copy the write buffer blobs file to the active directory and open it.
activeWriteBufferNumber <- uniqueToInt <$> incrUniqCounter uc
let activeWriteBufferBlobPath =
getActiveDir activeDir </> FS.mkFsPath [show activeWriteBufferNumber] <.> "wbblobs"
copyFile hfs reg (writeBufferBlobPath snapWriteBufferPaths) activeWriteBufferBlobPath
writeBufferBlobs <-
withRollback reg
(WBB.open hfs activeWriteBufferBlobPath FS.AllowExisting)
releaseRef
-- Read write buffer key/ops
let kOpsPath = ForKOps (writeBufferKOpsPath snapWriteBufferPaths)
writeBuffer <-
withRef writeBufferBlobs $ \wbb ->
WBR.readWriteBuffer resolve hfs hbio kOpsPath (WBB.blobFile wbb)
pure (writeBuffer, writeBufferBlobs)
{-------------------------------------------------------------------------------
Runs
-------------------------------------------------------------------------------}
-- | Information needed to open a 'Run' from disk using 'snapshotRun' and
-- 'openRun'.
--
-- TODO: one could imagine needing only the 'RunNumber' to identify the files
-- on disk, and the other parameters being stored with the run itself, rather
-- than needing to be supplied.
data SnapshotRun = SnapshotRun {
snapRunNumber :: !RunNumber,
snapRunCaching :: !Run.RunDataCaching,
snapRunIndex :: !Run.IndexType
}
deriving stock Eq
instance NFData SnapshotRun where
rnf (SnapshotRun a b c) = rnf a `seq` rnf b `seq` rnf c
{-# SPECIALISE snapshotRun ::
HasFS IO h
-> HasBlockIO IO h
-> UniqCounter IO
-> ActionRegistry IO
-> NamedSnapshotDir
-> Ref (Run IO h)
-> IO SnapshotRun #-}
-- | @'snapshotRun' _ _ snapUc _ targetDir run@ creates hard links for all files
-- associated with the @run@, and puts the new directory entries in the
-- @targetDir@ directory. The entries are renamed using @snapUc@.
snapshotRun ::
(MonadMask m, PrimMonad m)
=> HasFS m h
-> HasBlockIO m h
-> UniqCounter m
-> ActionRegistry m
-> NamedSnapshotDir
-> Ref (Run m h)
-> m SnapshotRun
snapshotRun hfs hbio snapUc reg (NamedSnapshotDir targetDir) run = do
rn <- uniqueToRunNumber <$> incrUniqCounter snapUc
let sourcePaths = Run.runFsPaths run
let targetPaths = sourcePaths { runDir = targetDir , runNumber = rn}
hardLinkRunFiles hfs hbio reg sourcePaths targetPaths
pure SnapshotRun {
snapRunNumber = runNumber targetPaths,
snapRunCaching = Run.runDataCaching run,
snapRunIndex = Run.runIndexType run
}
{-# SPECIALISE openRun ::
HasFS IO h
-> HasBlockIO IO h
-> UniqCounter IO
-> ActionRegistry IO
-> NamedSnapshotDir
-> ActiveDir
-> Bloom.Salt
-> SnapshotRun
-> IO (Ref (Run IO h)) #-}
-- | @'openRun' _ _ uniqCounter _ sourceDir targetDir _ snaprun@ takes all run
-- files that are referenced by @snaprun@, and hard links them from @sourceDir@
-- into @targetDir@ with new, unique names (using @uniqCounter@). Each set of
-- (hard linked) files that represents a run is opened and verified, returning
-- 'Run' as a result.
--
-- The result must ultimately be released using 'releaseRef'.
openRun ::
(MonadMask m, MonadSTM m, MonadST m)
=> HasFS m h
-> HasBlockIO m h
-> UniqCounter m
-> ActionRegistry m
-> NamedSnapshotDir
-> ActiveDir
-> Bloom.Salt
-> SnapshotRun
-> m (Ref (Run m h))
openRun hfs hbio uc reg
(NamedSnapshotDir sourceDir) (ActiveDir targetDir)
expectedSalt
SnapshotRun {
snapRunNumber = runNum,
snapRunCaching = caching,
snapRunIndex = indexType
} = do
let sourcePaths = RunFsPaths sourceDir runNum
runNum' <- uniqueToRunNumber <$> incrUniqCounter uc
let targetPaths = RunFsPaths targetDir runNum'
hardLinkRunFiles hfs hbio reg sourcePaths targetPaths
withRollback reg
(Run.openFromDisk hfs hbio caching indexType expectedSalt targetPaths)
releaseRef
{-------------------------------------------------------------------------------
Opening from levels snapshot format
-------------------------------------------------------------------------------}
{-# SPECIALISE fromSnapLevels ::
HasFS IO h
-> HasBlockIO IO h
-> Bloom.Salt
-> UniqCounter IO
-> TableConfig
-> ResolveSerialisedValue
-> ActionRegistry IO
-> ActiveDir
-> SnapLevels (Ref (Run IO h))
-> IO (Levels IO h)
#-}
-- | Duplicates runs and re-creates merging runs.
fromSnapLevels ::
forall m h. (MonadMask m, MonadMVar m, MonadSTM m, MonadST m)
=> HasFS m h
-> HasBlockIO m h
-> Bloom.Salt
-> UniqCounter m
-> TableConfig
-> ResolveSerialisedValue
-> ActionRegistry m
-> ActiveDir
-> SnapLevels (Ref (Run m h))
-> m (Levels m h)
fromSnapLevels hfs hbio salt uc conf resolve reg dir (SnapLevels levels) =
V.iforM levels $ \i -> fromSnapLevel (LevelNo (i+1))
where
-- TODO: we may wish to trace the merges created during snapshot restore:
fromSnapLevel :: LevelNo -> SnapLevel (Ref (Run m h)) -> m (Level m h)
fromSnapLevel ln SnapLevel{snapIncoming, snapResidentRuns} = do
incomingRun <- withRollback reg
(fromSnapIncomingRun ln snapIncoming)
releaseIncomingRun
residentRuns <- V.forM snapResidentRuns $ \r ->
withRollback reg
(dupRef r)
releaseRef
pure Level {incomingRun , residentRuns}
fromSnapIncomingRun ::
LevelNo
-> SnapIncomingRun (Ref (Run m h))
-> m (IncomingRun m h)
fromSnapIncomingRun _ln (SnapIncomingSingleRun run) =
newIncomingSingleRun run
fromSnapIncomingRun ln (SnapIncomingMergingRun mergePolicy nominalDebt
nominalCredits smrs) =
bracket
(fromSnapMergingRun hfs hbio salt uc resolve dir smrs)
releaseRef $ \mr -> do
ir <- newIncomingMergingRun mergePolicy nominalDebt mr
-- This will set the correct nominal credits, but it will not do any
-- more merging work because fromSnapMergingRun already supplies
-- all the merging credits already.
supplyCreditsIncomingRun conf ln ir nominalCredits
pure ir
{-# SPECIALISE fromSnapMergingRun ::
MR.IsMergeType t
=> HasFS IO h
-> HasBlockIO IO h
-> Bloom.Salt
-> UniqCounter IO
-> ResolveSerialisedValue
-> ActiveDir
-> SnapMergingRun t (Ref (Run IO h))
-> IO (Ref (MR.MergingRun t IO h)) #-}
fromSnapMergingRun ::
(MonadMask m, MonadMVar m, MonadSTM m, MonadST m, MR.IsMergeType t)
=> HasFS m h
-> HasBlockIO m h
-> Bloom.Salt
-> UniqCounter m
-> ResolveSerialisedValue
-> ActiveDir
-> SnapMergingRun t (Ref (Run m h))
-> m (Ref (MR.MergingRun t m h))
fromSnapMergingRun _ _ _ _ _ _ (SnapCompletedMerge mergeDebt r) =
MR.newCompleted mergeDebt r
fromSnapMergingRun hfs hbio salt uc resolve dir
(SnapOngoingMerge runParams mergeCredits rs mergeType) = do
bracketOnError
(do uniq <- incrUniqCounter uc
let runPaths = runPath dir (uniqueToRunNumber uniq)
MR.new hfs hbio resolve salt runParams mergeType runPaths rs)
releaseRef $ \mr -> do
-- When a snapshot is created, merge progress is lost, so we have to
-- redo merging work here. The MergeCredits in SnapMergingRun tracks
-- how many credits were supplied before the snapshot was taken.
--TODO: the threshold should be stored with the MergingRun
-- here we want to supply the credits now, so we can use a threshold of 1
let thresh = MR.CreditThreshold (MR.UnspentCredits 1)
_ <- MR.supplyCreditsAbsolute mr thresh mergeCredits
pure mr
{-------------------------------------------------------------------------------
Hard links
-------------------------------------------------------------------------------}
{-# SPECIALISE hardLinkRunFiles ::
HasFS IO h
-> HasBlockIO IO h
-> ActionRegistry IO
-> RunFsPaths
-> RunFsPaths
-> IO () #-}
-- | @'hardLinkRunFiles' _ _ _ sourcePaths targetPaths@ creates a hard link for
-- each @sourcePaths@ path using the corresponding @targetPaths@ path as the
-- name for the new directory entry.
hardLinkRunFiles ::
(MonadMask m, PrimMonad m)
=> HasFS m h
-> HasBlockIO m h
-> ActionRegistry m
-> RunFsPaths
-> RunFsPaths
-> m ()
hardLinkRunFiles hfs hbio reg sourceRunFsPaths targetRunFsPaths = do
let sourcePaths = pathsForRunFiles sourceRunFsPaths
targetPaths = pathsForRunFiles targetRunFsPaths
sequenceA_ (hardLink hfs hbio reg <$> sourcePaths <*> targetPaths)
hardLink hfs hbio reg (runChecksumsPath sourceRunFsPaths) (runChecksumsPath targetRunFsPaths)
{-# SPECIALISE
hardLink ::
HasFS IO h
-> HasBlockIO IO h
-> ActionRegistry IO
-> FS.FsPath
-> FS.FsPath
-> IO ()
#-}
-- | @'hardLink' hfs hbio reg sourcePath targetPath@ creates a hard link from
-- @sourcePath@ to @targetPath@.
hardLink ::
(MonadMask m, PrimMonad m)
=> HasFS m h
-> HasBlockIO m h
-> ActionRegistry m
-> FS.FsPath
-> FS.FsPath
-> m ()
hardLink hfs hbio reg sourcePath targetPath = do
withRollback_ reg
(FS.createHardLink hbio sourcePath targetPath)
(FS.removeFile hfs targetPath)
{-------------------------------------------------------------------------------
Copy file
-------------------------------------------------------------------------------}
{-# SPECIALISE
copyFile ::
HasFS IO h
-> ActionRegistry IO
-> FS.FsPath
-> FS.FsPath
-> IO ()
#-}
-- | @'copyFile' hfs reg source target@ copies the @source@ path to the @target@ path.
copyFile ::
(MonadMask m, PrimMonad m)
=> HasFS m h
-> ActionRegistry m
-> FS.FsPath
-> FS.FsPath
-> m ()
copyFile hfs reg sourcePath targetPath =
flip (withRollback_ reg) (FS.removeFile hfs targetPath) $
FS.withFile hfs sourcePath FS.ReadMode $ \sourceHandle ->
FS.withFile hfs targetPath (FS.WriteMode FS.MustBeNew) $ \targetHandle -> do
bs <- FSL.hGetAll hfs sourceHandle
void $ FSL.hPutAll hfs targetHandle bs