packages feed

lsm-tree-1.1.0.0: src-core/Database/LSMTree/Internal/Config/Override.hs

{-# LANGUAGE NamedFieldPuns #-}
{-# OPTIONS_HADDOCK not-home #-}

-- Definitions for override table config options.
module Database.LSMTree.Internal.Config.Override (
    -- $override-policy

    -- * Override table config
    TableConfigOverride (..)
  , noTableConfigOverride
  , overrideTableConfig
  ) where

import qualified Data.Vector as V
import           Database.LSMTree.Internal.Config
import           Database.LSMTree.Internal.MergeSchedule (MergePolicyForLevel,
                     NominalCredits, NominalDebt)
import           Database.LSMTree.Internal.MergingRun (LevelMergeType,
                     MergeCredits, MergeDebt, TreeMergeType)
import           Database.LSMTree.Internal.Run
import           Database.LSMTree.Internal.RunAcc (RunBloomFilterAlloc)
import           Database.LSMTree.Internal.RunNumber (RunNumber)
import           Database.LSMTree.Internal.Snapshot

-- $override-policy
--
-- === Limitations
--
-- Overriding config options should in many cases be possible as long as there
-- is a mitigation strategy to ensure that a change in options does not cause
-- the database state of a table to become inconsistent. But what does this
-- strategy look like? And if we allow a config option to be overridden on a
-- live table (one that a user has access to), how should we apply these new
-- options to shared data like merging runs? Moreover, would we answer these
-- questions differently for each type of config option?
--
-- For now, it seems to be the most straightforward to limit the config options
-- we allow to be overridden, and that we only change the config options
-- offline. That is, we override the config option just before opening a
-- snapshot from disk. At that point, there is no sharing because the table is
-- not live yet, which simplifies how changing a config option is handled.
--
-- Another complicating factor is that we have thought about the possibility of
-- restoring sharing of ongoing merges between live tables and newly opened
-- snapshots. At that point, we run into the same challenges again... But for
-- now, changing only the disk cache policy and merge batch size offline should
-- work fine.

{-------------------------------------------------------------------------------
  Helper class
-------------------------------------------------------------------------------}

-- | This class is only here so that we can recursively call 'override' on all
-- fields of a datatype, instead of having to invent a new name for each type
-- that the function is called on such as @overrideTableConfig@,
-- @overrideSnapshotRun@, etc.
class Override o a where
  override :: o -> a -> a

instance Override a c => Override (Maybe a) c where
  override = maybe id override

{-------------------------------------------------------------------------------
  Override table config
-------------------------------------------------------------------------------}

{- |
The 'TableConfigOverride' can be used to override the 'TableConfig'
when opening a table from a snapshot.
-}
data TableConfigOverride = TableConfigOverride {
       overrideDiskCachePolicy :: Maybe DiskCachePolicy,
       overrideMergeBatchSize  :: Maybe MergeBatchSize
     }
  deriving stock (Show, Eq)

-- | No override of the 'TableConfig'. You can use this as a default value and
-- record update to override some parameters, while being future-proof to new
-- parameters, e.g.
--
-- > noTableConfigOverride { overrideDiskCachePolicy = DiskCacheNone }
--
noTableConfigOverride :: TableConfigOverride
noTableConfigOverride = TableConfigOverride Nothing Nothing

-- | Override the a subset of the table configuration parameters that are
-- stored in snapshot metadata.
--
-- Tables opened from the new 'SnapshotMetaData' will use the new value for the
-- table configuration.
overrideTableConfig :: TableConfigOverride
                    -> SnapshotMetaData -> SnapshotMetaData
overrideTableConfig = override

instance Override TableConfigOverride SnapshotMetaData where
  override TableConfigOverride {..} =
      override overrideMergeBatchSize
    . override overrideDiskCachePolicy

{-------------------------------------------------------------------------------
  Override merge batch size
-------------------------------------------------------------------------------}

instance Override MergeBatchSize SnapshotMetaData where
  override mbs smd =
    smd { snapMetaConfig = override mbs (snapMetaConfig smd) }

instance Override MergeBatchSize TableConfig where
  override confMergeBatchSize' tc =
    tc { confMergeBatchSize = confMergeBatchSize' }

{-------------------------------------------------------------------------------
  Override disk cache policy
-------------------------------------------------------------------------------}

-- NOTE: the instances below explicitly pattern match on the types of
-- constructor fields. This makes the code more verbose, but it also makes the
-- code a little more future proof. It should help us not to forget to update
-- the instances when new fields are added or existing fields change. In
-- particular, if anything changes about the constructor or its fields (and
-- their types), then we will see a compiler error, and then we are forced to
-- look at the code and make adjustments.

instance Override DiskCachePolicy SnapshotMetaData where
  override dcp
    (SnapshotMetaData (sl :: SnapshotLabel)
      (tc :: TableConfig) (rn :: RunNumber)
      (sls :: (SnapLevels SnapshotRun))
      (smt :: (Maybe (SnapMergingTree SnapshotRun))))
    = SnapshotMetaData sl (override dcp tc) rn (override dcp sls) $
        let rdc = diskCachePolicyForLevel dcp UnionLevel
        in  fmap (override rdc) smt

instance Override DiskCachePolicy TableConfig where
  override confDiskCachePolicy' tc =
    tc { confDiskCachePolicy = confDiskCachePolicy' }

instance Override DiskCachePolicy (SnapLevels SnapshotRun) where
  override dcp (SnapLevels (vec :: V.Vector (SnapLevel SnapshotRun))) =
      SnapLevels $ V.imap go vec
    where
      go (LevelNo . (+1) -> ln) (x :: SnapLevel SnapshotRun) =
          let rdc = diskCachePolicyForLevel dcp (RegularLevel ln)
          in  override rdc x

instance Override RunDataCaching (SnapLevel SnapshotRun) where
  override _ lvl@SnapEmptyLevel =
    lvl
  override rdc
    (SnapLevel (sir :: SnapIncomingRun SnapshotRun) (srs :: V.Vector SnapshotRun))
    = SnapLevel (override rdc sir) (V.map (override rdc) srs)

instance Override RunDataCaching (SnapIncomingRun SnapshotRun) where
  override rdc = \case
      SnapIncomingSingleRun (sr :: SnapshotRun) ->
        SnapIncomingSingleRun $ override rdc sr
      SnapIncomingMergingRun
        (mpfl :: MergePolicyForLevel) (nd :: NominalDebt)
        (nc :: NominalCredits) (smr :: SnapMergingRun LevelMergeType SnapshotRun) ->
          SnapIncomingMergingRun mpfl nd nc (override rdc smr)

instance Override RunDataCaching (SnapMergingRun t SnapshotRun) where
  override rdc = \case
      SnapCompletedMerge (md :: MergeDebt) (sr :: SnapshotRun) ->
        SnapCompletedMerge md (override rdc sr)
      SnapOngoingMerge
        (rp :: RunParams) (mc :: MergeCredits)
        (srs :: V.Vector SnapshotRun) (t :: t) ->
          SnapOngoingMerge (override rdc rp) mc (V.map (override rdc) srs) t

instance Override RunDataCaching RunParams where
  override rdc
    (RunParams (_rdc :: RunDataCaching) (rbfa :: RunBloomFilterAlloc) (it :: IndexType))
    = RunParams rdc rbfa it

instance Override RunDataCaching SnapshotRun where
  override rdc
    (SnapshotRun (rn :: RunNumber) (_rdc :: RunDataCaching) (it ::IndexType))
    = SnapshotRun rn rdc it

instance Override RunDataCaching (SnapMergingTree SnapshotRun) where
  override rdc (SnapMergingTree (smts ::  SnapMergingTreeState SnapshotRun))
    = SnapMergingTree (override rdc smts)

instance Override RunDataCaching (SnapMergingTreeState SnapshotRun) where
  override rdc = \case
      SnapCompletedTreeMerge (sr :: SnapshotRun) ->
        SnapCompletedTreeMerge (override rdc sr)
      SnapPendingTreeMerge (spm :: SnapPendingMerge SnapshotRun) ->
        SnapPendingTreeMerge (override rdc spm)
      SnapOngoingTreeMerge (smr :: SnapMergingRun TreeMergeType SnapshotRun) ->
        SnapOngoingTreeMerge (override rdc smr)

instance Override RunDataCaching (SnapPendingMerge SnapshotRun) where
  override rdc = \case
      SnapPendingLevelMerge
        (spers :: [SnapPreExistingRun SnapshotRun])
        (msmt :: Maybe (SnapMergingTree SnapshotRun)) ->
          SnapPendingLevelMerge (fmap (override rdc) spers) (fmap (override rdc) msmt)
      SnapPendingUnionMerge (smts :: [SnapMergingTree SnapshotRun]) ->
        SnapPendingUnionMerge (fmap (override rdc) smts)

instance Override RunDataCaching (SnapPreExistingRun SnapshotRun) where
  override rdc = \case
      SnapPreExistingRun (sr :: SnapshotRun) -> SnapPreExistingRun (override rdc sr)
      SnapPreExistingMergingRun (smr :: SnapMergingRun LevelMergeType SnapshotRun) ->
        SnapPreExistingMergingRun (override rdc smr)