Skip to content

Commit 92cf79e

Browse files
committed
fix: Limit concurrent schema cache loads
Triggering schema cache reload immediately upon receival of notification by the listener leads to thundering herd problem in PostgREST cluster. This change adds limiting of number of concurrent schema cache loading queries using advisory locks.
1 parent 99984d3 commit 92cf79e

2 files changed

Lines changed: 19 additions & 5 deletions

File tree

postgrest.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ library
158158
, stm-hamt >= 1.2 && < 2
159159
, focus >= 1.0 && < 2
160160
, some >= 1.0.4.1 && < 2
161+
, MonadRandom >= 0.6.2 && < 0.7
161162
-- -fno-spec-constr may help keep compile time memory use in check,
162163
-- see https://gitlab.haskell.org/ghc/ghc/issues/16017#note_219304
163164
-- -optP-Wno-nonportable-include-path

src/PostgREST/AppState.hs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ import Data.Either.Combinators (whenLeft)
3535
import qualified Data.Text as T (unpack)
3636
import qualified Hasql.Pool as SQL
3737
import qualified Hasql.Pool.Config as SQL
38-
import qualified Hasql.Session as SQL
38+
import qualified Hasql.Session as SQL hiding (statement)
39+
import qualified Hasql.Transaction as SQL hiding (sql)
3940
import qualified Hasql.Transaction.Sessions as SQL
4041
import qualified Network.HTTP.Types.Status as HTTP
4142
import qualified Network.Socket as NS
@@ -72,9 +73,14 @@ import PostgREST.SchemaCache (SchemaCache (..),
7273
import PostgREST.SchemaCache.Identifiers (quoteQi)
7374
import PostgREST.Unix (createAndBindDomainSocket)
7475

75-
import Data.Streaming.Network (bindPortTCP, bindRandomPortTCP)
76-
import Data.String (IsString (..))
77-
import Protolude
76+
import Control.Monad.Random (MonadRandom (getRandomR))
77+
import Data.Streaming.Network (bindPortTCP,
78+
bindRandomPortTCP)
79+
import Data.String (IsString (..))
80+
import qualified Hasql.Decoders as HD
81+
import qualified Hasql.Encoders as HE
82+
import qualified Hasql.Statement as SQL
83+
import Protolude
7884

7985
data AppState = AppState
8086
-- | Database connection pool
@@ -401,9 +407,16 @@ retryingSchemaCacheLoad appState@AppState{stateObserver=observer, stateMainThrea
401407
qSchemaCache :: IO (Maybe SchemaCache)
402408
qSchemaCache = do
403409
conf@AppConfig{..} <- getConfig appState
410+
withTxLock <- do
411+
-- Allow 10 concurrent schema cache loads, guarded by advisory locks.
412+
-- This is to prevent thundering herd problem on startup or when many PostgREST instances receive "reload schema" notifications at the same time
413+
lockId <- getRandomR (50168275::Int64, 50168275 + 10)
414+
let stmt = SQL.Statement "SELECT pg_catalog.pg_try_advisory_lock($1)" (HE.param $ HE.nonNullable HE.int8) HD.noResult configDbPreparedStatements
415+
pure $ SQL.statement lockId stmt
416+
404417
(resultTime, result) <-
405418
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in
406-
timeItT $ usePool appState (transaction SQL.ReadCommitted SQL.Read $ querySchemaCache conf)
419+
timeItT $ usePool appState (transaction SQL.ReadCommitted SQL.Read $ withTxLock *> querySchemaCache conf)
407420
case result of
408421
Left e -> do
409422
putSCacheStatus appState SCPending

0 commit comments

Comments
 (0)