1
1
mirror of https://github.com/aelve/guide.git synced 2024-11-23 21:13:07 +03:00

Add a note about acid-state

This commit is contained in:
Artyom 2016-03-28 23:52:20 +03:00
parent e4555d0da3
commit c408b8b08d
3 changed files with 27 additions and 3 deletions

View File

@ -59,12 +59,28 @@ import JS (JS(..), allJSFunctions)
import Utils
------------------------------------------------------------------------------
-- working with global state via acid-state
------------------------------------------------------------------------------
{- Note [acid-state]
~~~~~~~~~~~~~~~~~~~~
This application doesn't use a database instead, it uses acid-state. Acid-state works as follows:
* Everything is stored as Haskell values (in particular, all data is stored in 'GlobalState').
* All changes to the state (and all queries) have to be done by using 'dbUpdate'/'dbQuery' and types (GetItem, SetItemName, etc) from the Types.hs module.
* The data is kept in-memory, but all changes are logged to the disk (which lets us recover the state in case of a crash by reapplying the changes) and you can't access the state directly. When the application exits, it creates a snapshot of the state (called checkpoint) and writes it to the disk. Additionally, a checkpoint is created every hour (grep for createCheckpoint).
* When any type is changed, we have to write a migration function that would read the old version of the type and turn it into the new version. It's enough to keep just one old version (and even that isn't needed after the migration happened and a new checkpoint has been created). For examples, look at instance Migrate in Types.hs. Also, all types involved in acid-state (whether migrate-able or not) have to have a SafeCopy instance, which is generated by 'deriveSafeCopy'.
* There are actually ways to access the state directly (GetGlobalState and SetGlobalState), but the latter should only be used when doing something one-off (like migrating all IDs to a different ID scheme, or whatever).
-}
-- | A pointer to an open acid-state database (allows making queries/updates,
-- creating checkpoints, etc).
type DB = AcidState GlobalState
-- | Update something in the database.
dbUpdate :: (MonadIO m, HasSpock m, SpockState m ~ ServerState,
EventState event ~ GlobalState, UpdateEvent event)
=> event -> m (EventResult event)
@ -72,6 +88,7 @@ dbUpdate x = do
db <- _db <$> Spock.getState
liftIO $ Acid.update db x
-- | Read something from the database.
dbQuery :: (MonadIO m, HasSpock m, SpockState m ~ ServerState,
EventState event ~ GlobalState, QueryEvent event)
=> event -> m (EventResult event)
@ -317,6 +334,8 @@ main = do
-- running. This makes running this in GHCi annoying, because you have to
-- restart GHCi before every run. So, we kill the thread in the finaliser.
ekgId <- newIORef Nothing
-- See Note [acid-state] for the explanation of 'openLocalStateFrom',
-- 'createCheckpoint', etc
let prepare = openLocalStateFrom "state/" emptyState
finalise db = do
createCheckpoint db

View File

@ -113,12 +113,15 @@ data Trait = Trait {
_traitContent :: MarkdownInline }
deriving (Eq)
-- See Note [acid-state]
deriveSafeCopy 1 'extension ''Trait
makeFields ''Trait
-- Old version, needed for safe migration. It can most likely be already
-- deleted (if a checkpoint has been created), but it's been left here as a
-- template for future migrations.
--
-- Again, see Note [acid-state].
data Trait_v0 = Trait_v0 {
_traitUid_v0 :: Uid,
_traitContent_v0 :: Text }
@ -306,6 +309,7 @@ instance Migrate Category where
--
-- See Note [acid-state]
data GlobalState = GlobalState {
_categories :: [Category],
_categoriesDeleted :: [Category] }

View File

@ -112,6 +112,7 @@ makeSlug =
newtype Uid = Uid {uidToText :: Text}
deriving (Eq, Ord, Show, PathPiece, Format.Buildable)
-- See Note [acid-state]
deriveSafeCopy 0 'base ''Uid
instance IsString Uid where