Skip to content

Commit 0dfd773

Browse files
committed
[lmdb facebookincubator#3] Add LMDB storage backend
1 parent a35cc98 commit 0dfd773

23 files changed

Lines changed: 15758 additions & 13 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ jobs:
169169
run: echo CABAL_CONFIG_FLAGS="$CABAL_CONFIG_FLAGS --index-state=${{ matrix.index-state }}" >>"$GITHUB_ENV"
170170

171171
- name: Install hsthrift and Glean dependencies
172-
run: apt-get install -y pkg-config rsync libgmock-dev libpcre3-dev libtinfo-dev libxxhash-dev
172+
run: apt-get install -y pkg-config rsync libgmock-dev libpcre3-dev libtinfo-dev libxxhash-dev lz4
173173

174174
- name: Build hsthrift and Glean
175175
run: make

glean.cabal.in

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,22 @@ library rocksdb
415415
glean:storage,
416416
glean:rts,
417417

418+
library lmdb
419+
import: fb-haskell, fb-cpp, deps, folly
420+
visibility: private
421+
CXX_LIB_glean_cpp_lmdb
422+
include-dirs: .
423+
install-includes:
424+
glean/lmdb/container-impl.h
425+
glean/lmdb/database-impl.h
426+
glean/lmdb/ffi.h
427+
glean/lmdb/glean_lmdb.h
428+
glean/lmdb/util.h
429+
pkgconfig-depends: fmt
430+
build-depends:
431+
glean:storage,
432+
glean:rts,
433+
418434
library util
419435
import: fb-haskell, fb-cpp, deps, thrift-client
420436
visibility: public
@@ -599,6 +615,7 @@ library db
599615
Glean.Database.Storage.Memory
600616
Glean.Database.Storage.DB
601617
Glean.Database.Storage.RocksDB
618+
Glean.Database.Storage.LMDB
602619
Glean.Database.Trace
603620
Glean.Database.Types
604621
Glean.Database.Validate
@@ -653,6 +670,7 @@ library db
653670
glean:rts,
654671
glean:stubs,
655672
glean:rocksdb,
673+
glean:lmdb,
656674

657675
-- Backend API, and a few things built on top
658676
library backend-api

glean/db/Glean/Database/Config.hs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module Glean.Database.Config (
1515
tmpDataStore,
1616
memoryDataStore,
1717
rocksdbName,
18+
lmdbName,
1819

1920
-- * Config, and options parser
2021
options,
@@ -84,6 +85,7 @@ import Glean.Database.Schema.ComputeIds
8485
import Glean.Database.Storage
8586
import qualified Glean.Database.Storage.Memory as Memory
8687
import qualified Glean.Database.Storage.RocksDB as RocksDB
88+
import qualified Glean.Database.Storage.LMDB as LMDB
8789
import Glean.Database.Trace
8890
import qualified Glean.Internal.Types as Internal
8991
import Glean.Internal.Types (StorageName(..))
@@ -118,17 +120,20 @@ data DataStore = DataStore
118120
, dataStoreTag :: String
119121
}
120122

121-
rocksdbName, memoryName :: StorageName
123+
rocksdbName, lmdbName, memoryName :: StorageName
122124
rocksdbName = StorageName "rocksdb"
125+
lmdbName = StorageName "lmdb"
123126
memoryName = StorageName "memory"
124127

125128
fileDataStore :: FilePath -> DataStore
126129
fileDataStore path = DataStore
127130
{ withStorage = \scfg f -> do
128131
rocksdb <- RocksDB.newStorage path scfg
132+
lmdb <- LMDB.newStorage path scfg
129133
f (
130134
HashMap.fromList
131-
[ (rocksdbName, Some rocksdb)
135+
[ (rocksdbName, Some rocksdb),
136+
(lmdbName, Some lmdb)
132137
],
133138
Some (Catalog.fileCatalog path)
134139
)
@@ -511,7 +516,10 @@ options = do
511516
long "db-root" <>
512517
metavar "DIR" <>
513518
help "Directory containing databases")
514-
pure $ fileDataStore path
519+
lmdb <- switch (long "lmdb")
520+
pure $
521+
(if lmdb then \s -> s { defaultStorage = lmdbName } else id) $
522+
fileDataStore path
515523
dbTmp = tmpDataStore <$ flag' () (
516524
long "db-tmp" <>
517525
help "Store databases in a temporary directory (default)")
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
{-
2+
Copyright (c) Meta Platforms, Inc. and affiliates.
3+
All rights reserved.
4+
5+
This source code is licensed under the BSD-style license found in the
6+
LICENSE file in the root directory of this source tree.
7+
-}
8+
9+
module Glean.Database.Storage.LMDB
10+
( LMDB(..)
11+
, newStorage
12+
) where
13+
14+
import Control.Exception
15+
import Data.Int
16+
import Foreign.C.String
17+
import Foreign.C.Types
18+
import Foreign.ForeignPtr
19+
import Foreign.Ptr
20+
import System.Directory
21+
import System.IO.Temp (withTempDirectory)
22+
import System.FilePath
23+
import System.Process (callProcess)
24+
25+
import Util.FFI
26+
import Util.IO (safeRemovePathForcibly)
27+
28+
import Glean.Database.Backup.Backend (Data(Data))
29+
import Glean.Database.Repo (databasePath)
30+
import Glean.Database.Storage
31+
import Glean.Database.Storage.DB
32+
import Glean.FFI
33+
import Glean.RTS.Foreign.Lookup (CanLookup(..))
34+
import Glean.RTS.Foreign.Ownership as Ownership
35+
hiding (computeDerivedOwnership)
36+
import Glean.RTS.Types (Fid(..), invalidFid)
37+
import qualified Glean.ServerConfig.Types as ServerConfig
38+
import Glean.Types (Repo)
39+
import Glean.Util.Disk
40+
import Glean.Impl.MemoryReader
41+
import System.IO.Extra (withTempFile)
42+
43+
data LMDB = LMDB
44+
{ lmdbRoot :: FilePath
45+
, lmdbMaxDiskSize :: Maybe Int
46+
-- ^ virtual limit to report capped disk capacities. The limit is
47+
-- not enforced. It's up to each io usage to check diskspace before writing.
48+
-- We're using this to avoid serving too many dbs on query servers,
49+
-- and smarter sharding.
50+
}
51+
52+
newStorage :: FilePath -> ServerConfig.Config -> IO LMDB
53+
newStorage root ServerConfig.Config{..} = do
54+
mem_capacity <- totalMemCapacityKB
55+
return LMDB
56+
{ lmdbRoot = root
57+
, lmdbMaxDiskSize = case mem_capacity of
58+
Just mem -> (* (mem * 1024)) . fromIntegral <$>
59+
config_db_rocksdb_disk_mem_capacity_ratio_limit
60+
Nothing -> Nothing
61+
}
62+
63+
newtype instance Database LMDB = Database DB
64+
deriving (CanLookup)
65+
66+
instance Storage LMDB where
67+
describe db = "lmdb:" <> lmdbRoot db
68+
69+
open lmdb repo mode (DBVersion version) = do
70+
(cmode, start, ownership) <- case mode of
71+
ReadOnly -> return (0, invalidFid, Nothing)
72+
ReadWrite -> return (1, invalidFid, Nothing)
73+
Create start ownership _ -> do
74+
createDirectoryIfMissing True path
75+
return (2, start, ownership)
76+
withCString path $ \cpath ->
77+
using (invoke $ glean_lmdb_container_open cpath cmode)
78+
$ \container -> do
79+
fp <- mask_ $ do
80+
first_unit_id <- maybe (return firstUsetId) nextUsetId ownership
81+
p <- invoke $
82+
glean_lmdb_container_open_database container start
83+
first_unit_id version
84+
newForeignPtr glean_rocksdb_database_free p
85+
return (Database (DB (castForeignPtr fp) repo))
86+
where
87+
path = containerPath lmdb repo
88+
89+
delete lmdb = safeRemovePathForcibly . containerPath lmdb
90+
91+
safeRemoveForcibly lmdb =
92+
safeRemovePathForcibly . databasePath (lmdbRoot lmdb)
93+
94+
getTotalCapacity lmdb = do
95+
exists <- doesDirectoryExist (lmdbRoot lmdb)
96+
if exists
97+
then do
98+
fullDiskCapacity <- getDiskSize (lmdbRoot lmdb)
99+
return $ Just $ case lmdbMaxDiskSize lmdb of
100+
Just maxDiskSize -> min maxDiskSize fullDiskCapacity
101+
Nothing -> fullDiskCapacity
102+
else return Nothing
103+
104+
getUsedCapacity lmdb = do
105+
exists <- doesDirectoryExist (lmdbRoot lmdb)
106+
if exists
107+
then Just <$> getUsedDiskSpace (lmdbRoot lmdb)
108+
else return Nothing
109+
110+
getFreeCapacity lmdb = do
111+
used <- getUsedCapacity lmdb
112+
total <- getTotalCapacity lmdb
113+
case (used,total) of
114+
(Just used, Just total) -> return $ total - used
115+
_ -> getFreeDiskSpace (lmdbRoot lmdb) -- not aware of disk limit
116+
117+
withScratchRoot rocks f = f $ lmdbRoot rocks </> ".scratch"
118+
119+
restore lmdb repo scratch scratch_file =
120+
withTempDirectory scratch "restore" $ \scratch_restore -> do
121+
let scratch_restore_backup = scratch_restore </> "backup"
122+
callProcess "lz4" ["-qd", scratch_file, scratch_restore_backup ]
123+
-- to avoid retaining an extra copy of the DB during restore,
124+
-- delete the input file now.
125+
removeFile scratch_file
126+
127+
let db = scratch </> "db"
128+
createDirectoryIfMissing True db
129+
withCString db $ \p_target ->
130+
withCString scratch_restore_backup $ \p_source ->
131+
invoke $ glean_lmdb_restore p_target p_source
132+
133+
let target = containerPath lmdb repo
134+
createDirectoryIfMissing True $ takeDirectory target
135+
renameDirectory db target
136+
137+
138+
containerPath :: LMDB -> Repo -> FilePath
139+
containerPath LMDB{..} repo = databasePath lmdbRoot repo </> "db"
140+
141+
instance DatabaseOps (Database LMDB) where
142+
close (Database db) = close db
143+
predicateStats (Database db) = predicateStats db
144+
store (Database db) = store db
145+
retrieve (Database db) = retrieve db
146+
commit (Database db) = commit db
147+
addOwnership (Database db) = addOwnership db
148+
optimize (Database db) = optimize db
149+
computeOwnership (Database db) = computeOwnership db
150+
storeOwnership (Database db) = storeOwnership db
151+
getOwnership (Database db) = getOwnership db
152+
getUnitId (Database db) = getUnitId db
153+
getUnit (Database db) = getUnit db
154+
addDefineOwnership (Database db) = addDefineOwnership db
155+
computeDerivedOwnership (Database db) = computeDerivedOwnership db
156+
cacheOwnership (Database db) = cacheOwnership db
157+
prepareFactOwnerCache (Database db) = prepareFactOwnerCache db
158+
159+
backup (Database db) scratch process = do
160+
backup db scratch $ \path _ ->
161+
withTempFile $ \zFile -> do
162+
callProcess "lz4" ["-qf", path, zFile]
163+
size <- getFileSize zFile
164+
process zFile (Data $ fromIntegral size)
165+
166+
foreign import ccall safe glean_lmdb_container_open
167+
:: CString
168+
-> CInt
169+
-> Ptr Container
170+
-> IO CString
171+
172+
foreign import ccall safe glean_lmdb_container_open_database
173+
:: Container
174+
-> Fid
175+
-> UsetId
176+
-> Int64
177+
-> Ptr (Ptr (Database LMDB))
178+
-> IO CString
179+
foreign import ccall safe "&glean_rocksdb_database_free"
180+
glean_rocksdb_database_free :: Destroy (Database LMDB)
181+
182+
foreign import ccall safe glean_lmdb_restore
183+
:: CString
184+
-> CString
185+
-> IO CString

0 commit comments

Comments
 (0)