Skip to content

Commit 81d9e4d

Browse files
committed
refactor: add internal-schema-cache-lock-id
1 parent 1a6ba20 commit 81d9e4d

4 files changed

Lines changed: 50 additions & 36 deletions

File tree

src/PostgREST/Config.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ data AppConfig = AppConfig
127127
, configRoleSettings :: RoleSettings
128128
, configRoleIsoLvl :: RoleIsolationLvl
129129
, configInternalSCQuerySleep :: Maybe Int32
130+
, configInternalSCLockId :: Maybe Int32
130131
}
131132

132133
data LogLevel = LogCrit | LogError | LogWarn | LogInfo | LogDebug
@@ -326,6 +327,7 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
326327
<*> pure roleSettings
327328
<*> pure roleIsolationLvl
328329
<*> optInt "internal-schema-cache-query-sleep"
330+
<*> optInt "internal-schema-cache-lock-id"
329331
where
330332
parseErrorVerbosity :: C.Key -> C.Parser C.Config Verbosity
331333
parseErrorVerbosity k =

src/PostgREST/SchemaCache.hs

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -157,46 +157,56 @@ maxDbTablesForFuzzySearch = 500
157157
querySchemaCache :: AppConfig -> SQL.Transaction SchemaCache
158158
querySchemaCache conf@AppConfig{..} = do
159159
SQL.sql "set local schema ''" -- This voids the search path. The following queries need this for getting the fully qualified name(schema.name) of every db object
160-
tabs <- sqlTimedStmt gucTbls conf allTables
161-
keyDeps <- sqlTimedStmt gucKDeps conf allViewsKeyDependencies
162-
m2oRels <- sqlTimedStmt gucRels mempty allM2OandO2ORels
163-
funcs <- sqlTimedStmt gucFuncs conf allFunctions
164-
cRels <- sqlTimedStmt gucCRels mempty allComputedRels
165-
reps <- sqlTimedStmt gucDReps conf dataRepresentations
166-
mHdlers <- sqlTimedStmt gucMHdrs conf mediaHandlers
167-
tzones <- if configDbTimezoneEnabled
168-
then sqlTimedStmt gucTzones mempty timezones
169-
else pure S.empty
170-
_ <-
171-
let sleepCall = SQL.Statement "select pg_sleep($1 / 1000.0)" (param HE.int4) HD.noResult True in
172-
for_ configInternalSCQuerySleep (`SQL.statement` sleepCall) -- only used for testing
173-
174-
qsTime <-
175-
if isLogDebug
176-
then Just <$> SQL.statement mempty (extractTimings configDbTimezoneEnabled)
177-
else pure Nothing
178-
179-
let tabsWViewsPks = addViewPrimaryKeys tabs keyDeps
180-
rels = addInverseRels $ addM2MRels tabsWViewsPks $ addViewM2OAndO2ORels keyDeps m2oRels
181-
182-
return $ removeInternal schemas $ SchemaCache {
183-
dbTables = tabsWViewsPks
184-
, dbRelationships = getOverrideRelationshipsMap rels cRels
185-
, dbRoutines = funcs
186-
, dbRepresentations = reps
187-
, dbMediaHandlers = HM.union mHdlers initialMediaHandlers -- the custom handlers will override the initial ones
188-
, dbTimezones = tzones
189-
190-
, dbTablesFuzzyIndex =
191-
-- Only build fuzzy index for schemas with a reasonable number of tables
192-
-- Fuzzy.FuzzySet is memory heavy we just don't use it for large schemas
193-
Fuzzy.fromList <$> HM.filter ((< maxDbTablesForFuzzySearch) . length) (HM.fromListWith (<>) ((qiSchema &&& pure . qiName) <$> HM.keys tabsWViewsPks))
194-
, dbQueryTimings = qsTime
195-
}
160+
(`evalStateT` 0) $ do
161+
tabs <- sqlWithLock $ sqlTimedStmt gucTbls conf allTables
162+
keyDeps <- sqlWithLock $ sqlTimedStmt gucKDeps conf allViewsKeyDependencies
163+
m2oRels <- sqlWithLock $ sqlTimedStmt gucRels mempty allM2OandO2ORels
164+
funcs <- sqlWithLock $ sqlTimedStmt gucFuncs conf allFunctions
165+
cRels <- sqlWithLock $ sqlTimedStmt gucCRels mempty allComputedRels
166+
reps <- sqlWithLock $ sqlTimedStmt gucDReps conf dataRepresentations
167+
mHdlers <- sqlWithLock $ sqlTimedStmt gucMHdrs conf mediaHandlers
168+
tzones <- if configDbTimezoneEnabled
169+
then sqlWithLock $ sqlTimedStmt gucTzones mempty timezones
170+
else pure S.empty
171+
_ <-
172+
let sleepCall = SQL.Statement "select pg_sleep($1 / 1000.0)" (param HE.int4) HD.noResult True in
173+
lift $ for_ configInternalSCQuerySleep (`SQL.statement` sleepCall) -- only used for testing
174+
175+
qsTime <-
176+
if isLogDebug
177+
then Just <$> sqlWithLock (SQL.statement mempty (extractTimings configDbTimezoneEnabled))
178+
else pure Nothing
179+
180+
let tabsWViewsPks = addViewPrimaryKeys tabs keyDeps
181+
rels = addInverseRels $ addM2MRels tabsWViewsPks $ addViewM2OAndO2ORels keyDeps m2oRels
182+
183+
return $ removeInternal schemas $ SchemaCache {
184+
dbTables = tabsWViewsPks
185+
, dbRelationships = getOverrideRelationshipsMap rels cRels
186+
, dbRoutines = funcs
187+
, dbRepresentations = reps
188+
, dbMediaHandlers = HM.union mHdlers initialMediaHandlers -- the custom handlers will override the initial ones
189+
, dbTimezones = tzones
190+
191+
, dbTablesFuzzyIndex =
192+
-- Only build fuzzy index for schemas with a reasonable number of tables
193+
-- Fuzzy.FuzzySet is memory heavy we just don't use it for large schemas
194+
Fuzzy.fromList <$> HM.filter ((< maxDbTablesForFuzzySearch) . length) (HM.fromListWith (<>) ((qiSchema &&& pure . qiName) <$> HM.keys tabsWViewsPks))
195+
, dbQueryTimings = qsTime
196+
}
196197
where
197198
schemas = toList configDbSchemas
198199
isLogDebug = configLogLevel == LogDebug
199200
sqlTimedStmt = sqlTimedStatement isLogDebug
201+
sqlWithLock stmt = maybe (lift stmt) (lockNext stmt) configInternalSCLockId
202+
lockNext stmt lockId = do
203+
nextLock <- get
204+
put $ succ nextLock
205+
lift $ do
206+
SQL.statement (lockId, nextLock) $ SQL.Statement "SELECT pg_advisory_xact_lock($1, $2)" ((fst >$< param HE.int4) <> (snd >$< param HE.int2)) HD.noResult False
207+
stmt
208+
209+
200210

201211
-- | overrides detected relationships with the computed relationships and gets the RelationshipsMap
202212
getOverrideRelationshipsMap :: [Relationship] -> [Relationship] -> RelationshipsMap

test/io/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def slow_schema_cache_env(defaultenv):
6161
"Slow schema cache load environment PostgREST."
6262
return {
6363
**defaultenv,
64+
"PGRST_INTERNAL_SCHEMA_CACHE_LOCK_ID": "1111",
6465
"PGRST_INTERNAL_SCHEMA_CACHE_QUERY_SLEEP": "1000", # this does a pg_sleep internally, it will cause the schema cache query to be slow
6566
# the slow schema cache query will keep using one pool connection until it finishes
6667
# to prevent requests waiting for PGRST_DB_POOL_ACQUISITION_TIMEOUT we'll increase the pool size (must be >= 2)

test/io/test_io.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,7 @@ def test_schema_cache_concurrent_notifications(slow_schema_cache_env):
13941394
internal_sleep = (
13951395
int(slow_schema_cache_env["PGRST_INTERNAL_SCHEMA_CACHE_QUERY_SLEEP"]) / 1000
13961396
)
1397+
# TODO change to usage of PGRST_INTERNAL_SCHEMA_CACHE_LOCK_ID
13971398

13981399
with run(env=slow_schema_cache_env, wait_for=None) as postgrest:
13991400
time.sleep(2 * internal_sleep + 0.1) # wait for readiness manually

0 commit comments

Comments
 (0)