Skip to content

Commit 9a9adc5

Browse files
committed
Define SnapshotVersion and SnapshotMetaData
1 parent 0e3012f commit 9a9adc5

File tree

5 files changed

+127
-17
lines changed

5 files changed

+127
-17
lines changed

src/Database/LSMTree/Common.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ module Database.LSMTree.Common (
1818
-- * Small types
1919
, Internal.Range (..)
2020
-- * Snapshots
21-
, Internal.SnapshotLabel
2221
, Labellable (..)
2322
, deleteSnapshot
2423
, listSnapshots
@@ -54,6 +53,7 @@ import Control.Monad.Fix (MonadFix)
5453
import Control.Monad.Primitive (PrimMonad)
5554
import Control.Tracer (Tracer)
5655
import Data.Kind (Type)
56+
import Data.Text (Text)
5757
import Data.Typeable (Proxy, Typeable)
5858
import qualified Database.LSMTree.Internal as Internal
5959
import qualified Database.LSMTree.Internal.BlobRef as Internal
@@ -192,7 +192,7 @@ closeSession (Internal.Session' sesh) = Internal.closeSession sesh
192192
-- directly instead, instead of deriving the label from a type using this type
193193
-- class.
194194
class Labellable a where
195-
makeSnapshotLabel :: Proxy a -> Internal.SnapshotLabel
195+
makeSnapshotLabel :: Proxy a -> Text
196196

197197
{-# SPECIALISE deleteSnapshot ::
198198
Session IO

src/Database/LSMTree/Internal/Snapshot.hs

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22
{-# OPTIONS_GHC -Wno-orphans #-}
33

44
module Database.LSMTree.Internal.Snapshot (
5-
SnapshotLabel (..)
5+
-- * Versioning
6+
SnapshotVersion (..)
7+
, major
8+
, minor
9+
, fromMajorMinor
10+
, prettySnapshotVersion
11+
, currentSnapshotVersion
12+
-- * Snapshot metadata
13+
, SnapshotMetaData (..)
14+
, SnapshotLabel (..)
615
-- * Snapshot format
716
, numSnapRuns
817
, SnapLevels
@@ -24,7 +33,6 @@ import Control.Monad.Primitive (PrimMonad)
2433
import Control.TempRegistry
2534
import Data.Foldable (forM_)
2635
import Data.Primitive.PrimVar
27-
import Data.String
2836
import Data.Text (Text)
2937
import qualified Data.Vector as V
3038
import Database.LSMTree.Internal.Config
@@ -42,10 +50,115 @@ import Database.LSMTree.Internal.UniqCounter (UniqCounter,
4250
import System.FS.API (HasFS)
4351
import System.FS.BlockIO.API (HasBlockIO)
4452

53+
{-------------------------------------------------------------------------------
54+
Versioning
55+
-------------------------------------------------------------------------------}
56+
57+
-- | The version of a snapshot.
58+
--
59+
-- When encoding snapshot metadata, 'currentSnapshotVersion' is included in the
60+
-- file. When snapshot metadata is decoded, then the decoded version is checked
61+
-- for compatibility against 'currentSnapshotVersion'. Decoding will fail if the
62+
-- versions are incompatible.
63+
--
64+
-- A version @x.y@ has two components: a major version number @x@ and a minor
65+
-- version number @y@. The major version number is used to signal breaking
66+
-- changes. The minor version number is used to signal non-breaking changes that
67+
-- are backwards compatible. To be precise, @x.y@ is guaranteed to be backwards
68+
-- compatible with @x'.y'@ as long as @x == x'@ and @y' <= y@. If @x > x'@, then
69+
-- backwards compatibility *may* be provided, but is not guaranteed. Forward
70+
-- compatibilitty is never guaranteed.
71+
--
72+
-- If @x.y@ is our current version, and if it @x.y@ is backwards compatible with
73+
-- @x'.y'@, then @x.y@ can decode snapshots that were encoded at version
74+
-- @x'.y'@.
75+
--
76+
-- The version number determines the format of the snapshot metadata file, but
77+
-- it also doubles as versioning information for the entire snapshot itself. For
78+
-- example, if a breaking change is made to the merge scheduling algorithm that
79+
-- would lead to errors when loading an older snapshot, then the major version
80+
-- should be increased as well.
81+
data SnapshotVersion = V0_0
82+
deriving stock (Show, Eq)
83+
84+
-- >>> major currentSnapshotVersion
85+
-- 0
86+
major :: SnapshotVersion -> Word
87+
major V0_0 = 0
88+
89+
-- >>> minor currentSnapshotVersion
90+
-- 0
91+
minor :: SnapshotVersion -> Word
92+
minor V0_0 = 0
93+
94+
fromMajorMinor :: Word -> Word -> Maybe SnapshotVersion
95+
fromMajorMinor 0 0 = Just V0_0
96+
fromMajorMinor _ _ = Nothing
97+
98+
-- >>> prettySnapshotVersion currentSnapshotVersion
99+
-- "v0.0"
100+
prettySnapshotVersion :: SnapshotVersion -> String
101+
prettySnapshotVersion version = prettyVersion (major version) (minor version)
102+
103+
-- >>> prettyVersion 17 32
104+
-- "v17.32"
105+
prettyVersion :: Word -> Word -> String
106+
prettyVersion majo mino =
107+
showChar 'v'
108+
. shows majo
109+
. showChar '.'
110+
. shows mino
111+
$ ""
112+
113+
-- >>> currentSnapshotVersion
114+
-- V0_0
115+
currentSnapshotVersion :: SnapshotVersion
116+
currentSnapshotVersion = V0_0
117+
118+
_isCompatible :: SnapshotVersion -> Either String ()
119+
_isCompatible otherVersion = do
120+
case ( currentSnapshotVersion, otherVersion ) of
121+
(V0_0, V0_0) -> Right ()
122+
123+
{-------------------------------------------------------------------------------
124+
Snapshot metadata
125+
-------------------------------------------------------------------------------}
126+
45127
-- | Custom text to include in a snapshot file
46128
newtype SnapshotLabel = SnapshotLabel Text
47129
deriving stock (Show, Eq, Read)
48-
deriving newtype (Semigroup, IsString)
130+
131+
data SnapshotTableType = SnapNormalTable | SnapMonoidalTable
132+
deriving stock (Show, Eq, Read)
133+
134+
data SnapshotMetaData = SnapshotMetaData {
135+
-- | Custom, user-supplied text that is included in the metadata.
136+
--
137+
-- The main use case for this field is for the user to supply textual
138+
-- information about the key\/value\/blob type for the table that
139+
-- corresponds to the snapshot. This information can then be used to
140+
-- dynamically check that a snapshot is opened at the correct
141+
-- key\/value\/blob type.
142+
--
143+
-- One could argue that the 'SnapshotName' could be used to to hold this
144+
-- information, but the file name of snapshot meta data is not guarded by a
145+
-- checksum, wherease the contents of the file are. Therefore using the
146+
-- 'SnapshotLabel' is safer.
147+
snapMetaLabel :: !SnapshotLabel
148+
-- | Whether a table is normal or monoidal.
149+
--
150+
-- TODO: if we at some point decide to get rid of the normal vs. monoidal
151+
-- distinction, we can get rid of this field.
152+
, snapMetaTableType :: !SnapshotTableType
153+
-- | The 'TableConfig' for the snapshotted table.
154+
--
155+
-- Some of these configuration options can be overridden when a snapshot is
156+
-- opened: see 'TableConfigOverride'.
157+
, snapMetaConfig :: !TableConfig
158+
-- | The shape of the LSM tree.
159+
, snapMetaLevels :: !SnapLevels
160+
}
161+
deriving stock (Show, Eq)
49162

50163
{-------------------------------------------------------------------------------
51164
Levels snapshot format

src/Database/LSMTree/Monoidal.hs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ module Database.LSMTree.Monoidal (
8888
-- * Durability (snapshots)
8989
, SnapshotName
9090
, Common.mkSnapshotName
91-
, Common.SnapshotLabel
9291
, Common.Labellable (..)
9392
, snapshot
9493
, open
@@ -139,6 +138,7 @@ import qualified Database.LSMTree.Internal as Internal
139138
import qualified Database.LSMTree.Internal.Entry as Entry
140139
import Database.LSMTree.Internal.RawBytes (RawBytes)
141140
import qualified Database.LSMTree.Internal.Serialise as Internal
141+
import qualified Database.LSMTree.Internal.Snapshot as Internal
142142
import qualified Database.LSMTree.Internal.Vector as V
143143

144144
-- $resource-management
@@ -561,8 +561,7 @@ snapshot :: forall m k v.
561561
snapshot snap (Internal.MonoidalTable t) =
562562
void $ Internal.snapshot (resolve @v Proxy) snap label t
563563
where
564-
-- to ensure we don't open a monoidal table as normal later
565-
label = Common.makeSnapshotLabel (Proxy @(k, v)) <> " (monoidal)"
564+
label = Internal.SnapshotLabel $ Common.makeSnapshotLabel (Proxy @(k, v))
566565

567566
{-# SPECIALISE open ::
568567
(SerialiseKey k, SerialiseValue v, ResolveValue v, Common.Labellable (k, v))
@@ -606,8 +605,7 @@ open :: forall m k v.
606605
open (Internal.Session' sesh) override snap =
607606
Internal.MonoidalTable <$> Internal.open sesh label override snap (resolve @v Proxy)
608607
where
609-
-- to ensure that the table is really a monoidal table
610-
label = Common.makeSnapshotLabel (Proxy @(k, v)) <> " (monoidal)"
608+
label = Internal.SnapshotLabel $ Common.makeSnapshotLabel (Proxy @(k, v))
611609

612610
{-------------------------------------------------------------------------------
613611
Multiple writable tables

src/Database/LSMTree/Normal.hs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ module Database.LSMTree.Normal (
8989
-- * Durability (snapshots)
9090
, SnapshotName
9191
, Common.mkSnapshotName
92-
, Common.SnapshotLabel
9392
, Common.Labellable (..)
9493
, snapshot
9594
, open
@@ -129,6 +128,7 @@ import qualified Database.LSMTree.Internal as Internal
129128
import qualified Database.LSMTree.Internal.BlobRef as Internal
130129
import qualified Database.LSMTree.Internal.Entry as Entry
131130
import qualified Database.LSMTree.Internal.Serialise as Internal
131+
import qualified Database.LSMTree.Internal.Snapshot as Internal
132132
import qualified Database.LSMTree.Internal.Vector as V
133133
import qualified System.FS.API as FS
134134

@@ -687,8 +687,7 @@ snapshot :: forall m k v blob.
687687
-> m ()
688688
snapshot snap (Internal.NormalTable t) = void $ Internal.snapshot const snap label t
689689
where
690-
-- to ensure we don't open a normal table as monoidal later
691-
label = Common.makeSnapshotLabel (Proxy @(k, v, blob)) <> " (normal)"
690+
label = Internal.SnapshotLabel $ Common.makeSnapshotLabel (Proxy @(k, v, blob))
692691

693692
{-# SPECIALISE open ::
694693
( SerialiseKey k
@@ -735,8 +734,7 @@ open :: forall m k v blob.
735734
open (Internal.Session' sesh) override snap =
736735
Internal.NormalTable <$!> Internal.open sesh label override snap const
737736
where
738-
-- to ensure that the table is really a normal table
739-
label = Common.makeSnapshotLabel (Proxy @(k, v, blob)) <> " (normal)"
737+
label = Internal.SnapshotLabel $ Common.makeSnapshotLabel (Proxy @(k, v, blob))
740738

741739
{-------------------------------------------------------------------------------
742740
Mutiple writable tables

test/Test/Database/LSMTree/Internal.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import Database.LSMTree.Internal.Entry
2929
import Database.LSMTree.Internal.MergeSchedule
3030
import Database.LSMTree.Internal.Paths (mkSnapshotName)
3131
import Database.LSMTree.Internal.Serialise
32+
import Database.LSMTree.Internal.Snapshot (SnapshotLabel (..))
3233
import qualified System.FS.API as FS
3334
import qualified Test.Database.LSMTree.Internal.Lookup as Test
3435
import Test.Database.LSMTree.Internal.Lookup
@@ -173,8 +174,8 @@ prop_interimOpenTable dat = ioProperty $
173174
withTable sesh conf $ \t -> do
174175
updates const upds t
175176
let snap = fromMaybe (error "invalid name") $ mkSnapshotName "snap"
176-
numRunsSnapped <- snapshot const snap "someLabel" t
177-
t' <- open sesh "someLabel" configNoOverride snap const
177+
numRunsSnapped <- snapshot const snap (SnapshotLabel "someLabel") t
178+
t' <- open sesh (SnapshotLabel "someLabel") configNoOverride snap const
178179
lhs <- fetchBlobs hfs =<< lookups const ks t
179180
rhs <- fetchBlobs hfs =<< lookups const ks t'
180181
-- We must fetch blobs because comparing blob references is meaningless

0 commit comments

Comments
 (0)