packages feed

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