generate more compact git-annex branch for imports
authorJoey Hess <joeyh@joeyh.name>
Wed, 23 Dec 2020 19:21:33 +0000 (15:21 -0400)
committerJoey Hess <joeyh@joeyh.name>
Wed, 23 Dec 2020 19:25:16 +0000 (15:25 -0400)
Especially from borg, where the content identifier logs
all end up being the same identical file!

But also, for other imports, the location tracking logs can,
in some cases, be identical files.

Bonus optimisation: Avoid looking up (and parsing when set)
GIT_ANNEX_VECTOR_CLOCK env var every time a log is written to.
Although the lookup does happen at startup even when no
log will be written now.

26 files changed:
Annex.hs
Annex/Import.hs
Annex/VectorClock.hs
Annex/VectorClock/Utility.hs [new file with mode: 0644]
Command/Forget.hs
Command/MetaData.hs
Logs/Activity.hs
Logs/Chunk.hs
Logs/Config.hs
Logs/ContentIdentifier.hs
Logs/Difference.hs
Logs/Export.hs
Logs/Group.hs
Logs/MetaData.hs
Logs/Multicast.hs
Logs/PreferredContent/Raw.hs
Logs/Presence.hs
Logs/Remote.hs
Logs/RemoteState.hs
Logs/Schedule.hs
Logs/SingleValue.hs
Logs/Trust/Basic.hs
Logs/UUID.hs
Types/VectorClock.hs [new file with mode: 0644]
doc/todo/use_same_vector_clock_for_content_identifier_updates_in_import.mdwn
git-annex.cabal

index 32686cf27461851790ce6143194827f66e7fe6b5..af34393b819107743c810f570322b90d35ce9b2b 100644 (file)
--- a/Annex.hs
+++ b/Annex.hs
@@ -72,6 +72,8 @@ import Types.IndexFiles
 import Types.CatFileHandles
 import Types.RemoteConfig
 import Types.TransferrerPool
+import Types.VectorClock
+import Annex.VectorClock.Utility
 import qualified Database.Keys.Handle as Keys
 import Utility.InodeCache
 import Utility.Url
@@ -121,6 +123,7 @@ data AnnexState = AnnexState
        , fast :: Bool
        , daemon :: Bool
        , branchstate :: BranchState
+       , getvectorclock :: IO VectorClock
        , repoqueue :: Maybe (Git.Queue.Queue Annex)
        , catfilehandles :: CatFileHandles
        , hashobjecthandle :: Maybe HashObjectHandle
@@ -172,6 +175,7 @@ newState c r = do
        sc <- newTMVarIO False
        kh <- Keys.newDbHandle
        tp <- newTransferrerPool
+       vc <- startVectorClock
        return $ AnnexState
                { repo = r
                , repoadjustment = return
@@ -187,6 +191,7 @@ newState c r = do
                , fast = False
                , daemon = False
                , branchstate = startBranchState
+               , getvectorclock = vc
                , repoqueue = Nothing
                , catfilehandles = catFileHandlesNonConcurrent
                , hashobjecthandle = Nothing
index be9b93a9fbd6e15293bb20fd0f2a2145b847e558..2f1adbb589667f6d79436e4ed16f84f07fc09434 100644 (file)
@@ -38,6 +38,7 @@ import Annex.RemoteTrackingBranch
 import Annex.HashObject
 import Annex.Transfer
 import Annex.CheckIgnore
+import Annex.VectorClock
 import Command
 import Backend
 import Types.Key
@@ -342,8 +343,17 @@ importKeys remote importtreeconfig importcontent thirdpartypopulated importablec
                bracket CIDDb.openDb CIDDb.closeDb $ \db -> do
                        CIDDb.needsUpdateFromLog db
                                >>= maybe noop (CIDDb.updateFromLog db)
-                       go False cidmap importing importablecontents db
+                       (run (go False cidmap importing importablecontents db))
   where
+       -- When not importing content, reuse the same vector
+       -- clock for all state that's recorded. This can save
+       -- a little bit of disk space. Individual file downloads
+       -- while downloading take too long for this optimisation
+       -- to be safe to do.
+       run a
+               | importcontent = a
+               | otherwise = reuseVectorClockWhile a
+
        go oldversion cidmap importing (ImportableContents l h) db = do
                largematcher <- largeFilesMatcher
                jobs <- forM l $ \i ->
index 18fc254701e2eab19bfd19be0b0ecf28079e7711..c6f0c7daddae8ed4004c7fd66a447b224aed5cf5 100644 (file)
@@ -3,42 +3,54 @@
  - We don't have a way yet to keep true distributed vector clocks.
  - The next best thing is a timestamp.
  -
- - Copyright 2017-2019 Joey Hess <id@joeyh.name>
+ - Copyright 2017-2020 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
 
-module Annex.VectorClock where
+module Annex.VectorClock (
+       module Annex.VectorClock,
+       module Types.VectorClock,
+) where
 
-import Data.Time.Clock.POSIX
-import Data.ByteString.Builder
-import Control.Applicative
-import Prelude
-
-import Utility.Env
+import Types.VectorClock
+import Annex.Common
+import qualified Annex
 import Utility.TimeStamp
-import Utility.QuickCheck
+
+import Data.ByteString.Builder
 import qualified Data.Attoparsec.ByteString.Lazy as A
 
--- | Some very old logs did not have any time stamp at all;
--- Unknown is used for those.
-data VectorClock = Unknown | VectorClock POSIXTime
-       deriving (Eq, Ord, Show)
+currentVectorClock :: Annex VectorClock
+currentVectorClock = liftIO =<< Annex.getState Annex.getvectorclock
 
--- Unknown is oldest.
-prop_VectorClock_sane :: Bool
-prop_VectorClock_sane = Unknown < VectorClock 1
+-- Runs the action and uses the same vector clock throughout.
+--
+-- When the action modifies several files in the git-annex branch,
+-- this can cause less space to be used, since the same vector clock
+-- value is used, which can compress better.
+--
+-- However, this should not be used when running a long-duration action,
+-- because the vector clock is based on the start of the action, and not on 
+-- the later points where it writes changes. For example, if this were
+-- used across downloads of several files, the location log information
+-- would have an earlier vector clock than necessary, which might cause it
+-- to be disregarded in favor of other information that was collected
+-- at an earlier point in time than when the transfers completted and the
+-- log was written.
+reuseVectorClockWhile :: Annex a -> Annex a
+reuseVectorClockWhile = bracket setup cleanup . const
+  where
+       setup = do
+               origget <- Annex.getState Annex.getvectorclock
+               vc <- liftIO origget
+               use (pure vc)
+               return origget
 
-instance Arbitrary  VectorClock where
-       arbitrary = VectorClock <$> arbitrary
+       cleanup origget = use origget
 
-currentVectorClock :: IO VectorClock
-currentVectorClock = go =<< getEnv "GIT_ANNEX_VECTOR_CLOCK"
-  where
-       go Nothing = VectorClock <$> getPOSIXTime
-       go (Just s) = case parsePOSIXTime s of
-               Just t -> return (VectorClock t)
-               Nothing -> VectorClock <$> getPOSIXTime
+       use vc = Annex.changeState $ \s ->
+               s { Annex.getvectorclock = vc }
 
 formatVectorClock :: VectorClock -> String
 formatVectorClock Unknown = "0"
diff --git a/Annex/VectorClock/Utility.hs b/Annex/VectorClock/Utility.hs
new file mode 100644 (file)
index 0000000..fb401b1
--- /dev/null
@@ -0,0 +1,23 @@
+{- git-annex vector clock utilities
+ -
+ - Copyright 2017-2020 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU AGPL version 3 or higher.
+ -}
+
+module Annex.VectorClock.Utility where
+
+import Data.Time.Clock.POSIX
+
+import Types.VectorClock
+import Utility.Env
+import Utility.TimeStamp
+
+startVectorClock :: IO (IO VectorClock)
+startVectorClock = go =<< getEnv "GIT_ANNEX_VECTOR_CLOCK"
+  where
+       go Nothing = timebased
+       go (Just s) = case parsePOSIXTime s of
+               Just t -> return (pure (VectorClock t))
+               Nothing -> timebased
+       timebased = return (VectorClock <$> getPOSIXTime)
index 85acb60e2a80aac6a19ada80bb0650a9782ec103..75083e4db7a0e08eb07a6e50cbea7e0f3eb04324 100644 (file)
@@ -35,7 +35,7 @@ seek = commandAction . start
 
 start :: ForgetOptions -> CommandStart
 start o = starting "forget" ai si $ do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        let basets = addTransition c ForgetGitHistory noTransitions
        let ts = if dropDead o
                then addTransition c ForgetDeadRemotes basets
index 8fd980d3457dc873127ddea0b54ea80ee21f3a6e..12d89c76586f8d0338b1d0cd5ce0b0c97715d072 100644 (file)
@@ -74,7 +74,7 @@ optParser desc = MetaDataOptions
 seek :: MetaDataOptions -> CommandSeek
 seek o = case batchOption o of
        NoBatch -> do
-               c <- liftIO currentVectorClock
+               c <- currentVectorClock
                let ww = WarnUnmatchLsFiles
                let seeker = AnnexedFileSeeker
                        { startAction = start c o
@@ -188,7 +188,7 @@ startBatch (si, (i, (MetaData m))) = case i of
                        , keyOptions = Nothing
                        , batchOption = NoBatch
                        }
-               t <- liftIO currentVectorClock
+               t <- currentVectorClock
                -- It would be bad if two batch mode changes used exactly
                -- the same timestamp, since the order of adds and removals
                -- of the same metadata value would then be indeterminate.
index 5a40e7d125b286d86b45c7935f1660c011a666d1..0424df6b8959ce31e6b6560c23c3f250e3702f2a 100644 (file)
@@ -27,7 +27,7 @@ data Activity
 
 recordActivity :: Activity -> UUID -> Annex ()
 recordActivity act uuid = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change activityLog $
                buildLogOld buildActivity
                        . changeLog c uuid (Right act)
index b8bf15f1d713cca2318906e3788252c1686739f3..a69d1bead09ad30a072e09a37c7c9a5088a3a114 100644 (file)
@@ -35,7 +35,7 @@ import qualified Data.Map as M
 
 chunksStored :: UUID -> Key -> ChunkMethod -> ChunkCount -> Annex ()
 chunksStored u k chunkmethod chunkcount = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        config <- Annex.getGitConfig
        Annex.Branch.change (chunkLogFile config k) $
                buildLog . changeMapLog c (u, chunkmethod) chunkcount . parseLog
index f9e31561e3d2f5434e86b7a39c051fd151e8a2a7..9d0f732de3208815ffa36001747a55b0b200cdee 100644 (file)
@@ -34,7 +34,7 @@ setGlobalConfig name new = do
 
 setGlobalConfig' :: ConfigKey -> ConfigValue -> Annex ()
 setGlobalConfig' name new = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change configLog $ 
                buildGlobalConfig . changeMapLog c name new . parseGlobalConfig
 
index 16c3969155e5c6114101d75071f037d396addde4..c456ed5f0e0f9fcda6e035f915124a9702b7ed4e 100644 (file)
@@ -30,7 +30,7 @@ import qualified Data.List.NonEmpty as NonEmpty
 -- so ones that were recorded before are preserved.
 recordContentIdentifier :: RemoteStateHandle -> ContentIdentifier -> Key -> Annex ()
 recordContentIdentifier (RemoteStateHandle u) cid k = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        config <- Annex.getGitConfig
        Annex.Branch.maybeChange (remoteContentIdentifierLogFile config k) $
                addcid c . parseLog
index c134235306c839ac115031c8da433294237ad30e..69b34b474d5645aeb5cae1b07098395a4c8df57a 100644 (file)
@@ -25,7 +25,7 @@ import Logs.Difference.Pure
 
 recordDifferences :: Differences -> UUID -> Annex ()
 recordDifferences ds@(Differences {}) uuid = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change differenceLog $
                buildLogOld byteString 
                        . changeLog c uuid (encodeBS $ showDifferences ds) 
index aab1f0445c6928b42e0e10c36f59c1bd614ddfcb..0358a7056c419adc3c92cfd2c263f42828fa5871 100644 (file)
@@ -92,7 +92,7 @@ getExport remoteuuid = nub . mapMaybe get . M.toList . simpleMap
 -- This is called before any changes are made to the remote.
 recordExportBeginning :: UUID -> Git.Ref -> Annex ()
 recordExportBeginning remoteuuid newtree = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        u <- getUUID
        let ep = ExportParticipants { exportFrom = u, exportTo = remoteuuid }
        old <- fromMaybe (Exported emptyTree [])
@@ -127,7 +127,7 @@ recordExportTreeish t =
 -- forward in lock-step.
 recordExportUnderway :: UUID -> ExportChange -> Annex ()
 recordExportUnderway remoteuuid ec = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        u <- getUUID
        let ep = ExportParticipants { exportFrom = u, exportTo = remoteuuid }
        let exported = Exported (newTreeish ec) []
index 03c59d18cef9c7d948005850be410e10301ebb69..ce67abb2797ee9b9ec1a26e13a0a3ba55bed778c 100644 (file)
@@ -38,7 +38,7 @@ lookupGroups u = (fromMaybe S.empty . M.lookup u) . groupsByUUID <$> groupMap
 groupChange :: UUID -> (S.Set Group -> S.Set Group) -> Annex ()
 groupChange uuid@(UUID _) modifier = do
        curr <- lookupGroups uuid
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change groupLog $
                buildLogOld buildGroup . changeLog c uuid (modifier curr) . parseLogOld parseGroup
        
index 6206ec2218648370ce1daa3177ba196cbec9beb6..e6e1529a92a3ff5aee93c438fb732a4de4ceb878 100644 (file)
@@ -103,7 +103,7 @@ addMetaData = addMetaData' metaDataLogFile
 
 addMetaData' :: (GitConfig -> Key -> RawFilePath) -> Key -> MetaData -> Annex ()
 addMetaData' getlogfile k metadata = 
-       addMetaDataClocked' getlogfile k metadata =<< liftIO currentVectorClock
+       addMetaDataClocked' getlogfile k metadata =<< currentVectorClock
 
 {- Reusing the same VectorClock when making changes to the metadata
  - of multiple keys is a nice optimisation. The same metadata lines
index f6504d43b1f7768e05f2d11d3bd47260c58a1bf4..ab852deeb9c455d65c86dc69458727fa32b9af7d 100644 (file)
@@ -25,7 +25,7 @@ newtype Fingerprint = Fingerprint String
 
 recordFingerprint :: Fingerprint -> UUID -> Annex ()
 recordFingerprint fp uuid = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change multicastLog $
                buildLogOld buildFindgerPrint
                        . changeLog c uuid fp
index fb95b8c26489f3ddf1912fd33a2e15830a66ad59..80dcf1f30e618f435341282c5023bdfe5fd7e302 100644 (file)
@@ -30,7 +30,7 @@ requiredContentSet = setLog requiredContentLog
 
 setLog :: RawFilePath -> UUID -> PreferredContentExpression -> Annex ()
 setLog logfile uuid@(UUID _) val = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change logfile $
                buildLogOld buildPreferredContentExpression
                . changeLog c uuid val
@@ -44,7 +44,7 @@ setLog _ NoUUID _ = error "unknown UUID; cannot modify"
 {- Changes the preferred content configuration of a group. -}
 groupPreferredContentSet :: Group -> PreferredContentExpression -> Annex ()
 groupPreferredContentSet g val = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change groupPreferredContentLog $
                buildGroupPreferredContent
                . changeMapLog c g val 
index 486af7ee13a592189077a12a4b7eff24cabf0001..2e21a99435fff106fd4d2e110df4b39178975ccc 100644 (file)
@@ -51,7 +51,7 @@ readLog = parseLog <$$> Annex.Branch.get
 {- Generates a new LogLine with the current time. -}
 logNow :: LogStatus -> LogInfo -> Annex LogLine
 logNow s i = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        return $ LogLine c s i
 
 {- Reads a log and returns only the info that is still in effect. -}
index 55e00bbcc5f1b4bc09833a743729f0b3bcb8660a..c1468b81bc2759c63587668cc4f23e62f340cefe 100644 (file)
@@ -32,7 +32,7 @@ import qualified Data.Map as M
 {- Adds or updates a remote's config in the log. -}
 configSet :: UUID -> RemoteConfig -> Annex ()
 configSet u cfg = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change remoteLog $
                buildRemoteConfigLog
                        . changeLog c u (removeSameasInherited cfg)
index 2c1c742ce79d4276fdf0e64b16c037262501dc51..2ced3b1487d6b1c2e129baad4d30063f51b757f1 100644 (file)
@@ -26,7 +26,7 @@ type RemoteState = String
 
 setRemoteState :: RemoteStateHandle -> Key -> RemoteState -> Annex ()
 setRemoteState (RemoteStateHandle u) k s = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        config <- Annex.getGitConfig
        Annex.Branch.change (remoteStateLogFile config k) $
                buildRemoteState . changeLog c u s . parseRemoteState
index 06d0a2141a4ff2d746a925746b783efb11e8efd8..fa7c6ee0d959efe59ecd2c9edf5442b4473a305f 100644 (file)
@@ -32,7 +32,7 @@ import Logs.File
 
 scheduleSet :: UUID -> [ScheduledActivity] -> Annex ()
 scheduleSet uuid@(UUID _) activities = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change scheduleLog $
                buildLogOld byteString 
                        . changeLog c uuid (encodeBS val)
index 8edbd507861d468c0c43b8d82f21d5e4bbd733bb..0652c9e5e2bada50eae65d54a3ed4e1a09be3e3f 100644 (file)
@@ -33,6 +33,6 @@ getLog = newestValue <$$> readLog
 
 setLog :: (SingleValueSerializable v) => RawFilePath -> v -> Annex ()
 setLog f v = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        let ent = LogEntry c v
        Annex.Branch.change f $ \_old -> buildLog (S.singleton ent)
index 929c6b552a5ea80b38fff5a8197c237d4741c7d4..84d1a58183f939be9e97182850a7d238d3e4c196 100644 (file)
@@ -22,7 +22,7 @@ import Logs.Trust.Pure as X
 {- Changes the trust level for a uuid in the trustLog. -}
 trustSet :: UUID -> TrustLevel -> Annex ()
 trustSet uuid@(UUID _) level = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change trustLog $
                buildLogOld buildTrustLevel .
                        changeLog c uuid level .
index 7dd1213517b557d139753f6ee0c8d3e26abd22b9..1abe0625148c4322eef55313a28e1a185118f9da 100644 (file)
@@ -31,7 +31,7 @@ import qualified Data.Attoparsec.ByteString.Lazy as A
 {- Records a description for a uuid in the log. -}
 describeUUID :: UUID -> UUIDDesc -> Annex ()
 describeUUID uuid desc = do
-       c <- liftIO currentVectorClock
+       c <- currentVectorClock
        Annex.Branch.change uuidLog $
                buildLogOld buildUUIDDesc . changeLog c uuid desc . parseUUIDLog
 
diff --git a/Types/VectorClock.hs b/Types/VectorClock.hs
new file mode 100644 (file)
index 0000000..160734f
--- /dev/null
@@ -0,0 +1,29 @@
+{- git-annex vector clocks
+ -
+ - We don't have a way yet to keep true distributed vector clocks.
+ - The next best thing is a timestamp.
+ -
+ - Copyright 2017-2020 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU AGPL version 3 or higher.
+ -}
+
+module Types.VectorClock where
+
+import Data.Time.Clock.POSIX
+import Control.Applicative
+import Prelude
+
+import Utility.QuickCheck
+
+-- | Some very old logs did not have any time stamp at all;
+-- Unknown is used for those.
+data VectorClock = Unknown | VectorClock POSIXTime
+       deriving (Eq, Ord, Show)
+
+-- Unknown is oldest.
+prop_VectorClock_sane :: Bool
+prop_VectorClock_sane = Unknown < VectorClock 1
+
+instance Arbitrary  VectorClock where
+       arbitrary = VectorClock <$> arbitrary
index 8afed3e82a548f8747367301981c9784f75b924b..0200e40a0d24ef0108a0c5f6aa87afd20aed3322 100644 (file)
@@ -11,3 +11,6 @@ As a bonus, since borg uses the same content identifiers for all keys
 (""), implementing this would make the content of the logs all identical,
 and so avoid any overhead entirely. Well.. perhaps that's my actual
 motivation. ;-) --[[Joey]]
+
+> [[done]], though reuseVectorClockWhile could be used in other places
+> perhaps. --[[Joey]]
index 91bf59d44de98ffa852ae95ea368e3683730f750..b9f0502c6d3e86025ff06d33caa8b9a184efe1ff 100644 (file)
@@ -670,6 +670,7 @@ Executable git-annex
     Annex.UUID
     Annex.Url
     Annex.VectorClock
+    Annex.VectorClock.Utility
     Annex.VariantFile
     Annex.Version
     Annex.View
@@ -1035,6 +1036,7 @@ Executable git-annex
     Types.TrustLevel
     Types.UUID
     Types.UrlContents
+    Types.VectorClock
     Types.View
     Types.WorkerPool
     Upgrade