lsm-tree-1.0.0.0: app/Database/LSMTree/Demo.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
{- HLINT ignore "Redundant pure" -}
module Database.LSMTree.Demo (demo) where
import Control.Exception (SomeException, try)
import Control.Monad (when)
import Control.Monad.Class.MonadST (MonadST (..))
import qualified Control.Monad.IOSim as IOSim
import Control.Monad.Primitive (RealWorld)
import Control.Monad.ST.Unsafe (unsafeIOToST)
import Control.Tracer (nullTracer)
import Data.Functor (void)
import Data.Primitive.PrimVar (PrimVar, newPrimVar, readPrimVar,
writePrimVar)
import Data.Typeable (Typeable)
import qualified Data.Vector as V
import Data.Word (Word64)
import Database.LSMTree as LSMT
import qualified System.Directory as IO (createDirectoryIfMissing,
doesDirectoryExist, removeDirectoryRecursive)
import qualified System.FS.API as FS
import qualified System.FS.BlockIO.API as FS
import qualified System.FS.BlockIO.IO as FS
import qualified System.FS.BlockIO.Sim as FSSim
import qualified System.FS.Sim.MockFS as FSSim
import System.IO.Unsafe (unsafePerformIO)
-- | Interactive demo showing functional requiremens for the @lsm-tree@ library
-- are met.
--
-- The functional requirements are discussed in this document: "Storing the
-- Cardano ledger state on disk: final report for high-performance backend"
--
-- Sections of the demo code are headed by the number of the corresponding
-- functional requirement.
demo :: Bool -> IO ()
demo interactive = do
freshDirectory "_demo"
withOpenSessionIO tracer "_demo" $ \session -> do
withTableWith config session $ \(table :: Table IO K V B) -> do
pause interactive -- [0]
-- 2. basic key-value store operations
inserts table $ V.fromList [ (K i, V i, Just (B i)) | i <- [1 .. 10_000] ]
as <- lookups table $ V.fromList [ K 1, K 2, K 3, K 4 ]
print (fmap getValue as)
pause interactive -- [1]
deletes table $ V.fromList [ K i | i <- [1 .. 10_000], even i ]
bs <- lookups table $ V.fromList [ K 1, K 2, K 3, K 4 ]
print (fmap getValue bs)
pause interactive -- [2]
-- 2. Intermezzo: blob retrieval
cs <- try @SomeException $ retrieveBlobs session $ V.mapMaybe getBlob as
print cs
pause interactive -- [3]
ds <- try @SomeException $ retrieveBlobs session $ V.mapMaybe getBlob bs
print ds
pause interactive -- [4]
-- 3. range lookups and cursors
es <- rangeLookup table $ FromToIncluding (K 1) (K 4)
print (fmap getEntryValue es)
pause interactive -- [5]
withCursorAtOffset table (K 1) $ \cursor -> do
fs <- LSMT.take 2 cursor
print (fmap getEntryValue fs)
pause interactive -- [6]
-- 4. upserts (or monoidal updates)
-- better than lookup followed by insert
upserts table $ V.fromList [ (K i, V 1) | i <- [1 .. 10_000] ]
gs <- lookups table $ V.fromList [ K 1, K 2, K 3, K 4 ]
print (fmap getValue gs)
pause interactive -- [7]
-- 5. multiple independently writable references
withDuplicate table $ \dupliTable -> do
inserts dupliTable $ V.fromList [ (K i, V 1, Nothing) | i <- [1 .. 10_000] ]
hs <- lookups dupliTable $ V.fromList [ K 1, K 2, K 3, K 4 ]
print (fmap getValue hs)
pause interactive -- [8]
is <- lookups table $ V.fromList [ K 1, K 2, K 3, K 4]
print (fmap getValue is)
pause interactive -- [9]
-- 6. snapshots
saveSnapshot "odds_evens" label table
saveSnapshot "all_ones" label dupliTable
js <- listSnapshots session
print js
pause interactive -- [10]
-- 6. snapshots continued
withTableFromSnapshot session "odds_evens" label $ \(table :: Table IO K V B) -> do
withTableFromSnapshot session "all_ones" label $ \(dupliTable :: Table IO K V B) -> do
pause interactive -- [11]
-- 7. table unions
withUnion table dupliTable $ \uniTable -> do
ks <- lookups uniTable $ V.fromList [ K 1, K 2, K 3, K 4]
print (fmap getValue ks)
pause interactive -- [12]
withIncrementalUnion table dupliTable $ \uniTable -> do
ls <- lookups uniTable $ V.fromList [ K 1, K 2, K 3, K 4]
print (fmap getValue ls)
pause interactive -- [13]
m@(UnionDebt m') <- remainingUnionDebt uniTable
supplyUnionCredits uniTable (UnionCredits (m' `div` 2))
print m
pause interactive -- [14]
ns <- lookups uniTable $ V.fromList [ K 1, K 2, K 3, K 4]
print (fmap getValue ns)
pause interactive -- [15]
-- 8. simulation
let
simpleAction ::
(LSMT.IOLike m, Typeable h)
=> FS.HasFS m h -> FS.HasBlockIO m h -> m ()
simpleAction hasFS hasBlockIO = do
let sessionDir = FS.mkFsPath ["_demo"]
FS.createDirectoryIfMissing hasFS False sessionDir
withOpenSession tracer hasFS hasBlockIO 17 sessionDir $ \session -> do
withTableWith config session $ \(table :: Table m K V B) -> do
inserts table $ V.fromList [ (K i, V i, Just (B i)) | i <- [1 .. 10_000] ]
os <- lookups table $ V.fromList [ K 1, K 2, K 3, K 4 ]
print' (fmap getValue os)
do
FS.withIOHasBlockIO (FS.MountPoint "") FS.defaultIOCtxParams $ \hasFS hasBlockIO -> do
simpleAction hasFS hasBlockIO
pause interactive -- [16]
do
pure $! IOSim.runSimOrThrow $ do
(hasFS, hasBlockIO) <- FSSim.simHasBlockIO' FSSim.empty
simpleAction hasFS hasBlockIO
pause interactive -- [17]
{-------------------------------------------------------------------------------
Types
-------------------------------------------------------------------------------}
newtype K = K Word64
deriving stock (Show, Eq)
deriving newtype SerialiseKey
newtype V = V Word64
deriving stock (Show, Eq)
deriving newtype (Num, SerialiseValue)
instance ResolveValue V where
resolve = (+)
newtype B = B Word64
deriving stock (Show, Eq)
deriving newtype (Num, SerialiseValue)
config :: TableConfig
config = defaultTableConfig {
confWriteBufferAlloc = AllocNumEntries 172
}
tracer :: Monad m => Tracer m LSMTreeTrace
tracer = nullTracer
label :: SnapshotLabel
label = "KVB"
{-------------------------------------------------------------------------------
Utils
-------------------------------------------------------------------------------}
{-# NOINLINE pauseRef #-}
pauseRef :: PrimVar RealWorld Int
pauseRef = unsafePerformIO $ newPrimVar 0
incrPauseRef :: IO Int
incrPauseRef = do
x <- readPrimVar pauseRef
writePrimVar pauseRef $! x + 1
pure x
pause :: Bool -> IO ()
pause interactive = do
x <- incrPauseRef
putStr ("[" <> show x <> "] " <> "press ENTER to continue...")
if interactive
then void $ getLine
else putStrLn ""
freshDirectory :: FilePath -> IO ()
freshDirectory path = do
b <- IO.doesDirectoryExist path
when b $ IO.removeDirectoryRecursive path
IO.createDirectoryIfMissing False path
print' :: (Show a, MonadST m) => a -> m ()
print' x = stToIO $ unsafeIOToST $ print x