diff --git a/lsm-tree/lsm-tree.cabal b/lsm-tree/lsm-tree.cabal index 031b9aa9c..421252196 100644 --- a/lsm-tree/lsm-tree.cabal +++ b/lsm-tree/lsm-tree.cabal @@ -570,6 +570,7 @@ library core Database.LSMTree.Internal.BlobFile Database.LSMTree.Internal.BlobRef Database.LSMTree.Internal.BloomFilter + Database.LSMTree.Internal.BloomFilterAcc Database.LSMTree.Internal.ByteString Database.LSMTree.Internal.ChecksumHandle Database.LSMTree.Internal.Chunk @@ -607,10 +608,12 @@ library core Database.LSMTree.Internal.RunBuilder Database.LSMTree.Internal.RunNumber Database.LSMTree.Internal.RunReader + Database.LSMTree.Internal.RunReader.Keys Database.LSMTree.Internal.Serialise Database.LSMTree.Internal.Serialise.Class Database.LSMTree.Internal.Snapshot Database.LSMTree.Internal.Snapshot.Codec + Database.LSMTree.Internal.Snapshot.Codec.Monad Database.LSMTree.Internal.Types Database.LSMTree.Internal.UniqCounter Database.LSMTree.Internal.Unsafe @@ -639,6 +642,7 @@ library core , io-classes:strict-mvar , lsm-tree:control , lsm-tree:kmerge + , mtl ^>=2.2 || ^>=2.3 , primitive ^>=0.9 , serialise ^>=0.2 , text ^>=2.1.1 diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/BloomFilter.hs b/lsm-tree/src-core/Database/LSMTree/Internal/BloomFilter.hs index 9524e4948..2b1b7fa0b 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/BloomFilter.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/BloomFilter.hs @@ -14,35 +14,20 @@ module Database.LSMTree.Internal.BloomFilter ( bloomQueries, RunIxKeyIx(RunIxKeyIx), RunIx, KeyIx, - - -- * Serialisation - bloomFilterVersion, - bloomFilterToLBS, - bloomFilterFromFile, ) where import Data.Bits -import qualified Data.ByteString as BS -import qualified Data.ByteString.Builder.Extra as B -import qualified Data.ByteString.Lazy as LBS import qualified Data.Primitive as P import qualified Data.Vector as V import qualified Data.Vector.Primitive as VP -import Data.Word (Word32, Word64, byteSwap32) -import Text.Printf (printf) +import Data.Word (Word32) import Control.Exception (assert) -import Control.Monad (void, when) -import Control.Monad.Class.MonadThrow -import Control.Monad.Primitive (PrimMonad) + import Control.Monad.ST (ST, runST) -import System.FS.API import Data.BloomFilter.Blocked (Bloom) import qualified Data.BloomFilter.Blocked as Bloom -import Database.LSMTree.Internal.ByteString (byteArrayToByteString) -import Database.LSMTree.Internal.CRC32C (FileCorruptedError (..), - FileFormat (..)) import Database.LSMTree.Internal.Serialise (SerialisedKey) import qualified Database.LSMTree.Internal.Vector as P @@ -206,121 +191,3 @@ toFiltersArray = id indexFiltersArray = V.unsafeIndex lengthFiltersArray = V.length #endif - - --- serialising ------------------------------------------------------------ - --- | By writing out the version in host endianness, we also indicate endianness. --- During deserialisation, we would discover an endianness mismatch. --- --- We base our version number on the 'Bloom.formatVersion' from the @bloomfilter@ --- library, plus our own version here. This accounts both for changes in the --- format code here, and changes in the library. --- -bloomFilterVersion :: Word32 -bloomFilterVersion = 1 + fromIntegral Bloom.formatVersion - -bloomFilterToLBS :: Bloom a -> LBS.ByteString -bloomFilterToLBS bf = - let (size, salt, ba, off, len) = Bloom.serialise bf - in header size salt <> byteArrayToLBS ba off len - where - header Bloom.BloomSize { sizeBits, sizeHashes } salt = - -- creates a single 24 byte chunk - B.toLazyByteStringWith (B.safeStrategy 24 B.smallChunkSize) mempty $ - B.word32Host bloomFilterVersion - <> B.word32Host (fromIntegral sizeHashes) - <> B.word64Host (fromIntegral sizeBits) - <> B.word64Host salt - - byteArrayToLBS :: P.ByteArray -> Int -> Int -> LBS.ByteString - byteArrayToLBS ba off len = - LBS.fromStrict (byteArrayToByteString off len ba) - --- deserialising ------------------------------------------------------------ - -{-# SPECIALISE bloomFilterFromFile :: - HasFS IO h - -> Bloom.Salt - -> Handle h - -> IO (Bloom a) #-} --- | Read a 'Bloom' from a file. --- -bloomFilterFromFile :: - (PrimMonad m, MonadCatch m) - => HasFS m h - -> Bloom.Salt -- ^ Expected salt - -> Handle h -- ^ The open file, in read mode - -> m (Bloom a) -bloomFilterFromFile hfs expectedSalt h = do - header <- rethrowEOFError "Doesn't contain a header" $ - hGetByteArrayExactly hfs h 24 - - let !version = P.indexByteArray header 0 :: Word32 - !nhashes = P.indexByteArray header 1 :: Word32 - !nbits = P.indexByteArray header 1 :: Word64 - !salt = P.indexByteArray header 2 :: Bloom.Salt - - when (version /= bloomFilterVersion) $ throwFormatError $ - if byteSwap32 version == bloomFilterVersion - then "Different byte order" - else "Unsupported version" - - when (nbits <= 0) $ throwFormatError "Length is zero" - - -- limit to 2^48 bits - when (nbits >= fromIntegral Bloom.maxSizeBits) $ throwFormatError "Too large bloomfilter" - - when (expectedSalt /= salt) $ throwFormatError $ - printf "Expected salt does not match actual salt: %d /= %d" - expectedSalt - salt - - -- read the filter data from the file directly into the bloom filter - bloom <- - Bloom.deserialise - Bloom.BloomSize { - Bloom.sizeBits = fromIntegral nbits, - Bloom.sizeHashes = fromIntegral nhashes - } - salt - (\buf off len -> - rethrowEOFError "bloom filter file too short" $ - void $ hGetBufExactly hfs - h buf (BufferOffset off) (fromIntegral len)) - - -- check we're now at EOF - trailing <- hGetSome hfs h 1 - when (not (BS.null trailing)) $ - throwFormatError "Byte array is too large for components" - pure bloom - where - throwFormatError = throwIO - . ErrFileFormatInvalid - (mkFsErrorPath hfs (handlePath h)) - FormatBloomFilterFile - rethrowEOFError msg = - handleJust - (\e -> if isFsErrorType FsReachedEOF e then Just e else Nothing) - (\e -> throwIO $ - ErrFileFormatInvalid - (fsErrorPath e) FormatBloomFilterFile msg) - -{-# SPECIALISE hGetByteArrayExactly :: - HasFS IO h - -> Handle h - -> Int - -> IO P.ByteArray #-} -hGetByteArrayExactly :: - (PrimMonad m, MonadThrow m) - => HasFS m h - -> Handle h - -> Int - -> m P.ByteArray -hGetByteArrayExactly hfs h len = do - buf <- P.newByteArray len - _ <- hGetBufExactly hfs h buf 0 (fromIntegral len) - P.unsafeFreezeByteArray buf - diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/BloomFilterAcc.hs b/lsm-tree/src-core/Database/LSMTree/Internal/BloomFilterAcc.hs new file mode 100644 index 000000000..2d8e7fe76 --- /dev/null +++ b/lsm-tree/src-core/Database/LSMTree/Internal/BloomFilterAcc.hs @@ -0,0 +1,58 @@ +{-# LANGUAGE RecordWildCards #-} +{-# OPTIONS_HADDOCK not-home #-} + +-- | Incremental, in-memory bloom filter construction +-- +module Database.LSMTree.Internal.BloomFilterAcc ( + -- * Bloom filter allocation + RunBloomFilterAlloc (..) + -- * Incremental, in-memory bloom filter construction + , newMBloom + , bloomInserts + ) where + +import Control.DeepSeq (NFData (..)) +import Control.Monad.ST.Strict +import qualified Data.BloomFilter.Blocked as Bloom +import Database.LSMTree.Internal.BloomFilter (MBloom) +import Database.LSMTree.Internal.Entry (NumEntries (..)) +import Database.LSMTree.Internal.PageAcc (PageAcc) +import qualified Database.LSMTree.Internal.PageAcc as PageAcc +import Database.LSMTree.Internal.Serialise (SerialisedKey) + +{------------------------------------------------------------------------------- + Bloom filter allocation +-------------------------------------------------------------------------------} + +-- | See 'Database.LSMTree.Internal.Config.BloomFilterAlloc' +data RunBloomFilterAlloc = + -- | Bits per element in a filter + RunAllocFixed !Double + | RunAllocRequestFPR !Double + deriving stock (Show, Eq) + +instance NFData RunBloomFilterAlloc where + rnf (RunAllocFixed a) = rnf a + rnf (RunAllocRequestFPR a) = rnf a + +{------------------------------------------------------------------------------- + Incremental, in-memory bloom filter construction +-------------------------------------------------------------------------------} + +newMBloom :: NumEntries -> RunBloomFilterAlloc -> Bloom.Salt -> ST s (MBloom s a) +newMBloom (NumEntries nentries) alloc salt = + Bloom.new (Bloom.sizeForPolicy (policy alloc) nentries) salt + where + --TODO: it'd be possible to turn the RunBloomFilterAlloc into a BloomPolicy + -- without the NumEntries, and cache the policy, avoiding recalculating the + -- policy every time. + policy (RunAllocFixed bitsPerEntry) = Bloom.policyForBits bitsPerEntry + policy (RunAllocRequestFPR fpr) = Bloom.policyForFPR fpr + +-- An instance of insertMany specialised to SerialisedKey and indexKeyPageAcc. +-- This is a performance-sensitive function. It is marked NOINLINE so we can +-- easily inspect the core and check all the specialisations worked as expected. +{-# NOINLINE bloomInserts #-} +bloomInserts :: MBloom s SerialisedKey -> PageAcc s -> Int -> ST s () +bloomInserts !mbloom !mpageacc !nkeys = + Bloom.insertMany mbloom (PageAcc.indexKeyPageAcc mpageacc) nkeys diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/ChecksumHandle.hs b/lsm-tree/src-core/Database/LSMTree/Internal/ChecksumHandle.hs index f1f60a05c..31c537f6d 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/ChecksumHandle.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/ChecksumHandle.hs @@ -15,7 +15,6 @@ module Database.LSMTree.Internal.ChecksumHandle writeRawOverflowPages, writeBlob, copyBlob, - writeFilter, writeIndexHeader, writeIndexChunk, writeIndexFinal, @@ -29,7 +28,6 @@ import Data.Primitive.PrimVar import Data.Word (Word64) import Database.LSMTree.Internal.BlobRef (BlobSpan (..), RawBlobRef) import qualified Database.LSMTree.Internal.BlobRef as BlobRef -import Database.LSMTree.Internal.BloomFilter (Bloom, bloomFilterToLBS) import Database.LSMTree.Internal.Chunk (Chunk) import qualified Database.LSMTree.Internal.Chunk as Chunk (toByteString) import Database.LSMTree.Internal.CRC32C (CRC32C) @@ -37,8 +35,8 @@ import qualified Database.LSMTree.Internal.CRC32C as CRC import Database.LSMTree.Internal.Entry import Database.LSMTree.Internal.Index (Index, IndexType) import qualified Database.LSMTree.Internal.Index as Index (finalLBS, headerLBS) -import Database.LSMTree.Internal.Paths (ForBlob (..), ForFilter (..), - ForIndex (..), ForKOps (..)) +import Database.LSMTree.Internal.Paths (ForBlob (..), ForIndex (..), + ForKOps (..)) import qualified Database.LSMTree.Internal.RawBytes as RB import Database.LSMTree.Internal.RawOverflowPage (RawOverflowPage) import qualified Database.LSMTree.Internal.RawOverflowPage as RawOverflowPage @@ -188,20 +186,6 @@ copyBlob hfs blobOffset blobHandle blobref = do blob <- BlobRef.readRawBlobRef hfs blobref writeBlob hfs blobOffset blobHandle blob -{-# SPECIALISE writeFilter :: - HasFS IO h - -> ForFilter (ChecksumHandle RealWorld h) - -> Bloom SerialisedKey - -> IO () #-} -writeFilter :: - (MonadSTM m, PrimMonad m) - => HasFS m h - -> ForFilter (ChecksumHandle (PrimState m) h) - -> Bloom SerialisedKey - -> m () -writeFilter hfs filterHandle bf = - writeToHandle hfs (unForFilter filterHandle) (bloomFilterToLBS bf) - {-# SPECIALISE writeIndexHeader :: HasFS IO h -> ForIndex (ChecksumHandle RealWorld h) diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/Config.hs b/lsm-tree/src-core/Database/LSMTree/Internal/Config.hs index 4c1755a87..aabe752d4 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/Config.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/Config.hs @@ -16,7 +16,7 @@ module Database.LSMTree.Internal.Config ( , WriteBufferAlloc (..) -- * Bloom filter allocation , BloomFilterAlloc (..) - , bloomFilterAllocForLevel + , bloomFilterAllocForRun -- * Fence pointer index , FencePointerIndexType (..) , indexTypeForRun @@ -153,7 +153,7 @@ runParamsForLevel :: TableConfig -> RunLevelNo -> RunParams runParamsForLevel conf@TableConfig {..} levelNo = RunParams { runParamCaching = diskCachePolicyForLevel confDiskCachePolicy levelNo - , runParamAlloc = bloomFilterAllocForLevel conf levelNo + , runParamAlloc = bloomFilterAllocForRun conf , runParamIndex = indexTypeForRun confFencePointerIndex } @@ -291,8 +291,8 @@ instance NFData BloomFilterAlloc where rnf (AllocFixed n) = rnf n rnf (AllocRequestFPR fpr) = rnf fpr -bloomFilterAllocForLevel :: TableConfig -> RunLevelNo -> RunBloomFilterAlloc -bloomFilterAllocForLevel conf _levelNo = +bloomFilterAllocForRun :: TableConfig -> RunBloomFilterAlloc +bloomFilterAllocForRun conf = case confBloomFilterAlloc conf of AllocFixed n -> RunAllocFixed n AllocRequestFPR fpr -> RunAllocRequestFPR fpr diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/Config/Override.hs b/lsm-tree/src-core/Database/LSMTree/Internal/Config/Override.hs index 91e0c3ad6..a8033b4bb 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/Config/Override.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/Config/Override.hs @@ -175,8 +175,8 @@ instance Override RunDataCaching RunParams where instance Override RunDataCaching SnapshotRun where override rdc - (SnapshotRun (rn :: RunNumber) (_rdc :: RunDataCaching) (it ::IndexType)) - = SnapshotRun rn rdc it + (SnapshotRun (rn :: RunNumber) (_rdc :: RunDataCaching) (rbfa :: RunBloomFilterAlloc) (it ::IndexType)) + = SnapshotRun rn rdc rbfa it instance Override RunDataCaching (SnapMergingTree SnapshotRun) where override rdc (SnapMergingTree (smts :: SnapMergingTreeState SnapshotRun)) diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/Page.hs b/lsm-tree/src-core/Database/LSMTree/Internal/Page.hs index 52fc792ef..e5ecf914c 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/Page.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/Page.hs @@ -11,9 +11,24 @@ module Database.LSMTree.Internal.Page ( , singlePage , multiPage , pageSpanSize + -- * Constants + , pageSize + -- * I\/O + , readDiskPage + , skipPages ) where import Control.DeepSeq (NFData (..)) +import Control.Exception (assert) +import Control.Monad (guard) +import Control.Monad.Class.MonadThrow +import Control.Monad.Primitive (PrimMonad (..)) +import Data.Primitive.ByteArray +import Data.Word (Word32) +import Database.LSMTree.Internal.BitMath (roundUpToPageSize) +import Database.LSMTree.Internal.RawPage (RawPage, unsafeMakeRawPage) +import qualified System.FS.API as FS +import System.FS.API (Handle, HasFS) -- | A 0-based number identifying a disk page. newtype PageNo = PageNo { unPageNo :: Int } @@ -65,3 +80,48 @@ multiPage i j = PageSpan i j pageSpanSize :: PageSpan -> NumPages pageSpanSize pspan = NumPages . toEnum $ unPageNo (pageSpanEnd pspan) - unPageNo (pageSpanStart pspan) + 1 + +{------------------------------------------------------------------------------- + Constants +-------------------------------------------------------------------------------} + +pageSize :: Int +pageSize = 4096 + +{------------------------------------------------------------------------------- + I\/O +-------------------------------------------------------------------------------} + +{-# SPECIALISE readDiskPage :: + HasFS IO h + -> Handle h + -> IO (Maybe RawPage) #-} +-- | Returns 'Nothing' on EOF. +readDiskPage :: + (MonadCatch m, PrimMonad m) + => HasFS m h + -> Handle h + -> m (Maybe RawPage) +readDiskPage fs h = do + mba <- newPinnedByteArray pageSize + -- TODO: make sure no other exception type can be thrown + -- + -- TODO: if FS.FsReachEOF is thrown as an injected disk fault, then we + -- incorrectly deduce that the file has no more contents. We should probably + -- use an explicit file pointer instead in the style of 'FilePointer'. + handleJust (guard . FS.isFsErrorType FS.FsReachedEOF) (\_ -> pure Nothing) $ do + bytesRead <- FS.hGetBufExactly fs h mba 0 (fromIntegral pageSize) + assert (fromIntegral bytesRead == pageSize) $ pure () + ba <- unsafeFreezeByteArray mba + let !rawPage = unsafeMakeRawPage ba 0 + pure (Just rawPage) + +-- | Move the handle's file pointer the given number of pages +skipPages :: + HasFS m h + -> Handle h + -> Word32 + -> m () +skipPages fs h len = do + let lenPages = fromIntegral (roundUpToPageSize len) + FS.hSeek fs h FS.RelativeSeek lenPages diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/Paths.hs b/lsm-tree/src-core/Database/LSMTree/Internal/Paths.hs index 87c8439c9..c920624a8 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/Paths.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/Paths.hs @@ -27,7 +27,6 @@ module Database.LSMTree.Internal.Paths ( , pathsForRunFiles , runKOpsPath , runBlobPath - , runFilterPath , runIndexPath , runChecksumsPath -- * Checksums for Run files @@ -40,12 +39,10 @@ module Database.LSMTree.Internal.Paths ( -- * ForRunFiles abstraction , ForKOps (..) , ForBlob (..) - , ForFilter (..) , ForIndex (..) , ForRunFiles (..) , forRunKOpsRaw , forRunBlobRaw - , forRunFilterRaw , forRunIndexRaw -- * WriteBuffer paths , WriteBufferFsPaths (WrapRunFsPaths, WriteBufferFsPaths, writeBufferDir, writeBufferNumber) @@ -254,9 +251,6 @@ runKOpsPath = unForKOps . forRunKOps . pathsForRunFiles runBlobPath :: RunFsPaths -> FsPath runBlobPath = unForBlob . forRunBlob . pathsForRunFiles -runFilterPath :: RunFsPaths -> FsPath -runFilterPath = unForFilter . forRunFilter . pathsForRunFiles - runIndexPath :: RunFsPaths -> FsPath runIndexPath = unForIndex . forRunIndex . pathsForRunFiles @@ -271,7 +265,6 @@ runFileExts :: ForRunFiles String runFileExts = ForRunFiles { forRunKOps = ForKOps "keyops" , forRunBlob = ForBlob "blobs" - , forRunFilter = ForFilter "filter" , forRunIndex = ForIndex "index" } @@ -302,9 +295,6 @@ newtype ForKOps a = ForKOps {unForKOps :: a} newtype ForBlob a = ForBlob {unForBlob :: a} deriving stock (Show, Foldable, Functor, Traversable) -newtype ForFilter a = ForFilter {unForFilter :: a} - deriving stock (Show, Foldable, Functor, Traversable) - newtype ForIndex a = ForIndex {unForIndex :: a} deriving stock (Show, Foldable, Functor, Traversable) @@ -315,10 +305,9 @@ newtype ForIndex a = ForIndex {unForIndex :: a} -- | Stores something for each run file (except the checksums file), allowing to -- easily do something for all of them without mixing them up. data ForRunFiles a = ForRunFiles { - forRunKOps :: !(ForKOps a) - , forRunBlob :: !(ForBlob a) - , forRunFilter :: !(ForFilter a) - , forRunIndex :: !(ForIndex a) + forRunKOps :: !(ForKOps a) + , forRunBlob :: !(ForBlob a) + , forRunIndex :: !(ForIndex a) } deriving stock (Show, Foldable, Functor, Traversable) @@ -328,16 +317,13 @@ forRunKOpsRaw = unForKOps . forRunKOps forRunBlobRaw :: ForRunFiles a -> a forRunBlobRaw = unForBlob . forRunBlob -forRunFilterRaw :: ForRunFiles a -> a -forRunFilterRaw = unForFilter . forRunFilter - forRunIndexRaw :: ForRunFiles a -> a forRunIndexRaw = unForIndex . forRunIndex instance Applicative ForRunFiles where - pure x = ForRunFiles (ForKOps x) (ForBlob x) (ForFilter x) (ForIndex x) - ForRunFiles (ForKOps f1) (ForBlob f2) (ForFilter f3) (ForIndex f4) <*> ForRunFiles (ForKOps x1) (ForBlob x2) (ForFilter x3) (ForIndex x4) = - ForRunFiles (ForKOps $ f1 x1) (ForBlob $ f2 x2) (ForFilter $ f3 x3) (ForIndex $ f4 x4) + pure x = ForRunFiles (ForKOps x) (ForBlob x) (ForIndex x) + ForRunFiles (ForKOps f1) (ForBlob f2) (ForIndex f3) <*> ForRunFiles (ForKOps x1) (ForBlob x2) (ForIndex x3) = + ForRunFiles (ForKOps $ f1 x1) (ForBlob $ f2 x2) (ForIndex $ f3 x3) {------------------------------------------------------------------------------- WriteBuffer paths diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/Run.hs b/lsm-tree/src-core/Database/LSMTree/Internal/Run.hs index aea9f0e2f..e52895862 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/Run.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/Run.hs @@ -18,6 +18,7 @@ module Database.LSMTree.Internal.Run ( , runFsPaths , runFsPathsNumber , runDataCaching + , runFilterAlloc , runIndexType , mkRawBlobRef , mkWeakBlobRef @@ -33,20 +34,20 @@ module Database.LSMTree.Internal.Run ( ) where import Control.DeepSeq (NFData (..), rwhnf) -import Control.Monad.Class.MonadST (MonadST) +import Control.Monad.Class.MonadST (MonadST (stToIO)) import Control.Monad.Class.MonadSTM (MonadSTM (..)) import Control.Monad.Class.MonadThrow import Control.Monad.Primitive import Control.RefCount +import qualified Data.BloomFilter.Blocked as Bloom import qualified Data.ByteString.Short as SBS import Data.Foldable (for_) import Database.LSMTree.Internal.BlobFile import Database.LSMTree.Internal.BlobRef hiding (mkRawBlobRef, mkWeakBlobRef) import qualified Database.LSMTree.Internal.BlobRef as BlobRef -import Database.LSMTree.Internal.BloomFilter (Bloom, - bloomFilterFromFile) -import qualified Database.LSMTree.Internal.BloomFilter as Bloom +import Database.LSMTree.Internal.BloomFilter (Bloom) +import Database.LSMTree.Internal.BloomFilterAcc import qualified Database.LSMTree.Internal.CRC32C as CRC import Database.LSMTree.Internal.Entry (NumEntries (..)) import Database.LSMTree.Internal.Index (Index, IndexType (..)) @@ -57,6 +58,7 @@ import Database.LSMTree.Internal.RunBuilder (RunBuilder, RunDataCaching (..), RunParams (..)) import qualified Database.LSMTree.Internal.RunBuilder as Builder import Database.LSMTree.Internal.RunNumber +import qualified Database.LSMTree.Internal.RunReader.Keys as Keys import Database.LSMTree.Internal.Serialise import Database.LSMTree.Internal.WriteBuffer (WriteBuffer) import qualified Database.LSMTree.Internal.WriteBuffer as WB @@ -93,6 +95,7 @@ data Run m h = Run { -- I\/O, reading arbitrary file offset and length spans. , blobFile :: !(Ref (BlobFile m h)) , dataCaching :: !RunDataCaching + , filterAlloc :: !RunBloomFilterAlloc , hasFS :: !(HasFS m h) , hasBlockIO :: !(HasBlockIO m h) } @@ -102,10 +105,10 @@ instance Show (Run m h) where showsPrec _ run = showString "Run { fsPaths = " . showsPrec 0 run.fsPaths . showString " }" instance NFData h => NFData (Run m h) where - rnf (Run numEntries refCounter fsPaths bloomFilter index kOpsFile blobFile dataCaching hasFS hasBlockIO) = + rnf (Run numEntries refCounter fsPaths bloomFilter index kOpsFile blobFile dataCaching filterAlloc hasFS hasBlockIO) = rnf numEntries `seq` rwhnf refCounter `seq` rnf fsPaths `seq` rnf bloomFilter `seq` rnf index `seq` rnf kOpsFile `seq` - rnf blobFile `seq` rnf dataCaching `seq` rwhnf hasFS `seq` rwhnf hasBlockIO + rnf blobFile `seq` rnf dataCaching `seq` rnf filterAlloc `seq` rwhnf hasFS `seq` rwhnf hasBlockIO instance RefCounted m (Run m h) where getRefCounter r = r.refCounter @@ -130,6 +133,9 @@ runIndexType (DeRef r) = Index.indexToIndexType r.index runDataCaching :: Ref (Run m h) -> RunDataCaching runDataCaching (DeRef r) = r.dataCaching +-- | See 'openFromDisk' +runFilterAlloc :: Ref (Run m h) -> RunBloomFilterAlloc +runFilterAlloc (DeRef r) = r.filterAlloc -- | Helper function to make a 'WeakBlobRef' that points into a 'Run'. mkRawBlobRef :: Run m h -> BlobSpan -> RawBlobRef m h @@ -160,7 +166,6 @@ finaliser hfs kopsFile blobFile fsPaths = do FS.hClose hfs kopsFile releaseRef blobFile FS.removeFile hfs (runKOpsPath fsPaths) - FS.removeFile hfs (runFilterPath fsPaths) FS.removeFile hfs (runIndexPath fsPaths) FS.removeFile hfs (runChecksumsPath fsPaths) @@ -220,12 +225,12 @@ fromBuilder :: fromBuilder refCtx builder = do (runHasFS, runHasBlockIO, runRunFsPaths, runFilter, runIndex, - RunParams {runParamCaching = runRunDataCaching}, runNumEntries) <- + params, runNumEntries) <- Builder.unsafeFinalise builder runKOpsFile <- FS.hOpen runHasFS (runKOpsPath runRunFsPaths) FS.ReadMode -- TODO: openBlobFile should be called with exceptions masked runBlobFile <- openBlobFile runHasFS refCtx (runBlobPath runRunFsPaths) FS.ReadMode - setRunDataCaching runHasBlockIO runKOpsFile runRunDataCaching + setRunDataCaching runHasBlockIO runKOpsFile params.runParamCaching newRef refCtx (finaliser runHasFS runKOpsFile runBlobFile runRunFsPaths) (\refCounter -> Run { @@ -236,7 +241,8 @@ fromBuilder refCtx builder = do , index = runIndex , kOpsFile = runKOpsFile , blobFile = runBlobFile - , dataCaching = runRunDataCaching + , dataCaching = params.runParamCaching + , filterAlloc = params.runParamAlloc , hasFS = runHasFS , hasBlockIO = runHasBlockIO }) @@ -288,6 +294,7 @@ fromWriteBuffer fs hbio refCtx salt params fsPaths buffer blobs = do -> HasBlockIO IO h -> RefCtx -> RunDataCaching + -> RunBloomFilterAlloc -> IndexType -> Bloom.Salt -> RunFsPaths @@ -310,17 +317,18 @@ fromWriteBuffer fs hbio refCtx salt params fsPaths buffer blobs = do -- openFromDisk :: forall m h. - (MonadSTM m, MonadMask m, PrimMonad m) + (MonadSTM m, MonadMask m, MonadST m) => HasFS m h -> HasBlockIO m h -> RefCtx -> RunDataCaching + -> RunBloomFilterAlloc -> IndexType - -> Bloom.Salt -- ^ Expected salt + -> Bloom.Salt -> RunFsPaths -> m (Ref (Run m h)) -- TODO: make exception safe -openFromDisk fs hbio refCtx runRunDataCaching indexType expectedSalt runRunFsPaths = do +openFromDisk fs hbio refCtx runRunDataCaching filterAlloc indexType salt runRunFsPaths = do expectedChecksums <- CRC.expectValidFile fs (runChecksumsPath runRunFsPaths) CRC.FormatChecksumsFile . fromChecksumsFile @@ -332,16 +340,27 @@ openFromDisk fs hbio refCtx runRunDataCaching indexType expectedSalt runRunFsPat checkCRC runRunDataCaching (forRunBlobRaw expectedChecksums) (forRunBlobRaw paths) -- read and try parsing files - let filterPath = forRunFilterRaw paths - checkCRC CacheRunData (forRunFilterRaw expectedChecksums) filterPath - runFilter <- FS.withFile fs filterPath FS.ReadMode $ - bloomFilterFromFile fs expectedSalt - (runNumEntries, runIndex) <- CRC.expectValidFile fs (forRunIndexRaw paths) CRC.FormatIndexFile . Index.fromSBS indexType =<< readCRC (forRunIndexRaw expectedChecksums) (forRunIndexRaw paths) + let runInfo = Keys.RunInfo { + hasFS = fs + , hasBlockIO = hbio + , kOpsPath = runKOpsPath runRunFsPaths + , dataCaching = runRunDataCaching + , numPages = Index.sizeInPages runIndex + } + runFilter <- bracketOnError (Keys.unsafeNew runInfo) Keys.close $ \keys -> do + mbloom <- stToIO $ newMBloom runNumEntries filterAlloc salt + let loop = do + Keys.next keys >>= \case + Keys.Empty -> pure () + Keys.Key k -> stToIO (Bloom.insert mbloom k) >> loop + loop + stToIO $ Bloom.unsafeFreeze mbloom + runKOpsFile <- FS.hOpen fs (runKOpsPath runRunFsPaths) FS.ReadMode -- TODO: openBlobFile should be called with exceptions masked runBlobFile <- openBlobFile fs refCtx (runBlobPath runRunFsPaths) FS.ReadMode @@ -356,6 +375,7 @@ openFromDisk fs hbio refCtx runRunDataCaching indexType expectedSalt runRunFsPat , kOpsFile = runKOpsFile , blobFile = runBlobFile , dataCaching = runRunDataCaching + , filterAlloc = filterAlloc , hasFS = fs , hasBlockIO = hbio } diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/RunAcc.hs b/lsm-tree/src-core/Database/LSMTree/Internal/RunAcc.hs index 977510348..dc8a54f1c 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/RunAcc.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/RunAcc.hs @@ -33,7 +33,6 @@ module Database.LSMTree.Internal.RunAcc ( , newMBloom ) where -import Control.DeepSeq (NFData (..)) import Control.Exception (assert) import Control.Monad.ST.Strict import qualified Data.BloomFilter.Blocked as Bloom @@ -41,6 +40,8 @@ import Data.Primitive.PrimVar (PrimVar, modifyPrimVar, newPrimVar, readPrimVar) import Database.LSMTree.Internal.BlobRef (BlobSpan (..)) import Database.LSMTree.Internal.BloomFilter (Bloom, MBloom) +import Database.LSMTree.Internal.BloomFilterAcc + (RunBloomFilterAlloc (..), bloomInserts, newMBloom) import Database.LSMTree.Internal.Chunk (Chunk) import Database.LSMTree.Internal.Entry (Entry (..), NumEntries (..)) import Database.LSMTree.Internal.Index (Index, IndexAcc, IndexType) @@ -307,14 +308,6 @@ flushPageIfNonEmpty RunAcc{mpageacc, mindex, mbloom} = do else pure Nothing --- An instance of insertMany specialised to SerialisedKey and indexKeyPageAcc. --- This is a performance-sensitive function. It is marked NOINLINE so we can --- easily inspect the core and check all the specialisations worked as expected. -{-# NOINLINE bloomInserts #-} -bloomInserts :: MBloom s SerialisedKey -> PageAcc s -> Int -> ST s () -bloomInserts !mbloom !mpageacc !nkeys = - Bloom.insertMany mbloom (PageAcc.indexKeyPageAcc mpageacc) nkeys - -- | Internal helper for 'addLargeKeyOp' and 'addLargeSerialisedKeyOp'. -- Combine the result of 'flushPageIfNonEmpty' with extra pages and index -- chunks. @@ -328,28 +321,3 @@ selectPagesAndChunks mpagemchunkPre page chunks = Nothing -> ( [page], chunks) Just (pagePre, Nothing) -> ([pagePre, page], chunks) Just (pagePre, Just chunkPre) -> ([pagePre, page], chunkPre:chunks) - -{------------------------------------------------------------------------------- - Bloom filter allocation --------------------------------------------------------------------------------} - --- | See 'Database.LSMTree.Internal.Config.BloomFilterAlloc' -data RunBloomFilterAlloc = - -- | Bits per element in a filter - RunAllocFixed !Double - | RunAllocRequestFPR !Double - deriving stock (Show, Eq) - -instance NFData RunBloomFilterAlloc where - rnf (RunAllocFixed a) = rnf a - rnf (RunAllocRequestFPR a) = rnf a - -newMBloom :: NumEntries -> RunBloomFilterAlloc -> Bloom.Salt -> ST s (MBloom s a) -newMBloom (NumEntries nentries) alloc salt = - Bloom.new (Bloom.sizeForPolicy (policy alloc) nentries) salt - where - --TODO: it'd be possible to turn the RunBloomFilterAlloc into a BloomPolicy - -- without the NumEntries, and cache the policy, avoiding recalculating the - -- policy every time. - policy (RunAllocFixed bitsPerEntry) = Bloom.policyForBits bitsPerEntry - policy (RunAllocRequestFPR fpr) = Bloom.policyForFPR fpr diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/RunBuilder.hs b/lsm-tree/src-core/Database/LSMTree/Internal/RunBuilder.hs index 218187bc3..6c66e1403 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/RunBuilder.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/RunBuilder.hs @@ -219,15 +219,13 @@ unsafeFinalise RunBuilder {..} = do for_ mPage $ writeRawPage runBuilderHasFS (forRunKOps runBuilderHandles) for_ mChunk $ writeIndexChunk runBuilderHasFS (forRunIndex runBuilderHandles) writeIndexFinal runBuilderHasFS (forRunIndex runBuilderHandles) numEntries runIndex - writeFilter runBuilderHasFS (forRunFilter runBuilderHandles) runFilter -- write checksums checksums <- toChecksumsFile <$> traverse readChecksum runBuilderHandles FS.withFile runBuilderHasFS (runChecksumsPath runBuilderFsPaths) (FS.WriteMode FS.MustBeNew) $ \h -> do CRC.writeChecksumsFile' runBuilderHasFS h checksums -- always drop the checksum file from the cache FS.hDropCacheAll runBuilderHasBlockIO h - -- always drop filter and index files from the cache - dropCache runBuilderHasBlockIO (forRunFilterRaw runBuilderHandles) + -- always drop index files from the cache dropCache runBuilderHasBlockIO (forRunIndexRaw runBuilderHandles) -- drop the KOps and blobs files from the cache if asked for when (runParamCaching runBuilderParams == NoCacheRunData) $ do diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/RunReader.hs b/lsm-tree/src-core/Database/LSMTree/Internal/RunReader.hs index febe20dda..18701600f 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/RunReader.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/RunReader.hs @@ -17,16 +17,15 @@ module Database.LSMTree.Internal.RunReader ( , appendOverflow -- * Exported for WriteBufferReader , mkEntryOverflow - , readDiskPage , readOverflowPages ) where import Control.Exception (assert) -import Control.Monad (guard, when) +import Control.Monad (when) import Control.Monad.Class.MonadST (MonadST (..)) import Control.Monad.Class.MonadSTM (MonadSTM (..)) -import Control.Monad.Class.MonadThrow (MonadCatch (..), - MonadMask (..), MonadThrow (..)) +import Control.Monad.Class.MonadThrow (MonadMask (..), + MonadThrow (..)) import Control.Monad.Primitive (PrimMonad (..)) import Control.RefCount import Data.Bifunctor (first) @@ -44,7 +43,7 @@ import Database.LSMTree.Internal.BlobRef as BlobRef import qualified Database.LSMTree.Internal.Entry as E import qualified Database.LSMTree.Internal.Index as Index (search) import Database.LSMTree.Internal.Page (PageNo (..), PageSpan (..), - getNumPages, nextPageNo) + getNumPages, nextPageNo, readDiskPage) import Database.LSMTree.Internal.Paths import qualified Database.LSMTree.Internal.RawBytes as RB import Database.LSMTree.Internal.RawOverflowPage (RawOverflowPage, @@ -300,30 +299,6 @@ seekToDiskPage fs pageNo h = do assert (n >= 0) $ mulPageSize (fromIntegral n) -{-# SPECIALISE readDiskPage :: - HasFS IO h - -> FS.Handle h - -> IO (Maybe RawPage) #-} --- | Returns 'Nothing' on EOF. -readDiskPage :: - (MonadCatch m, PrimMonad m) - => HasFS m h - -> FS.Handle h - -> m (Maybe RawPage) -readDiskPage fs h = do - mba <- newPinnedByteArray pageSize - -- TODO: make sure no other exception type can be thrown - -- - -- TODO: if FS.FsReachEOF is thrown as an injected disk fault, then we - -- incorrectly deduce that the file has no more contents. We should probably - -- use an explicit file pointer instead in the style of 'FilePointer'. - handleJust (guard . FS.isFsErrorType FS.FsReachedEOF) (\_ -> pure Nothing) $ do - bytesRead <- FS.hGetBufExactly fs h mba 0 (fromIntegral pageSize) - assert (fromIntegral bytesRead == pageSize) $ pure () - ba <- unsafeFreezeByteArray mba - let !rawPage = unsafeMakeRawPage ba 0 - pure (Just rawPage) - pageSize :: Int pageSize = 4096 diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/RunReader/Keys.hs b/lsm-tree/src-core/Database/LSMTree/Internal/RunReader/Keys.hs new file mode 100644 index 000000000..45261f5a6 --- /dev/null +++ b/lsm-tree/src-core/Database/LSMTree/Internal/RunReader/Keys.hs @@ -0,0 +1,158 @@ +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE NoFieldSelectors #-} + +{-# OPTIONS_HADDOCK not-home #-} + +module Database.LSMTree.Internal.RunReader.Keys ( + Keys (..) + , unsafeNew + , RunInfo (..) + , close + , next + , Result (..) + ) where + +import Control.Exception (assert) +import Control.Monad (when) +import Control.Monad.Class.MonadST (MonadST (..)) +import Control.Monad.Class.MonadSTM (MonadSTM (..)) +import Control.Monad.Class.MonadThrow (MonadCatch (..), + MonadMask (..)) +import Control.Monad.Primitive (PrimMonad (..)) +import Data.Maybe (isNothing) +import Data.Primitive.MutVar (MutVar, newMutVar, readMutVar, + writeMutVar) +import Data.Primitive.PrimVar (PrimVar, modifyPrimVar, newPrimVar, + readPrimVar, writePrimVar) +import Data.Word (Word16) +import Database.LSMTree.Internal.Page (NumPages, getNumPages, + pageSize, readDiskPage, skipPages) +import Database.LSMTree.Internal.RawPage (RawPage, + RawPageIndex (IndexEntry, IndexEntryOverflow, IndexNotPresent), + rawPageIndex) +import Database.LSMTree.Internal.RunBuilder (RunDataCaching (..)) +import Database.LSMTree.Internal.Serialise (SerialisedKey) +import qualified System.FS.API as FS +import System.FS.API (FsPath, Handle, HasFS) +import qualified System.FS.BlockIO.API as FS +import System.FS.BlockIO.API (HasBlockIO) + +data Keys m h = Keys { + currentPage :: !(MutVar (PrimState m) (Maybe RawPage)) + , currentEntryNo :: !(PrimVar (PrimState m) Word16) + , kOpsHandle :: !(Handle h) + , dataCaching :: !RunDataCaching + , hasFS :: !(HasFS m h) + , hasBlockIO :: !(HasBlockIO m h) + } + +data RunInfo m h = RunInfo { + hasFS :: HasFS m h + , hasBlockIO :: HasBlockIO m h + , kOpsPath :: FsPath + , dataCaching :: RunDataCaching + , numPages :: NumPages + } + +{-# SPECIALISE openKOpsFile :: + RunInfo IO h + -> IO (Handle h) #-} +openKOpsFile :: + MonadCatch m + => RunInfo m h + -> m (Handle h) +openKOpsFile runInfo = bracketOnError acq rel $ \h -> do + fileSize <- FS.hGetSize runInfo.hasFS h + let fileSizeInPages = fileSize `div` toEnum pageSize + let indexedPages = getNumPages runInfo.numPages + assert (indexedPages == fileSizeInPages) $ pure () + pure h + where + acq = FS.hOpen runInfo.hasFS runInfo.kOpsPath FS.ReadMode + rel = FS.hClose runInfo.hasFS + +{-# SPECIALISE unsafeNew :: + RunInfo IO h + -> IO (Keys IO h) #-} +unsafeNew :: forall m h. + (MonadMask m, MonadSTM m, PrimMonad m) + => RunInfo m h + -> m (Keys m h) +unsafeNew runInfo = do + bracketOnError (openKOpsFile runInfo) (FS.hClose runInfo.hasFS) $ \kOpsHandle -> do + + FS.hAdviseAll runInfo.hasBlockIO kOpsHandle FS.AdviceSequential + + -- Load first page from disk, if it exists. + page <- readDiskPage runInfo.hasFS kOpsHandle + + currentEntryNo <- newPrimVar 0 + currentPage <- newMutVar page + let reader = Keys { + currentPage = currentPage + , currentEntryNo = currentEntryNo + , kOpsHandle = kOpsHandle + , dataCaching = runInfo.dataCaching + , hasFS = runInfo.hasFS + , hasBlockIO = runInfo.hasBlockIO + } + + when (isNothing page) $ + close reader + pure reader + +{-# SPECIALISE close :: + Keys IO h + -> IO () #-} +close :: + (MonadSTM m) + => Keys m h + -> m () +close r = do + when (r.dataCaching == NoCacheRunData) $ + FS.hDropCacheAll r.hasBlockIO r.kOpsHandle + FS.hClose r.hasFS r.kOpsHandle + +data Result m h + = Empty + | Key !SerialisedKey + +{-# SPECIALISE next :: + Keys IO h + -> IO (Result IO h) #-} +-- | Stop using the 'RunReader' after getting 'Empty', because the 'Reader' is +-- automatically closed! +next :: forall m h. + (MonadMask m, MonadSTM m, MonadST m) + => Keys m h + -> m (Result m h) +next reader = do + readMutVar reader.currentPage >>= \case + Nothing -> + pure Empty + Just page -> do + entryNo <- readPrimVar reader.currentEntryNo + go entryNo page + where + go :: Word16 -> RawPage -> m (Result m h) + go !entryNo !page = + -- take entry from current page (resolve blob if necessary) + case rawPageIndex page entryNo of + IndexNotPresent -> do + -- if it is past the last one, load a new page from disk, try again + newPage <- readDiskPage reader.hasFS reader.kOpsHandle + stToIO $ writeMutVar reader.currentPage newPage + case newPage of + Nothing -> do + close reader + pure Empty + Just p -> do + writePrimVar reader.currentEntryNo 0 + go 0 p -- try again on the new page + IndexEntry key _entry -> do + modifyPrimVar reader.currentEntryNo (+1) + pure $ Key key + IndexEntryOverflow key _entry lenSuffix -> do + modifyPrimVar reader.currentEntryNo (+1) + skipPages reader.hasFS reader.kOpsHandle lenSuffix + pure $ Key key diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot.hs b/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot.hs index 812a9f798..29cd3905d 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot.hs @@ -48,6 +48,7 @@ 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.BloomFilterAcc (RunBloomFilterAlloc) import Database.LSMTree.Internal.Config import Database.LSMTree.Internal.CRC32C (checkCRC) import qualified Database.LSMTree.Internal.CRC32C as CRC @@ -545,12 +546,13 @@ openWriteBuffer reg resolve hfs hbio refCtx uc activeDir snapWriteBufferPaths = data SnapshotRun = SnapshotRun { snapRunNumber :: !RunNumber, snapRunCaching :: !Run.RunDataCaching, + filterAlloc :: !RunBloomFilterAlloc, snapRunIndex :: !Run.IndexType } deriving stock Eq instance NFData SnapshotRun where - rnf (SnapshotRun a b c) = rnf a `seq` rnf b `seq` rnf c + rnf (SnapshotRun a b c d) = rnf a `seq` rnf b `seq` rnf c `seq` rnf d {-# SPECIALISE snapshotRun :: HasFS IO h @@ -580,6 +582,7 @@ snapshotRun hfs hbio snapUc reg (NamedSnapshotDir targetDir) run = do pure SnapshotRun { snapRunNumber = runNumber targetPaths, snapRunCaching = Run.runDataCaching run, + filterAlloc = Run.runFilterAlloc run, snapRunIndex = Run.runIndexType run } @@ -615,10 +618,11 @@ openRun :: -> m (Ref (Run m h)) openRun hfs hbio refCtx uc reg (NamedSnapshotDir sourceDir) (ActiveDir targetDir) - expectedSalt + salt SnapshotRun { snapRunNumber = runNum, snapRunCaching = caching, + filterAlloc = filterAlloc, snapRunIndex = indexType } = do let sourcePaths = RunFsPaths sourceDir runNum @@ -627,7 +631,7 @@ openRun hfs hbio refCtx uc reg hardLinkRunFiles hfs hbio reg sourcePaths targetPaths withRollback reg - (Run.openFromDisk hfs hbio refCtx caching indexType expectedSalt targetPaths) + (Run.openFromDisk hfs hbio refCtx caching filterAlloc indexType salt targetPaths) releaseRef {------------------------------------------------------------------------------- diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot/Codec.hs b/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot/Codec.hs index a548665dc..3bd620fbc 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot/Codec.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot/Codec.hs @@ -1,4 +1,5 @@ {-# OPTIONS_HADDOCK not-home #-} +{-# LANGUAGE UndecidableInstances #-} -- | Encoders and decoders for snapshot metadata -- @@ -16,15 +17,20 @@ module Database.LSMTree.Internal.Snapshot.Codec ( , Encode (..) , Decode (..) , DecodeVersioned (..) + , NoCtx (..) , Versioned (..) ) where -import Codec.CBOR.Decoding +import Codec.CBOR.Decoding hiding (liftST) import Codec.CBOR.Encoding import Codec.CBOR.Read import Codec.CBOR.Write +import Control.DeepSeq (NFData) +import Control.Exception (assert) +import Control.Monad (when) import Control.Monad.Class.MonadThrow (Exception (displayException), MonadThrow (..)) +import Control.Monad.Reader (MonadReader (ask)) import Data.Bifunctor (Bifunctor (..)) import qualified Data.ByteString.Char8 as BSC import Data.ByteString.Lazy (ByteString) @@ -40,6 +46,8 @@ import Database.LSMTree.Internal.RunBuilder (IndexType (..), RunParams (..)) import Database.LSMTree.Internal.RunNumber import Database.LSMTree.Internal.Snapshot +import Database.LSMTree.Internal.Snapshot.Codec.Monad +import GHC.Generics (Generic) import qualified System.FS.API as FS import System.FS.API (FsPath, HasFS) import Text.Printf @@ -58,34 +66,35 @@ import Text.Printf -- for more. Forwards compatibility is not provided at all: snapshots with a -- later version than the current version for the library release will always -- fail. -data SnapshotVersion = V0 | V1 | V2 +data SnapshotVersion = V0 | V1 | V2 | V3 deriving stock (Show, Eq, Ord) -- | Pretty-print a snapshot version -- -- >>> prettySnapshotVersion currentSnapshotVersion --- "v2" +-- "v3" prettySnapshotVersion :: SnapshotVersion -> String prettySnapshotVersion V0 = "v0" prettySnapshotVersion V1 = "v1" prettySnapshotVersion V2 = "v2" +prettySnapshotVersion V3 = "v3" -- | The current snapshot version -- -- >>> currentSnapshotVersion --- V2 +-- V3 currentSnapshotVersion :: SnapshotVersion -currentSnapshotVersion = V2 +currentSnapshotVersion = V3 -- | All snapshot versions that the current snapshot version is compatible with. -- -- >>> allCompatibleSnapshotVersions --- [V0,V1,V2] +-- [V0,V1,V2,V3] -- -- >>> last allCompatibleSnapshotVersions == currentSnapshotVersion -- True allCompatibleSnapshotVersions :: [SnapshotVersion] -allCompatibleSnapshotVersions = [V0, V1, V2] +allCompatibleSnapshotVersions = [V0, V1, V2, V3] isCompatible :: SnapshotVersion -> Either String () isCompatible otherVersion @@ -134,6 +143,7 @@ encodeSnapshotMetaData = toLazyByteString . encode . Versioned HasFS IO h -> FsPath -> FsPath + -> Bool -> IO SnapshotMetaData #-} -- | Read from 'SnapshotMetaDataFile' and attempt to decode it to @@ -143,8 +153,9 @@ readFileSnapshotMetaData :: => HasFS m h -> FsPath -- ^ Source file for snapshot metadata -> FsPath -- ^ Source file for checksum + -> Bool -- ^ Run V3 assertions -> m SnapshotMetaData -readFileSnapshotMetaData hfs contentPath checksumPath = do +readFileSnapshotMetaData hfs contentPath checksumPath v3Assert = do checksumFile <- readChecksumsFile hfs checksumPath let checksumFileName = ChecksumsFileName (BSC.pack "metadata") @@ -156,10 +167,13 @@ readFileSnapshotMetaData hfs contentPath checksumPath = do expectChecksum hfs contentPath expectedChecksum actualChecksum - expectValidFile hfs contentPath FormatSnapshotMetaData (decodeSnapshotMetaData lbs) + expectValidFile hfs contentPath FormatSnapshotMetaData (decodeSnapshotMetaData lbs v3Assert) -decodeSnapshotMetaData :: ByteString -> Either String SnapshotMetaData -decodeSnapshotMetaData lbs = bimap displayException (getVersioned . snd) (deserialiseFromBytes decode lbs) +decodeSnapshotMetaData :: ByteString -> Bool -> Either String SnapshotMetaData +decodeSnapshotMetaData lbs v3Assert = bimap displayException (getVersioned . snd) (deserialiseFromBytes dec lbs) + where + dec :: forall s. Decoder s (Versioned SnapshotMetaData) + dec = runDec decode (Env v3Assert) {------------------------------------------------------------------------------- Encoding and decoding @@ -173,13 +187,20 @@ class Encode a where -- Used only for 'SnapshotVersion' and 'Versioned', which live outside the -- 'SnapshotMetaData' type hierarchy. class Decode a where - decode :: Decoder s a + decode :: Dec s a -- | Decoder parameterised by a 'SnapshotVersion'. -- -- Used for every type in the 'SnapshotMetaData' type hierarchy. -class DecodeVersioned a where - decodeVersioned :: SnapshotVersion -> Decoder s a +class DecodeVersioned ctx a where + decodeVersionedWith :: SnapshotVersion -> ctx -> Dec s a + +decodeVersioned :: DecodeVersioned NoCtx a => SnapshotVersion -> Dec s a +decodeVersioned v = decodeVersionedWith v NoCtx + +data NoCtx = NoCtx + deriving stock (Show, Eq, Generic) + deriving anyclass NFData newtype Versioned a = Versioned { getVersioned :: a } deriving stock (Show, Eq) @@ -192,9 +213,9 @@ instance Encode a => Encode (Versioned a) where -- | Decodes a 'SnapshotVersion' first, and then passes that into the versioned -- decoder for @a@. -instance DecodeVersioned a => Decode (Versioned a) where +instance DecodeVersioned NoCtx a => Decode (Versioned a) where decode = do - _ <- decodeListLenOf 2 + _ <- liftDecoder $ decodeListLenOf 2 version <- decode case isCompatible version of Right () -> pure () @@ -218,15 +239,17 @@ instance Encode SnapshotVersion where V0 -> encodeWord 0 V1 -> encodeWord 1 V2 -> encodeWord 2 + V3 -> encodeWord 3 instance Decode SnapshotVersion where decode = do - _ <- decodeListLenOf 1 - ver <- decodeWord + _ <- liftDecoder $ decodeListLenOf 1 + ver <- liftDecoder decodeWord case ver of 0 -> pure V0 1 -> pure V1 2 -> pure V2 + 3 -> pure V3 _ -> fail ("Unknown snapshot format version number: " <> show ver) {------------------------------------------------------------------------------- @@ -244,42 +267,74 @@ instance Encode SnapshotMetaData where <> encode levels <> encodeMaybe mergingTree -instance DecodeVersioned SnapshotMetaData where - decodeVersioned ver = do - _ <- decodeListLenOf 5 - SnapshotMetaData - <$> decodeVersioned ver - <*> decodeVersioned ver - <*> decodeVersioned ver - <*> decodeVersioned ver - <*> decodeMaybe ver +instance DecodeVersioned NoCtx SnapshotMetaData where + decodeVersionedWith ver NoCtx = do + _ <- liftDecoder $ decodeListLenOf 5 + label <- decodeVersioned ver + config <- decodeVersioned ver + writeBuffer <- decodeVersioned ver + levels <- decodeVersionedWith ver config + mergingTree <- decodeMaybe (decodeVersionedWith ver config) + pure SnapshotMetaData { + snapMetaLabel = label + , snapMetaConfig = config + , snapWriteBuffer = writeBuffer + , snapMetaLevels = levels + , snapMergingTree = mergingTree + } -- SnapshotLabel instance Encode SnapshotLabel where encode (SnapshotLabel s) = encodeString s -instance DecodeVersioned SnapshotLabel where - decodeVersioned _v = SnapshotLabel <$> decodeString +instance DecodeVersioned NoCtx SnapshotLabel where + decodeVersionedWith _v NoCtx = SnapshotLabel <$> liftDecoder decodeString instance Encode SnapshotRun where - encode SnapshotRun { snapRunNumber, snapRunCaching, snapRunIndex } = - encodeListLen 4 + encode SnapshotRun { snapRunNumber, snapRunCaching, filterAlloc, snapRunIndex } = + encodeListLen 5 <> encodeWord 0 <> encode snapRunNumber <> encode snapRunCaching + <> encode filterAlloc <> encode snapRunIndex -instance DecodeVersioned SnapshotRun where - decodeVersioned v = do - n <- decodeListLen - tag <- decodeWord - case (n, tag) of - (4, 0) -> do snapRunNumber <- decodeVersioned v - snapRunCaching <- decodeVersioned v - snapRunIndex <- decodeVersioned v - pure SnapshotRun{..} +instance DecodeVersioned TableConfig SnapshotRun where + decodeVersionedWith v conf = do + env <- ask + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord + case (v, n, tag) of + -- In older snapshot versions, we stored the serialised representation + -- of a bloom filter in a file. Since V3, we instead reconstruct the + -- bloom filter from the keys in the run's keyops file. For this + -- reconstruction, we store the @RunBloomFilterAlloc@ in the + -- 'SnapshotRun'. + (V3, 5, 0) -> do + snapRunNumber <- decodeVersioned v + snapRunCaching <- decodeVersioned v + filterAlloc <- decodeVersioned v + -- Sanity check: the synthesised filter allocation for snapshot + -- versions <=V2 should match the filter allocation stored in the + -- snapshot. + when env.v3Assert $ do + assert (filterAlloc == filterAllocSynthesised) $ pure () + snapRunIndex <- decodeVersioned v + pure SnapshotRun{..} + -- If we open a snapshot that was created with V2 or earlier, then the + -- snapshot format does not include @RunBloomFilterAlloc@ information. + -- We synthesise that information in @filterAllocSynthesised@. The bloom + -- filter that is serialised to file is ignored. + (_, 4, 0) | v <= V2 -> do + snapRunNumber <- decodeVersioned v + snapRunCaching <- decodeVersioned v + let filterAlloc = filterAllocSynthesised + snapRunIndex <- decodeVersioned v + pure SnapshotRun{..} _ -> fail ("[SnapshotRun] Unexpected combination of list length and tag: " <> show (n, tag)) + where + filterAllocSynthesised = bloomFilterAllocForRun conf {------------------------------------------------------------------------------- Encoding and decoding: TableConfig @@ -310,11 +365,11 @@ instance Encode TableConfig where <> encode diskCachePolicy <> encode mergeBatchSize -instance DecodeVersioned TableConfig where - decodeVersioned v +instance DecodeVersioned NoCtx TableConfig where + decodeVersionedWith v NoCtx | v < V1 = do -- For backwards compatibility. We added confMergeBatchSize in V1. - decodeListLenOf 7 + liftDecoder $ decodeListLenOf 7 confMergePolicy <- decodeVersioned v confMergeSchedule <- decodeVersioned v confSizeRatio <- decodeVersioned v @@ -327,7 +382,7 @@ instance DecodeVersioned TableConfig where pure TableConfig {..} | otherwise = do - decodeListLenOf 8 + liftDecoder $ decodeListLenOf 8 confMergePolicy <- decodeVersioned v confMergeSchedule <- decodeVersioned v confSizeRatio <- decodeVersioned v @@ -343,9 +398,9 @@ instance DecodeVersioned TableConfig where instance Encode MergePolicy where encode LazyLevelling = encodeWord 0 -instance DecodeVersioned MergePolicy where - decodeVersioned _v = do - tag <- decodeWord +instance DecodeVersioned NoCtx MergePolicy where + decodeVersionedWith _v NoCtx = do + tag <- liftDecoder decodeWord case tag of 0 -> pure LazyLevelling _ -> fail ("[MergePolicy] Unexpected tag: " <> show tag) @@ -355,9 +410,9 @@ instance DecodeVersioned MergePolicy where instance Encode SizeRatio where encode Four = encodeInt 4 -instance DecodeVersioned SizeRatio where - decodeVersioned _v = do - x <- decodeWord64 +instance DecodeVersioned NoCtx SizeRatio where + decodeVersionedWith _v NoCtx = do + x <- liftDecoder decodeWord64 case x of 4 -> pure Four _ -> fail ("Expected 4, but found " <> show x) @@ -370,12 +425,12 @@ instance Encode WriteBufferAlloc where <> encodeWord 0 <> encodeInt numEntries -instance DecodeVersioned WriteBufferAlloc where - decodeVersioned _v = do - _ <- decodeListLenOf 2 - tag <- decodeWord +instance DecodeVersioned NoCtx WriteBufferAlloc where + decodeVersionedWith _v NoCtx = do + _ <- liftDecoder $ decodeListLenOf 2 + tag <- liftDecoder decodeWord case tag of - 0 -> AllocNumEntries <$> decodeInt + 0 -> AllocNumEntries <$> liftDecoder decodeInt _ -> fail ("[WriteBufferAlloc] Unexpected tag: " <> show tag) -- RunParams and friends @@ -388,10 +443,10 @@ instance Encode RunParams where <> encode runParamAlloc <> encode runParamIndex -instance DecodeVersioned RunParams where - decodeVersioned v = do - n <- decodeListLen - tag <- decodeWord +instance DecodeVersioned NoCtx RunParams where + decodeVersionedWith v NoCtx = do + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord case (n, tag) of (4, 0) -> do runParamCaching <- decodeVersioned v runParamAlloc <- decodeVersioned v @@ -403,9 +458,9 @@ instance Encode RunDataCaching where encode CacheRunData = encodeWord 0 encode NoCacheRunData = encodeWord 1 -instance DecodeVersioned RunDataCaching where - decodeVersioned _v = do - tag <- decodeWord +instance DecodeVersioned NoCtx RunDataCaching where + decodeVersionedWith _v NoCtx = do + tag <- liftDecoder decodeWord case tag of 0 -> pure CacheRunData 1 -> pure NoCacheRunData @@ -415,9 +470,9 @@ instance Encode IndexType where encode Ordinary = encodeWord 0 encode Compact = encodeWord 1 -instance DecodeVersioned IndexType where - decodeVersioned _v = do - tag <- decodeWord +instance DecodeVersioned NoCtx IndexType where + decodeVersionedWith _v NoCtx = do + tag <- liftDecoder decodeWord case tag of 0 -> pure Ordinary 1 -> pure Compact @@ -433,13 +488,13 @@ instance Encode RunBloomFilterAlloc where <> encodeWord 1 <> encodeDouble fpr -instance DecodeVersioned RunBloomFilterAlloc where - decodeVersioned _v = do - n <- decodeListLen - tag <- decodeWord +instance DecodeVersioned NoCtx RunBloomFilterAlloc where + decodeVersionedWith _v NoCtx = do + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord case (n, tag) of - (2, 0) -> RunAllocFixed <$> decodeDouble - (2, 1) -> RunAllocRequestFPR <$> decodeDouble + (2, 0) -> RunAllocFixed <$> liftDecoder decodeDouble + (2, 1) -> RunAllocRequestFPR <$> liftDecoder decodeDouble _ -> fail ("[RunBloomFilterAlloc] Unexpected combination of list length and tag: " <> show (n, tag)) -- BloomFilterAlloc @@ -454,13 +509,13 @@ instance Encode BloomFilterAlloc where <> encodeWord 1 <> encodeDouble x -instance DecodeVersioned BloomFilterAlloc where - decodeVersioned _v = do - n <- decodeListLen - tag <- decodeWord +instance DecodeVersioned NoCtx BloomFilterAlloc where + decodeVersionedWith _v NoCtx = do + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord case (n, tag) of - (2, 0) -> AllocFixed <$> decodeDouble - (2, 1) -> AllocRequestFPR <$> decodeDouble + (2, 0) -> AllocFixed <$> liftDecoder decodeDouble + (2, 1) -> AllocRequestFPR <$> liftDecoder decodeDouble _ -> fail ("[BloomFilterAlloc] Unexpected combination of list length and tag: " <> show (n, tag)) -- FencePointerIndexType @@ -469,9 +524,9 @@ instance Encode FencePointerIndexType where encode CompactIndex = encodeWord 0 encode OrdinaryIndex = encodeWord 1 -instance DecodeVersioned FencePointerIndexType where - decodeVersioned _v = do - tag <- decodeWord +instance DecodeVersioned NoCtx FencePointerIndexType where + decodeVersionedWith _v NoCtx = do + tag <- liftDecoder decodeWord case tag of 0 -> pure CompactIndex 1 -> pure OrdinaryIndex @@ -491,13 +546,13 @@ instance Encode DiskCachePolicy where encodeListLen 1 <> encodeWord 2 -instance DecodeVersioned DiskCachePolicy where - decodeVersioned _v = do - n <- decodeListLen - tag <- decodeWord +instance DecodeVersioned NoCtx DiskCachePolicy where + decodeVersionedWith _v NoCtx = do + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord case (n, tag) of (1, 0) -> pure DiskCacheAll - (2, 1) -> DiskCacheLevelOneTo <$> decodeInt + (2, 1) -> DiskCacheLevelOneTo <$> liftDecoder decodeInt (1, 2) -> pure DiskCacheNone _ -> fail ("[DiskCachePolicy] Unexpected combination of list length and tag: " <> show (n, tag)) @@ -507,9 +562,9 @@ instance Encode MergeSchedule where encode OneShot = encodeWord 0 encode Incremental = encodeWord 1 -instance DecodeVersioned MergeSchedule where - decodeVersioned _v = do - tag <- decodeWord +instance DecodeVersioned NoCtx MergeSchedule where + decodeVersionedWith _v NoCtx = do + tag <- liftDecoder decodeWord case tag of 0 -> pure OneShot 1 -> pure Incremental @@ -520,8 +575,8 @@ instance DecodeVersioned MergeSchedule where instance Encode MergeBatchSize where encode (MergeBatchSize n) = encodeInt n -instance DecodeVersioned MergeBatchSize where - decodeVersioned _v = MergeBatchSize <$> decodeInt +instance DecodeVersioned NoCtx MergeBatchSize where + decodeVersionedWith _v NoCtx = MergeBatchSize <$> liftDecoder decodeInt {------------------------------------------------------------------------------- Encoding and decoding: SnapLevels @@ -532,8 +587,8 @@ instance DecodeVersioned MergeBatchSize where instance Encode r => Encode (SnapLevels r) where encode (SnapLevels levels) = encode levels -instance DecodeVersioned r => DecodeVersioned (SnapLevels r) where - decodeVersioned v = SnapLevels <$> decodeVersioned v +instance DecodeVersioned ctx r => DecodeVersioned ctx (SnapLevels r) where + decodeVersionedWith v ctx = SnapLevels <$> decodeVersionedWith v ctx -- SnapLevel @@ -548,19 +603,19 @@ instance Encode r => Encode (SnapLevel r) where <> encode residentRuns -instance DecodeVersioned r => DecodeVersioned (SnapLevel r) where - decodeVersioned v +instance DecodeVersioned ctx r => DecodeVersioned ctx (SnapLevel r) where + decodeVersionedWith v ctx | v < V2 = do -- For backwards compatibility. There were no empty levels before V2. - _ <- decodeListLenOf 2 - SnapLevel <$> decodeVersioned v <*> decodeVersioned v + _ <- liftDecoder $ decodeListLenOf 2 + SnapLevel <$> decodeVersionedWith v ctx <*> decodeVersionedWith v ctx | otherwise = do - n <- decodeListLen - tag <- decodeWord + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord case (n, tag) of (1, 0) -> pure SnapEmptyLevel - (3, 1) -> SnapLevel <$> decodeVersioned v <*> decodeVersioned v + (3, 1) -> SnapLevel <$> decodeVersionedWith v ctx <*> decodeVersionedWith v ctx _ -> fail ("[SnapLevel] Unexpected combination of list length and tag: " <> show (n, tag)) -- Vector @@ -568,16 +623,16 @@ instance DecodeVersioned r => DecodeVersioned (SnapLevel r) where instance Encode r => Encode (V.Vector r) where encode = encodeVector -instance DecodeVersioned r => DecodeVersioned (V.Vector r) where - decodeVersioned = decodeVector +instance DecodeVersioned ctx r => DecodeVersioned ctx (V.Vector r) where + decodeVersionedWith v ctx = decodeVector (decodeVersionedWith v ctx) -- RunNumber instance Encode RunNumber where encode (RunNumber x) = encodeInt x -instance DecodeVersioned RunNumber where - decodeVersioned _v = RunNumber <$> decodeInt +instance DecodeVersioned NoCtx RunNumber where + decodeVersionedWith _v NoCtx = RunNumber <$> liftDecoder decodeInt -- SnapIncomingRun @@ -594,15 +649,15 @@ instance Encode r => Encode (SnapIncomingRun r) where <> encodeWord 1 <> encode x -instance DecodeVersioned r => DecodeVersioned (SnapIncomingRun r) where - decodeVersioned v = do - n <- decodeListLen - tag <- decodeWord +instance DecodeVersioned ctx r => DecodeVersioned ctx (SnapIncomingRun r) where + decodeVersionedWith v ctx = do + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord case (n, tag) of (5, 0) -> SnapIncomingMergingRun <$> decodeVersioned v <*> decodeVersioned v - <*> decodeVersioned v <*> decodeVersioned v - (2, 1) -> SnapIncomingSingleRun <$> decodeVersioned v + <*> decodeVersioned v <*> decodeVersionedWith v ctx + (2, 1) -> SnapIncomingSingleRun <$> decodeVersionedWith v ctx _ -> fail ("[SnapIncomingRun] Unexpected combination of list length and tag: " <> show (n, tag)) -- MergePolicyForLevel @@ -611,9 +666,9 @@ instance Encode MergePolicyForLevel where encode LevelTiering = encodeWord 0 encode LevelLevelling = encodeWord 1 -instance DecodeVersioned MergePolicyForLevel where - decodeVersioned _v = do - tag <- decodeWord +instance DecodeVersioned NoCtx MergePolicyForLevel where + decodeVersionedWith _v NoCtx = do + tag <- liftDecoder decodeWord case tag of 0 -> pure LevelTiering 1 -> pure LevelLevelling @@ -635,15 +690,15 @@ instance (Encode t, Encode r) => Encode (SnapMergingRun t r) where <> encode rs <> encode mt -instance (DecodeVersioned t, DecodeVersioned r) => DecodeVersioned (SnapMergingRun t r) where - decodeVersioned v = do - n <- decodeListLen - tag <- decodeWord +instance (DecodeVersioned NoCtx t, DecodeVersioned ctx r) => DecodeVersioned ctx (SnapMergingRun t r) where + decodeVersionedWith v ctx = do + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord case (n, tag) of (3, 0) -> SnapCompletedMerge <$> decodeVersioned v - <*> decodeVersioned v + <*> decodeVersionedWith v ctx (5, 1) -> SnapOngoingMerge <$> decodeVersioned v <*> decodeVersioned v - <*> decodeVersioned v <*> decodeVersioned v + <*> decodeVersionedWith v ctx <*> decodeVersioned v _ -> fail ("[SnapMergingRun] Unexpected combination of list length and tag: " <> show (n, tag)) -- NominalDebt, NominalCredits, MergeDebt and MergeCredits @@ -651,26 +706,26 @@ instance (DecodeVersioned t, DecodeVersioned r) => DecodeVersioned (SnapMergingR instance Encode NominalDebt where encode (NominalDebt x) = encodeInt x -instance DecodeVersioned NominalDebt where - decodeVersioned _v = NominalDebt <$> decodeInt +instance DecodeVersioned NoCtx NominalDebt where + decodeVersionedWith _v NoCtx = NominalDebt <$> liftDecoder decodeInt instance Encode NominalCredits where encode (NominalCredits x) = encodeInt x -instance DecodeVersioned NominalCredits where - decodeVersioned _v = NominalCredits <$> decodeInt +instance DecodeVersioned NoCtx NominalCredits where + decodeVersionedWith _v NoCtx = NominalCredits <$> liftDecoder decodeInt instance Encode MergeDebt where encode (MergeDebt (MergeCredits x)) = encodeInt x -instance DecodeVersioned MergeDebt where - decodeVersioned _v = (MergeDebt . MergeCredits) <$> decodeInt +instance DecodeVersioned NoCtx MergeDebt where + decodeVersionedWith _v NoCtx = (MergeDebt . MergeCredits) <$> liftDecoder decodeInt instance Encode MergeCredits where encode (MergeCredits x) = encodeInt x -instance DecodeVersioned MergeCredits where - decodeVersioned _v = MergeCredits <$> decodeInt +instance DecodeVersioned NoCtx MergeCredits where + decodeVersionedWith _v NoCtx = MergeCredits <$> liftDecoder decodeInt -- MergeType @@ -678,9 +733,9 @@ instance Encode MR.LevelMergeType where encode MR.MergeMidLevel = encodeWord 0 encode MR.MergeLastLevel = encodeWord 1 -instance DecodeVersioned MR.LevelMergeType where - decodeVersioned _v = do - tag <- decodeWord +instance DecodeVersioned NoCtx MR.LevelMergeType where + decodeVersionedWith _v NoCtx = do + tag <- liftDecoder decodeWord case tag of 0 -> pure MR.MergeMidLevel 1 -> pure MR.MergeLastLevel @@ -700,9 +755,9 @@ instance Encode MR.TreeMergeType where encode MR.MergeLevel = encodeWord 1 encode MR.MergeUnion = encodeWord 2 -instance DecodeVersioned MR.TreeMergeType where - decodeVersioned _v = do - tag <- decodeWord +instance DecodeVersioned NoCtx MR.TreeMergeType where + decodeVersionedWith _v NoCtx = do + tag <- liftDecoder decodeWord case tag of 1 -> pure MR.MergeLevel 2 -> pure MR.MergeUnion @@ -717,8 +772,8 @@ instance DecodeVersioned MR.TreeMergeType where instance Encode r => Encode (SnapMergingTree r) where encode (SnapMergingTree tState) = encode tState -instance DecodeVersioned r => DecodeVersioned (SnapMergingTree r) where - decodeVersioned ver = SnapMergingTree <$> decodeVersioned ver +instance DecodeVersioned ctx r => DecodeVersioned ctx (SnapMergingTree r) where + decodeVersionedWith ver ctx = SnapMergingTree <$> decodeVersionedWith ver ctx -- SnapMergingTreeState @@ -736,14 +791,14 @@ instance Encode r => Encode (SnapMergingTreeState r) where <> encodeWord 2 <> encode smrs -instance DecodeVersioned r => DecodeVersioned (SnapMergingTreeState r) where - decodeVersioned v = do - n <- decodeListLen - tag <- decodeWord +instance DecodeVersioned ctx r => DecodeVersioned ctx (SnapMergingTreeState r) where + decodeVersionedWith v ctx = do + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord case (n, tag) of - (2, 0) -> SnapCompletedTreeMerge <$> decodeVersioned v - (2, 1) -> SnapPendingTreeMerge <$> decodeVersioned v - (2, 2) -> SnapOngoingTreeMerge <$> decodeVersioned v + (2, 0) -> SnapCompletedTreeMerge <$> decodeVersionedWith v ctx + (2, 1) -> SnapPendingTreeMerge <$> decodeVersionedWith v ctx + (2, 2) -> SnapOngoingTreeMerge <$> decodeVersionedWith v ctx _ -> fail ("[SnapMergingTreeState] Unexpected combination of list length and tag: " <> show (n, tag)) -- SnapPendingMerge @@ -759,13 +814,13 @@ instance Encode r => Encode (SnapPendingMerge r) where <> encodeWord 1 <> encodeList mts -instance DecodeVersioned r => DecodeVersioned (SnapPendingMerge r) where - decodeVersioned v = do - n <- decodeListLen - tag <- decodeWord +instance DecodeVersioned ctx r => DecodeVersioned ctx (SnapPendingMerge r) where + decodeVersionedWith v ctx = do + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord case (n, tag) of - (3, 0) -> SnapPendingLevelMerge <$> decodeList v <*> decodeMaybe v - (2, 1) -> SnapPendingUnionMerge <$> decodeList v + (3, 0) -> SnapPendingLevelMerge <$> decodeList (decodeVersionedWith v ctx) <*> decodeMaybe (decodeVersionedWith v ctx) + (2, 1) -> SnapPendingUnionMerge <$> decodeList (decodeVersionedWith v ctx) _ -> fail ("[SnapPendingMerge] Unexpected combination of list length and tag: " <> show (n, tag)) -- SnapPreExistingRun @@ -780,13 +835,13 @@ instance Encode r => Encode (SnapPreExistingRun r) where <> encodeWord 1 <> encode smrs -instance DecodeVersioned r => DecodeVersioned (SnapPreExistingRun r) where - decodeVersioned v = do - n <- decodeListLen - tag <- decodeWord +instance DecodeVersioned ctx r => DecodeVersioned ctx (SnapPreExistingRun r) where + decodeVersionedWith v ctx = do + n <- liftDecoder decodeListLen + tag <- liftDecoder decodeWord case (n, tag) of - (2, 0) -> SnapPreExistingRun <$> decodeVersioned v - (2, 1) -> SnapPreExistingMergingRun <$> decodeVersioned v + (2, 0) -> SnapPreExistingRun <$> decodeVersionedWith v ctx + (2, 1) -> SnapPreExistingMergingRun <$> decodeVersionedWith v ctx _ -> fail ("[SnapPreExistingRun] Unexpected combination of list length and tag: " <> show (n, tag)) -- Utilities for encoding/decoding Maybe values @@ -798,30 +853,35 @@ encodeMaybe = \case Nothing -> encodeNull Just en -> encode en -decodeMaybe :: DecodeVersioned a => SnapshotVersion -> Decoder s (Maybe a) -decodeMaybe v = do - tok <- peekTokenType +decodeMaybe :: Dec s a -> Dec s (Maybe a) +decodeMaybe dec = do + tok <- liftDecoder peekTokenType case tok of - TypeNull -> Nothing <$ decodeNull - _ -> Just <$> decodeVersioned v + TypeNull -> Nothing <$ liftDecoder decodeNull + _ -> Just <$> dec encodeList :: Encode a => [a] -> Encoding encodeList xs = encodeListLen (fromIntegral (length xs)) <> foldr (\x r -> encode x <> r) mempty xs -decodeList :: DecodeVersioned a => SnapshotVersion -> Decoder s [a] -decodeList v = do - n <- decodeListLen - decodeSequenceLenN (flip (:)) [] reverse n (decodeVersioned v) +decodeList :: Dec s a -> Dec s [a] +decodeList dec = do + n <- liftDecoder decodeListLen + env <- ask + liftDecoder $ + decodeSequenceLenN (flip (:)) [] reverse n + (runDec dec env) encodeVector :: Encode a => V.Vector a -> Encoding encodeVector xs = encodeListLen (fromIntegral (V.length xs)) <> foldr (\x r -> encode x <> r) mempty xs -decodeVector :: DecodeVersioned a => SnapshotVersion -> Decoder s (V.Vector a) -decodeVector v = do - n <- decodeListLen - decodeSequenceLenN (flip (:)) [] (V.reverse . V.fromList) - n (decodeVersioned v) +decodeVector :: Dec s a -> Dec s (V.Vector a) +decodeVector dec = do + n <- liftDecoder decodeListLen + env <- ask + liftDecoder $ + decodeSequenceLenN (flip (:)) [] (V.reverse . V.fromList) + n (runDec dec env) diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot/Codec/Monad.hs b/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot/Codec/Monad.hs new file mode 100644 index 000000000..f55c1e094 --- /dev/null +++ b/lsm-tree/src-core/Database/LSMTree/Internal/Snapshot/Codec/Monad.hs @@ -0,0 +1,35 @@ +{-# OPTIONS_HADDOCK not-home #-} + +-- | Decoder monad for snapshot metadata +module Database.LSMTree.Internal.Snapshot.Codec.Monad ( + Dec (..) + , Env (..) + , runDec + , liftDecoder + , liftST + ) where + +import Codec.CBOR.Decoding (Decoder) +import qualified Codec.CBOR.Decoding as CBOR +import Control.Monad.Reader (MonadReader, MonadTrans (lift), + ReaderT (..)) +import Control.Monad.ST (ST) + +newtype Dec s a = Dec { unwrap :: ReaderT Env (Decoder s) a } + deriving newtype (Functor, Applicative, Monad) + +deriving newtype instance MonadReader Env (Dec s) +deriving newtype instance MonadFail (Dec s) + +data Env = Env { + v3Assert :: Bool + } + +runDec :: Dec s a -> Env -> Decoder s a +runDec dec env = runReaderT dec.unwrap env + +liftDecoder :: Decoder s a -> Dec s a +liftDecoder = Dec . lift + +liftST :: ST s a -> Dec s a +liftST = Dec . lift . CBOR.liftST diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/Unsafe.hs b/lsm-tree/src-core/Database/LSMTree/Internal/Unsafe.hs index d0b3d1f8d..8d7a8ecb0 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/Unsafe.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/Unsafe.hs @@ -1792,7 +1792,7 @@ openTableFromSnapshot policyOveride sesh snap label resolve = do let SnapshotMetaDataFile contentPath = Paths.snapshotMetaDataFile snapDir SnapshotMetaDataChecksumFile checksumPath = Paths.snapshotMetaDataChecksumFile snapDir - snapMetaData <- readFileSnapshotMetaData hfs contentPath checksumPath + snapMetaData <- readFileSnapshotMetaData hfs contentPath checksumPath True let SnapshotMetaData label' conf snapWriteBuffer snapLevels mTreeOpt = overrideTableConfig policyOveride snapMetaData diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/Vector.hs b/lsm-tree/src-core/Database/LSMTree/Internal/Vector.hs index f7f332237..6949f8ec5 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/Vector.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/Vector.hs @@ -34,13 +34,20 @@ import Data.Word (Word8) import Database.LSMTree.Internal.Assertions import GHC.Exts (Int (..)) import GHC.ST (runST) +import GHC.Stack -mkPrimVector :: forall a. Prim a => Int -> Int -> ByteArray -> VP.Vector a +mkPrimVector :: + forall a. (HasCallStack, Prim a) + => Int + -> Int + -> ByteArray + -> VP.Vector a mkPrimVector off len ba = assert (isValidSlice (off * sizeof) (len * sizeof) ba) $ VP.Vector off len ba where sizeof = I# (sizeOfType# (Proxy @a)) + _unused = callStack {-# INLINE mkPrimVector #-} byteVectorFromPrim :: forall a. Prim a => a -> VP.Vector Word8 diff --git a/lsm-tree/src-core/Database/LSMTree/Internal/WriteBufferReader.hs b/lsm-tree/src-core/Database/LSMTree/Internal/WriteBufferReader.hs index d570a0d6f..b243a6ba2 100644 --- a/lsm-tree/src-core/Database/LSMTree/Internal/WriteBufferReader.hs +++ b/lsm-tree/src-core/Database/LSMTree/Internal/WriteBufferReader.hs @@ -21,11 +21,11 @@ import Database.LSMTree.Internal.BlobFile (BlobFile) import Database.LSMTree.Internal.BlobRef (RawBlobRef (..), mkRawBlobRef) import qualified Database.LSMTree.Internal.Entry as E +import Database.LSMTree.Internal.Page (readDiskPage) import Database.LSMTree.Internal.Paths import Database.LSMTree.Internal.RawPage import Database.LSMTree.Internal.RunReader (Entry (..), Result (..), - mkEntryOverflow, readDiskPage, readOverflowPages, - toFullEntry) + mkEntryOverflow, readOverflowPages, toFullEntry) import Database.LSMTree.Internal.Serialise (ResolveSerialisedValue, SerialisedValue) import Database.LSMTree.Internal.WriteBuffer (WriteBuffer) diff --git a/lsm-tree/src-extras/Database/LSMTree/Extras/NoThunks.hs b/lsm-tree/src-extras/Database/LSMTree/Extras/NoThunks.hs index 2ac0f7f33..bbd6fb848 100644 --- a/lsm-tree/src-extras/Database/LSMTree/Extras/NoThunks.hs +++ b/lsm-tree/src-extras/Database/LSMTree/Extras/NoThunks.hs @@ -246,9 +246,6 @@ deriving anyclass instance NoThunks a => NoThunks (ForKOps a) deriving stock instance Generic (ForBlob a) deriving anyclass instance NoThunks a => NoThunks (ForBlob a) -deriving stock instance Generic (ForFilter a) -deriving anyclass instance NoThunks a => NoThunks (ForFilter a) - deriving stock instance Generic (ForIndex a) deriving anyclass instance NoThunks a => NoThunks (ForIndex a) diff --git a/lsm-tree/test/Test/Database/LSMTree/Internal/BloomFilter.hs b/lsm-tree/test/Test/Database/LSMTree/Internal/BloomFilter.hs index c9b457815..31ba8280f 100644 --- a/lsm-tree/test/Test/Database/LSMTree/Internal/BloomFilter.hs +++ b/lsm-tree/test/Test/Database/LSMTree/Internal/BloomFilter.hs @@ -1,23 +1,9 @@ module Test.Database.LSMTree.Internal.BloomFilter (tests) where -import Control.DeepSeq (deepseq) -import Control.Exception (Exception (..), displayException) -import Control.Monad (void) -import qualified Control.Monad.IOSim as IOSim -import Data.Bits ((.&.)) -import qualified Data.ByteString as BS -import qualified Data.ByteString.Builder as BS.Builder -import qualified Data.ByteString.Builder.Extra as BS.Builder -import qualified Data.ByteString.Lazy as LBS -import qualified Data.List as List import qualified Data.Set as Set import qualified Data.Vector as V import qualified Data.Vector.Primitive as VP -import Data.Word (Word32, Word64, byteSwap32) -import qualified System.FS.API as FS -import qualified System.FS.API.Strict as FS -import qualified System.FS.Sim.MockFS as MockFS -import qualified System.FS.Sim.STM as FSSim +import Data.Word (Word64) import Test.QuickCheck.Gen (genDouble) import Test.QuickCheck.Instances () @@ -26,24 +12,14 @@ import Test.Tasty.QuickCheck hiding ((.&.)) import qualified Data.BloomFilter.Blocked as Bloom import Database.LSMTree.Internal.BloomFilter -import Database.LSMTree.Internal.CRC32C (FileCorruptedError (..), - FileFormat (..)) import Database.LSMTree.Internal.Serialise (SerialisedKey, serialiseKey) -import Test.Util.QC.Compat (withNumTests_compat) --TODO: add a golden test for the BloomFilter format vs the 'formatVersion' -- to ensure we don't change the format without conciously bumping the version. tests :: TestTree tests = testGroup "Database.LSMTree.Internal.BloomFilter" - [ testProperty "roundtrip" roundtrip_prop - -- a specific case: 300 bits is just under 5x 64 bit words - , testProperty "roundtrip-3-300" $ roundtrip_prop (Positive (Small 3)) (Positive 300) - , testProperty "total-deserialisation" $ withNumTests_compat 10000 $ - prop_total_deserialisation - , testProperty "total-deserialisation-whitebox" $ withNumTests_compat 10000 $ - prop_total_deserialisation_whitebox - , testProperty "bloomQueries (bulk)" $ + [ testProperty "bloomQueries (bulk)" $ prop_bloomQueries , testProperty "prop_packUnpack_RunIxKeyIx" prop_packUnpack_RunIxKeyIx , testProperty "prop_packUnpack_RunIxKeyIx_limits" prop_packUnpack_RunIxKeyIx_limits @@ -52,74 +28,6 @@ tests = testGroup "Database.LSMTree.Internal.BloomFilter" testSalt :: Bloom.Salt testSalt = 4 -roundtrip_prop :: Positive (Small Int) -> Positive Int -> [Word64] -> Property -roundtrip_prop (Positive (Small hfN)) (Positive bits) ws = - counterexample (show bs) $ - case bloomFilterFromBS bs of - Left err -> counterexample (displayException err) $ property False - Right rhs -> lhs === rhs - where - sz = Bloom.BloomSize { sizeBits = limitBits bits, - sizeHashes = hfN } - lhs = Bloom.create sz testSalt (\b -> mapM_ (Bloom.insert b) ws) - bs = LBS.toStrict (bloomFilterToLBS lhs) - -limitBits :: Int -> Int -limitBits b = b .&. 0xffffff - -prop_total_deserialisation :: BS.ByteString -> Property -prop_total_deserialisation bs = - case bloomFilterFromBS bs of - Left err -> - label (mkLabel err) $ property True - Right bf -> label "parsed successfully" $ property $ - bf `deepseq` True - where - mkLabel err = case err of - IOSim.FailureException e - | Just (ErrFileFormatInvalid fsep FormatBloomFilterFile msg) <- fromException e - , let msg' = "Expected salt does not match actual salt" - , msg' `List.isPrefixOf` msg - -> displayException $ ErrFileFormatInvalid fsep FormatBloomFilterFile msg' - _ -> displayException err - --- | Write the bytestring to a file in the mock file system and then use --- 'bloomFilterFromFile'. -bloomFilterFromBS :: BS.ByteString -> Either IOSim.Failure (Bloom a) -bloomFilterFromBS bs = - IOSim.runSim $ do - hfs <- FSSim.simHasFS' MockFS.empty - let file = FS.mkFsPath ["filter"] - -- write the bytestring - FS.withFile hfs file (FS.WriteMode FS.MustBeNew) $ \h -> do - void $ FS.hPutAllStrict hfs h bs - -- deserialise from file - FS.withFile hfs file FS.ReadMode $ \h -> - bloomFilterFromFile hfs testSalt h - --- Length is in bits. A large length would require significant amount of --- memory, so we make it 'Small'. -prop_total_deserialisation_whitebox :: Word32 -> Small Word64 -> Property -prop_total_deserialisation_whitebox hsn (Small nbits) = - forAll genBytes $ \bytes -> - forAll genVersion $ \version -> - prop_total_deserialisation (prefix version <> BS.pack bytes) - where - genBytes = do n <- choose (-1,1) - vector (nbytes' + n) - nbytes = (fromIntegral nbits+7) `div` 8 - nbytes' = ((nbytes + 63) `div` 64) * 64 -- rounded to nearest 64 bytes - genVersion = frequency [ - (6, pure bloomFilterVersion), - (1, pure (byteSwap32 bloomFilterVersion)), - (1, arbitrary) - ] - prefix version = - LBS.toStrict $ BS.Builder.toLazyByteString $ - BS.Builder.word32Host version - <> BS.Builder.word32Host hsn - <> BS.Builder.word64Host nbits - newtype FPR = FPR Double deriving stock Show instance Arbitrary FPR where diff --git a/lsm-tree/test/Test/Database/LSMTree/Internal/Run.hs b/lsm-tree/test/Test/Database/LSMTree/Internal/Run.hs index 992b3cc22..8d1fad939 100644 --- a/lsm-tree/test/Test/Database/LSMTree/Internal/Run.hs +++ b/lsm-tree/test/Test/Database/LSMTree/Internal/Run.hs @@ -114,11 +114,9 @@ testSingleInsert sessionRoot key val mblob = let activeDir = sessionRoot bsKOps <- BS.readFile (activeDir "42.keyops") bsBlobs <- BS.readFile (activeDir "42.blobs") - bsFilter <- BS.readFile (activeDir "42.filter") bsIndex <- BS.readFile (activeDir "42.index") not (BS.null bsKOps) @? "k/ops file is empty" null mblob @=? BS.null bsBlobs -- blob file might be empty - not (BS.null bsFilter) @? "filter file is empty" not (BS.null bsIndex) @? "index file is empty" -- checksums checksums <- CRC.readChecksumsFile fs (FS.mkFsPath ["42.checksums"]) @@ -126,8 +124,6 @@ testSingleInsert sessionRoot key val mblob = @=? Just (CRC.updateCRC32C bsKOps CRC.initialCRC32C) Map.lookup (CRC.ChecksumsFileName "blobs") checksums @=? Just (CRC.updateCRC32C bsBlobs CRC.initialCRC32C) - Map.lookup (CRC.ChecksumsFileName "filter") checksums - @=? Just (CRC.updateCRC32C bsFilter CRC.initialCRC32C) Map.lookup (CRC.ChecksumsFileName "index") checksums @=? Just (CRC.updateCRC32C bsIndex CRC.initialCRC32C) -- check page @@ -217,8 +213,8 @@ prop_WriteAndOpen fs hbio wb = paths' = paths { runNumber = RunNumber 17} hardLinkRunFiles fs hbio reg paths paths' loaded <- openFromDisk fs hbio refCtx (runParamCaching runParams) - (runParamIndex runParams) testSalt - (simplePath 17) + (runParamAlloc runParams) (runParamIndex runParams) + testSalt (simplePath 17) Run.size written @=? Run.size loaded withRef written $ \written' -> diff --git a/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/Codec.hs b/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/Codec.hs index de037acb3..da8cb0aaa 100644 --- a/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/Codec.hs +++ b/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/Codec.hs @@ -24,6 +24,8 @@ import Database.LSMTree.Internal.RunBuilder (IndexType (..), import Database.LSMTree.Internal.RunNumber import Database.LSMTree.Internal.Snapshot import Database.LSMTree.Internal.Snapshot.Codec +import Database.LSMTree.Internal.Snapshot.Codec.Monad (Env (Env), + runDec) import Test.Tasty import Test.Tasty.QuickCheck import Test.Util.Arbitrary @@ -49,9 +51,17 @@ tests = testGroup "Test.Database.LSMTree.Internal.Snapshot.Codec" [ propAll roundtripFlatTerm' -- Test generators and shrinkers , testGroup "Generators and shrinkers are finite" $ - testAll $ \(p :: Proxy a) -> - testGroup (show $ typeRep p) $ - prop_arbitraryAndShrinkPreserveInvariant @a noTags deepseqInvariant + testAll $ \(pa :: Proxy a) (pc :: Proxy ctx) -> + testGroup (show (typeRep pa, typeRep pc)) [ + testGroup (show $ typeRep pa) $ + prop_arbitraryAndShrinkPreserveInvariant @a noTags deepseqInvariant + -- TODO: tests for ctx are duplicated because multiple codec types + -- may have the same ctx type. We could remove this duplication, + -- but it is also not currently a problem because the tests are + -- quick to run. + , testGroup (show $ typeRep pc) $ + prop_arbitraryAndShrinkPreserveInvariant @ctx noTags deepseqInvariant + ] ] {------------------------------------------------------------------------------- @@ -82,12 +92,27 @@ explicitRoundtripCBOR enc dec x = case back (there x) of back = deserialiseFromBytes dec -- | See 'explicitRoundtripCBOR'. -roundtripCBOR :: (Encode a, Decode a, Eq a, Show a) => Proxy a -> a -> Property -roundtripCBOR _ = explicitRoundtripCBOR encode decode +roundtripCBOR :: + (Encode a, Decode a, Eq a, Show a) + => Proxy a + -> a + -> Property +roundtripCBOR _ = explicitRoundtripCBOR encode dec + where + dec = runDec decode (Env False) -- | See 'explicitRoundtripCBOR'. -roundtripCBOR' :: (Encode a, DecodeVersioned a, Eq a, Show a) => Proxy a -> a -> Property -roundtripCBOR' _ = explicitRoundtripCBOR encode (decodeVersioned currentSnapshotVersion) +roundtripCBOR' :: + forall a ctx. (Encode a, DecodeVersioned ctx a, Eq a, Show a) + => Proxy a + -> Proxy ctx + -> a + -> ctx + -> Property +roundtripCBOR' _ _ x ctx = explicitRoundtripCBOR encode dec x + where + dec :: forall s. Decoder s a + dec = runDec (decodeVersionedWith currentSnapshotVersion ctx ) (Env False) -- | @fromFlatTerm . toFlatTerm = id@ -- @@ -122,86 +147,106 @@ roundtripFlatTerm :: => Proxy a -> a -> Property -roundtripFlatTerm _ = explicitRoundtripFlatTerm encode decode +roundtripFlatTerm _ = explicitRoundtripFlatTerm encode dec + where + dec = runDec decode (Env False) -- | See 'explicitRoundtripFlatTerm'. roundtripFlatTerm' :: - (Encode a, DecodeVersioned a, Eq a, Show a) + forall a ctx. (Encode a, DecodeVersioned ctx a, Eq a, Show a) => Proxy a + -> Proxy ctx -> a + -> ctx -> Property -roundtripFlatTerm' _ = explicitRoundtripFlatTerm encode (decodeVersioned currentSnapshotVersion) +roundtripFlatTerm' _ _ x ctx = explicitRoundtripFlatTerm encode dec x + where + dec :: forall s. Decoder s a + dec = runDec (decodeVersionedWith currentSnapshotVersion ctx) (Env False) {------------------------------------------------------------------------------- Test and property runners -------------------------------------------------------------------------------} -type Constraints a = ( +type Constraints a ctx = ( Eq a, Show a, Typeable a, Arbitrary a - , Encode a, DecodeVersioned a, NFData a + , NFData a + , Encode a, DecodeVersioned ctx a + + , Eq ctx, Show ctx, Typeable ctx, Arbitrary ctx + , NFData ctx ) -- | Run a property on all types in the snapshot metadata hierarchy. propAll :: - (forall a. Constraints a => Proxy a -> a -> Property) + (forall a ctx. Constraints a ctx => Proxy a -> Proxy ctx -> a -> ctx -> Property) -> [TestTree] propAll prop = testAll mkTest where - mkTest :: forall a. Constraints a => Proxy a -> TestTree - mkTest pa = testProperty (show $ typeRep pa) (prop pa) + mkTest :: forall a ctx. Constraints a ctx => Proxy a -> Proxy ctx -> TestTree + mkTest pa pc = testProperty (show (typeRep pa, typeRep pc)) (prop pa pc) -- | Run a test on all types in the snapshot metadata hierarchy. testAll :: - (forall a. Constraints a => Proxy a -> TestTree) + (forall a ctx. Constraints a ctx => Proxy a -> Proxy ctx -> TestTree) -> [TestTree] testAll test = [ -- SnapshotMetaData - test (Proxy @SnapshotMetaData) - , test (Proxy @SnapshotLabel) - , test (Proxy @SnapshotRun) + test (Proxy @SnapshotMetaData) (Proxy @NoCtx) + , test (Proxy @SnapshotLabel) (Proxy @NoCtx) + , test (Proxy @SnapshotRun) (Proxy @TableConfig) -- TableConfig - , test (Proxy @TableConfig) - , test (Proxy @MergePolicy) - , test (Proxy @SizeRatio) - , test (Proxy @WriteBufferAlloc) - , test (Proxy @BloomFilterAlloc) - , test (Proxy @FencePointerIndexType) - , test (Proxy @DiskCachePolicy) - , test (Proxy @MergeSchedule) + , test (Proxy @TableConfig) (Proxy @NoCtx) + , test (Proxy @MergePolicy) (Proxy @NoCtx) + , test (Proxy @SizeRatio) (Proxy @NoCtx) + , test (Proxy @WriteBufferAlloc) (Proxy @NoCtx) + , test (Proxy @BloomFilterAlloc) (Proxy @NoCtx) + , test (Proxy @FencePointerIndexType) (Proxy @NoCtx) + , test (Proxy @DiskCachePolicy) (Proxy @NoCtx) + , test (Proxy @MergeSchedule) (Proxy @NoCtx) -- SnapLevels - , test (Proxy @(SnapLevels SnapshotRun)) - , test (Proxy @(SnapLevel SnapshotRun)) - , test (Proxy @(V.Vector SnapshotRun)) - , test (Proxy @RunNumber) - , test (Proxy @(SnapIncomingRun SnapshotRun)) - , test (Proxy @MergePolicyForLevel) - , test (Proxy @RunDataCaching) - , test (Proxy @RunBloomFilterAlloc) - , test (Proxy @IndexType) - , test (Proxy @RunParams) - , test (Proxy @(SnapMergingRun LevelMergeType SnapshotRun)) - , test (Proxy @MergeDebt) - , test (Proxy @MergeCredits) - , test (Proxy @NominalDebt) - , test (Proxy @NominalCredits) - , test (Proxy @LevelMergeType) - , test (Proxy @TreeMergeType) - , test (Proxy @(SnapMergingTree SnapshotRun)) - , test (Proxy @(SnapMergingTreeState SnapshotRun)) - , test (Proxy @(SnapMergingRun TreeMergeType SnapshotRun)) - , test (Proxy @(SnapPendingMerge SnapshotRun)) - , test (Proxy @(SnapPreExistingRun SnapshotRun)) + , test (Proxy @(SnapLevels SnapshotRun)) (Proxy @TableConfig) + , test (Proxy @(SnapLevel SnapshotRun)) (Proxy @TableConfig) + , test (Proxy @(V.Vector SnapshotRun)) (Proxy @TableConfig) + , test (Proxy @RunNumber) (Proxy @NoCtx) + , test (Proxy @(SnapIncomingRun SnapshotRun)) (Proxy @TableConfig) + , test (Proxy @MergePolicyForLevel) (Proxy @NoCtx) + , test (Proxy @RunDataCaching) (Proxy @NoCtx) + , test (Proxy @RunBloomFilterAlloc) (Proxy @NoCtx) + , test (Proxy @IndexType) (Proxy @NoCtx) + , test (Proxy @RunParams) (Proxy @NoCtx) + , test (Proxy @(SnapMergingRun LevelMergeType SnapshotRun)) (Proxy @TableConfig) + , test (Proxy @MergeDebt) (Proxy @NoCtx) + , test (Proxy @MergeCredits) (Proxy @NoCtx) + , test (Proxy @NominalDebt) (Proxy @NoCtx) + , test (Proxy @NominalCredits) (Proxy @NoCtx) + , test (Proxy @LevelMergeType) (Proxy @NoCtx) + , test (Proxy @TreeMergeType) (Proxy @NoCtx) + , test (Proxy @(SnapMergingTree SnapshotRun)) (Proxy @TableConfig) + , test (Proxy @(SnapMergingTreeState SnapshotRun)) (Proxy @TableConfig) + , test (Proxy @(SnapMergingRun TreeMergeType SnapshotRun)) (Proxy @TableConfig) + , test (Proxy @(SnapPendingMerge SnapshotRun)) (Proxy @TableConfig) + , test (Proxy @(SnapPreExistingRun SnapshotRun)) (Proxy @TableConfig) ] +{------------------------------------------------------------------------------- + Arbitrary: context +-------------------------------------------------------------------------------} + +instance Arbitrary NoCtx where + arbitrary = pure NoCtx + shrink NoCtx = [] + {------------------------------------------------------------------------------- Arbitrary: versioning -------------------------------------------------------------------------------} instance Arbitrary SnapshotVersion where - arbitrary = elements [V0, V1, V2] + arbitrary = elements [V0, V1, V2, V3] shrink V0 = [] shrink V1 = [V0] shrink V2 = [V0, V1] + shrink V3 = [V0, V1, V2] deriving newtype instance Arbitrary a => Arbitrary (Versioned a) @@ -228,10 +273,10 @@ instance Arbitrary SnapshotLabel where shrink (SnapshotLabel txt) = SnapshotLabel <$> shrink txt instance Arbitrary SnapshotRun where - arbitrary = SnapshotRun <$> arbitrary <*> arbitrary <*> arbitrary - shrink (SnapshotRun a b c) = - [ SnapshotRun a' b' c' - | (a', b', c') <- shrink (a, b, c)] + arbitrary = SnapshotRun <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary + shrink (SnapshotRun a b c d) = + [ SnapshotRun a' b' c' d' + | (a', b', c', d') <- shrink (a, b, c, d)] {------------------------------------------------------------------------------- Arbitrary: TableConfig diff --git a/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/Codec/Golden.hs b/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/Codec/Golden.hs index 43f8641fc..19bec4205 100644 --- a/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/Codec/Golden.hs +++ b/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/Codec/Golden.hs @@ -18,7 +18,8 @@ import qualified Data.Vector as V import Database.LSMTree.Internal.Config (BloomFilterAlloc (..), DiskCachePolicy (..), FencePointerIndexType (..), MergeBatchSize (..), MergePolicy (..), MergeSchedule (..), - SizeRatio (..), TableConfig (..), WriteBufferAlloc (..)) + SizeRatio (..), TableConfig (..), WriteBufferAlloc (..), + defaultTableConfig) import Database.LSMTree.Internal.MergeSchedule (MergePolicyForLevel (..), NominalCredits (..), NominalDebt (..)) @@ -28,6 +29,8 @@ import Database.LSMTree.Internal.RunBuilder (IndexType (..), import Database.LSMTree.Internal.RunNumber (RunNumber (..)) import Database.LSMTree.Internal.Snapshot import Database.LSMTree.Internal.Snapshot.Codec +import Database.LSMTree.Internal.Snapshot.Codec.Monad (Env (Env), + runDec) import Paths_lsm_tree import qualified System.Directory as Dir import System.FilePath @@ -56,6 +59,7 @@ tests = , testGroup "Backwards compatibility" [ testCase "test_compatTableConfigV0" test_compatTableConfigV0 , testCase "test_compatSnapLevelV1" test_compatSnapLevelV1 + , testCase "test_compatSnapshotRunV2" test_compatSnapshotRunV2 ] ] @@ -140,7 +144,7 @@ prop_noUnexpectedOrMissingGoldenFiles = once $ ioProperty $ do test_compatTableConfigV0 :: Assertion test_compatTableConfigV0 = - assertGoldenFileDecodesTo (Proxy @TableConfig) "A" V0 $ + assertGoldenFileDecodesTo (Proxy @TableConfig) (Proxy @NoCtx )"A" V0 NoCtx $ TableConfig { confMergePolicy = singGolden V0 , confMergeSchedule = singGolden V0 @@ -155,24 +159,39 @@ test_compatTableConfigV0 = test_compatSnapLevelV1 :: Assertion test_compatSnapLevelV1 = - assertGoldenFileDecodesTo (Proxy @(SnapLevel SnapshotRun)) "A" V1 $ + assertGoldenFileDecodesTo (Proxy @(SnapLevel SnapshotRun)) (Proxy @TableConfig) "A" V1 conf $ -- Until V2, there was only a single constructor, so there was no tag -- in the serialisation format to distinguish constructors. SnapLevel (singGolden V1) (singGolden V1) + where + conf = defaultTableConfig { + confBloomFilterAlloc = singGolden V1 + } + +test_compatSnapshotRunV2 :: Assertion +test_compatSnapshotRunV2 = + assertGoldenFileDecodesTo (Proxy @(SnapshotRun)) (Proxy @TableConfig) "A" V2 conf $ + singGolden V2 + where + conf = defaultTableConfig { + confBloomFilterAlloc = singGolden V2 + } -- | For types that changed their snapshot format between versions, we should -- also test that we can in fact still decode the old format. assertGoldenFileDecodesTo :: - (DecodeVersioned a, Eq a, Show a, Typeable a) + (DecodeVersioned ctx a, Eq a, Show a, Typeable a) => Proxy a + -> Proxy ctx -> String -> SnapshotVersion + -> ctx -> a -> Assertion -assertGoldenFileDecodesTo proxy ann v expected = do - let fp = goldenDataFilePath filePathGolden proxy ann v +assertGoldenFileDecodesTo pa _pc ann v ctx expected = do + let fp = goldenDataFilePath filePathGolden pa ann v lbs <- BSL.readFile fp - case deserialiseFromBytes (decodeVersioned v) lbs of + case deserialiseFromBytes (runDec (decodeVersionedWith v ctx) (Env False)) lbs of Left err -> assertFailure $ "Error decoding " ++ fp ++ ": " ++ displayException err Right (_, decoded) -> @@ -560,7 +579,7 @@ instance EnumGolden RunDataCaching where NoCacheRunData{} -> () instance EnumGolden SnapshotRun where - singGolden v = SnapshotRun (singGolden v) (singGolden v) (singGolden v) + singGolden v = SnapshotRun (singGolden v) (singGolden v) (singGolden v) (singGolden v) where _coveredAllCases = \case SnapshotRun{} -> () diff --git a/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/FS.hs b/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/FS.hs index 05f2c5a33..5d34acb82 100644 --- a/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/FS.hs +++ b/lsm-tree/test/Test/Database/LSMTree/Internal/Snapshot/FS.hs @@ -53,7 +53,7 @@ prop_fsRoundtripSnapshotMetaData metadata = withTempIOHasFS "temp" $ \hfs -> do writeFileSnapshotMetaData hfs contentPath checksumPath metadata snapshotMetaData' <- - try @_ @FileCorruptedError (readFileSnapshotMetaData hfs contentPath checksumPath) + try @_ @FileCorruptedError (readFileSnapshotMetaData hfs contentPath checksumPath False) pure $ case snapshotMetaData' of Left e -> counterexample (show e) False Right metadata' -> metadata === metadata' @@ -83,7 +83,7 @@ prop_fault_fsRoundtripSnapshotMetaData testErrs metadata = readResult <- try @_ @SomeException $ withErrors errsVar (readErrors testErrs) $ - readFileSnapshotMetaData hfs metadataPath checksumPath + readFileSnapshotMetaData hfs metadataPath checksumPath False let -- Regardless of whether the write part failed with an exception, if diff --git a/lsm-tree/test/Test/Database/LSMTree/Snapshots.hs b/lsm-tree/test/Test/Database/LSMTree/Snapshots.hs index ac522625f..1c1f84bcc 100644 --- a/lsm-tree/test/Test/Database/LSMTree/Snapshots.hs +++ b/lsm-tree/test/Test/Database/LSMTree/Snapshots.hs @@ -24,6 +24,7 @@ import Test.Util.FS (withTempIOHasBlockIO) tests :: TestTree tests = testGroup "Test.Database.LSMTree.Snapshots" [ testProperty "prop_exportImportSnapshot" prop_exportImportSnapshot + , testProperty "prop_exportImportSnapshotSalted" prop_exportImportSnapshotSalted ] {------------------------------------------------------------------------------- @@ -98,3 +99,60 @@ prop_exportImportSnapshot ins los = conf = defaultTableConfig { confWriteBufferAlloc = AllocNumEntries 3 } + +-- | Like 'prop_exportImportSnapshot', but import the exported snapshot into a +-- new session with a different salt +prop_exportImportSnapshotSalted :: + V.Vector (Key, Value) + -> V.Vector Key + -> Property +prop_exportImportSnapshotSalted ins los = + checkCoverage $ + ioProperty $ + withTempIOHasBlockIO "prop_exportImportSnapshot" $ \hfs hbio -> do + FS.createDirectoryIfMissing hfs True sessionDir + + -- Open a session and create a snapshot for some arbitrary table contents + lrs1 <- + withOpenSession nullTracer hfs hbio salt1 sessionDir $ \session -> + withTableWith conf session $ \(table1 :: Table IO Key Value Void) -> do + inserts table1 $ V.map (\(k, v) -> (k, v, Nothing)) ins + saveSnapshot "snap1" "KeyValueBlob" table1 + + -- Export then re-import the snapshot + exportSnapshot session "snap1" exportDir + V.map getValue <$> lookups table1 los + + lrs2 <- + withOpenSession nullTracer hfs hbio salt2 sessionDir $ \session -> do + importSnapshot session "snap2" exportDir + + -- Open a table from the re-imported snapshot. Any corruption of the + -- snapshot would be identified here. + withTableFromSnapshot session "snap2" "KeyValueBlob" $ \(table2 :: Table IO Key Value Void) -> do + V.map getValue <$> lookups table2 los + + -- Check that lookups on both the original and re-imported table + -- match + pure $ + tabulate "# of physical table entries" [ showRangesOf 5 $ V.length ins ] $ + tabulate "# of lookups" [ showRangesOf 5 $ V.length los ] $ + tabulate "# of successful lookups" [ showRangesOf 5 $ V.length $ V.catMaybes lrs1 ] $ + tabulate "# of unsuccessful lookups" [ showRangesOf 5 $ V.length $ V.filter (==Nothing) lrs1 ] $ + lrs1 === lrs2 + where + sessionDir = FS.mkFsPath ["session"] + + -- | Directory for storing exported snapshots + exportDir = FS.mkFsPath ["export"] + + salt1 :: Salt + salt1 = 17 + + salt2 :: Salt + salt2 = 19 + + conf :: TableConfig + conf = defaultTableConfig { + confWriteBufferAlloc = AllocNumEntries 3 + } diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.BloomFilterAlloc.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.BloomFilterAlloc.A.snapshot.golden new file mode 100644 index 000000000..a343dc766 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.BloomFilterAlloc.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.BloomFilterAlloc.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.BloomFilterAlloc.B.snapshot.golden new file mode 100644 index 000000000..51182ace4 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.BloomFilterAlloc.B.snapshot.golden @@ -0,0 +1 @@ +‚ū@ !ūTD- \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.DiskCachePolicy.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.DiskCachePolicy.A.snapshot.golden new file mode 100644 index 000000000..8b040ead3 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.DiskCachePolicy.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.DiskCachePolicy.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.DiskCachePolicy.B.snapshot.golden new file mode 100644 index 000000000..9478b3348 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.DiskCachePolicy.B.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.DiskCachePolicy.C.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.DiskCachePolicy.C.snapshot.golden new file mode 100644 index 000000000..02b41ff4e --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.DiskCachePolicy.C.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.FencePointerIndexType.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.FencePointerIndexType.A.snapshot.golden new file mode 100644 index 000000000..f76dd238a Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.FencePointerIndexType.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.FencePointerIndexType.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.FencePointerIndexType.B.snapshot.golden new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.FencePointerIndexType.B.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.IndexType.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.IndexType.A.snapshot.golden new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.IndexType.A.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.IndexType.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.IndexType.B.snapshot.golden new file mode 100644 index 000000000..f76dd238a Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.IndexType.B.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.LevelMergeType.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.LevelMergeType.A.snapshot.golden new file mode 100644 index 000000000..f76dd238a Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.LevelMergeType.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.LevelMergeType.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.LevelMergeType.B.snapshot.golden new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.LevelMergeType.B.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeBatchSize.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeBatchSize.A.snapshot.golden new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeBatchSize.A.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeBatchSize.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeBatchSize.B.snapshot.golden new file mode 100644 index 000000000..fda74fd9c --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeBatchSize.B.snapshot.golden @@ -0,0 +1 @@ +č \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeCredits.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeCredits.A.snapshot.golden new file mode 100644 index 000000000..a850a922c --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeCredits.A.snapshot.golden @@ -0,0 +1 @@ +X \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeDebt.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeDebt.A.snapshot.golden new file mode 100644 index 000000000..a850a922c --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeDebt.A.snapshot.golden @@ -0,0 +1 @@ +X \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergePolicy.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergePolicy.A.snapshot.golden new file mode 100644 index 000000000..f76dd238a Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergePolicy.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergePolicyForLevel.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergePolicyForLevel.A.snapshot.golden new file mode 100644 index 000000000..f76dd238a Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergePolicyForLevel.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergePolicyForLevel.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergePolicyForLevel.B.snapshot.golden new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergePolicyForLevel.B.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeSchedule.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeSchedule.A.snapshot.golden new file mode 100644 index 000000000..f76dd238a Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeSchedule.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeSchedule.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeSchedule.B.snapshot.golden new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.MergeSchedule.B.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.NominalCredits.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.NominalCredits.A.snapshot.golden new file mode 100644 index 000000000..d9ba7315a --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.NominalCredits.A.snapshot.golden @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.NominalDebt.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.NominalDebt.A.snapshot.golden new file mode 100644 index 000000000..a850a922c --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.NominalDebt.A.snapshot.golden @@ -0,0 +1 @@ +X \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunBloomFilterAlloc.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunBloomFilterAlloc.A.snapshot.golden new file mode 100644 index 000000000..a343dc766 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunBloomFilterAlloc.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunBloomFilterAlloc.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunBloomFilterAlloc.B.snapshot.golden new file mode 100644 index 000000000..51182ace4 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunBloomFilterAlloc.B.snapshot.golden @@ -0,0 +1 @@ +‚ū@ !ūTD- \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunDataCaching.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunDataCaching.A.snapshot.golden new file mode 100644 index 000000000..f76dd238a Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunDataCaching.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunDataCaching.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunDataCaching.B.snapshot.golden new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunDataCaching.B.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunNumber.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunNumber.A.snapshot.golden new file mode 100644 index 000000000..912f823fa Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunNumber.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunParams.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunParams.A.snapshot.golden new file mode 100644 index 000000000..f7584567e Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.RunParams.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SizeRatio.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SizeRatio.A.snapshot.golden new file mode 100644 index 000000000..45a8ca02b --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SizeRatio.A.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapIncomingRun_SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapIncomingRun_SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..36bed8b44 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapIncomingRun_SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapIncomingRun_SnapshotRun.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapIncomingRun_SnapshotRun.B.snapshot.golden new file mode 100644 index 000000000..ca205e30a Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapIncomingRun_SnapshotRun.B.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapLevel_SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapLevel_SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..8dd168350 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapLevel_SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapLevel_SnapshotRun.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapLevel_SnapshotRun.B.snapshot.golden new file mode 100644 index 000000000..8b040ead3 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapLevel_SnapshotRun.B.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapLevels_SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapLevels_SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..cb194cb1b Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapLevels_SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_LevelMergeType_SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_LevelMergeType_SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..76b990b08 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_LevelMergeType_SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_LevelMergeType_SnapshotRun.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_LevelMergeType_SnapshotRun.B.snapshot.golden new file mode 100644 index 000000000..9f9e2e044 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_LevelMergeType_SnapshotRun.B.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_TreeMergeType_SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_TreeMergeType_SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..76b990b08 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_TreeMergeType_SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_TreeMergeType_SnapshotRun.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_TreeMergeType_SnapshotRun.B.snapshot.golden new file mode 100644 index 000000000..15f7f8d29 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingRun_TreeMergeType_SnapshotRun.B.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTreeState_SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTreeState_SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..98f36c424 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTreeState_SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTreeState_SnapshotRun.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTreeState_SnapshotRun.B.snapshot.golden new file mode 100644 index 000000000..6b0b72b1e Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTreeState_SnapshotRun.B.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTreeState_SnapshotRun.C.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTreeState_SnapshotRun.C.snapshot.golden new file mode 100644 index 000000000..fa646e817 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTreeState_SnapshotRun.C.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTree_SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTree_SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..98f36c424 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapMergingTree_SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPendingMerge_SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPendingMerge_SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..d8c5ad61a Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPendingMerge_SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPendingMerge_SnapshotRun.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPendingMerge_SnapshotRun.B.snapshot.golden new file mode 100644 index 000000000..940198ac2 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPendingMerge_SnapshotRun.B.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPreExistingRun_SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPreExistingRun_SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..98f36c424 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPreExistingRun_SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPreExistingRun_SnapshotRun.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPreExistingRun_SnapshotRun.B.snapshot.golden new file mode 100644 index 000000000..382dba28d Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapPreExistingRun_SnapshotRun.B.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotLabel.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotLabel.A.snapshot.golden new file mode 100644 index 000000000..97651bcee --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotLabel.A.snapshot.golden @@ -0,0 +1 @@ +qUserProvidedLabel \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotLabel.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotLabel.B.snapshot.golden new file mode 100644 index 000000000..64845fb76 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotLabel.B.snapshot.golden @@ -0,0 +1 @@ +` \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotMetaData.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotMetaData.A.snapshot.golden new file mode 100644 index 000000000..ea44b39c2 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotMetaData.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..aa80fd76e Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.TableConfig.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.TableConfig.A.snapshot.golden new file mode 100644 index 000000000..77830a933 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.TableConfig.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.TreeMergeType.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.TreeMergeType.A.snapshot.golden new file mode 100644 index 000000000..6b2aaa764 --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.TreeMergeType.A.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.TreeMergeType.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.TreeMergeType.B.snapshot.golden new file mode 100644 index 000000000..25cb955ba --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.TreeMergeType.B.snapshot.golden @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.Vector_SnapshotRun.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.Vector_SnapshotRun.A.snapshot.golden new file mode 100644 index 000000000..d8c8a9833 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.Vector_SnapshotRun.A.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.Vector_SnapshotRun.B.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.Vector_SnapshotRun.B.snapshot.golden new file mode 100644 index 000000000..5416677bc --- /dev/null +++ b/lsm-tree/test/golden-file-data/snapshot-codec/V3.Vector_SnapshotRun.B.snapshot.golden @@ -0,0 +1 @@ +€ \ No newline at end of file diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.Vector_SnapshotRun.C.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.Vector_SnapshotRun.C.snapshot.golden new file mode 100644 index 000000000..b2b418081 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.Vector_SnapshotRun.C.snapshot.golden differ diff --git a/lsm-tree/test/golden-file-data/snapshot-codec/V3.WriteBufferAlloc.A.snapshot.golden b/lsm-tree/test/golden-file-data/snapshot-codec/V3.WriteBufferAlloc.A.snapshot.golden new file mode 100644 index 000000000..ce1e271e2 Binary files /dev/null and b/lsm-tree/test/golden-file-data/snapshot-codec/V3.WriteBufferAlloc.A.snapshot.golden differ