feat(db): optionally disable some db connection pooling

This commit is contained in:
Gregor Kleen 2021-02-21 20:44:45 +01:00
parent e4f10ec1f3
commit 35ac503bf9
7 changed files with 60 additions and 48 deletions

View File

@ -18,7 +18,7 @@ module Application
import Control.Monad.Logger (liftLoc, LoggingT(..), MonadLoggerIO(..)) import Control.Monad.Logger (liftLoc, LoggingT(..), MonadLoggerIO(..))
import Database.Persist.Postgresql (createPostgresqlPool, pgConnStr, import Database.Persist.Postgresql (createPostgresqlPool, pgConnStr,
pgPoolSize, runSqlPool, ConnectionPool) pgPoolSize, runSqlPool, ConnectionPool, runSqlConn, withPostgresqlConn)
import Import hiding (cancel, respond) import Import hiding (cancel, respond)
import Language.Haskell.TH.Syntax (qLocation) import Language.Haskell.TH.Syntax (qLocation)
import Network.Wai (Middleware) import Network.Wai (Middleware)
@ -202,25 +202,28 @@ makeFoundation appSettings''@AppSettings{..} = do
-- logging function. To get out of this loop, we initially create a -- logging function. To get out of this loop, we initially create a
-- temporary foundation without a real connection pool, get a log function -- temporary foundation without a real connection pool, get a log function
-- from there, and then create the real foundation. -- from there, and then create the real foundation.
let mkFoundation appSettings' appConnPool appSmtpPool appLdapPool appCryptoIDKey appSessionStore appSecretBoxKey appWidgetMemcached appJSONWebKeySet appClusterID appMemcached appUploadCache appVerpSecret appAuthKey = UniWorX {..} let
-- The UniWorX {..} syntax is an example of record wild cards. For more mkFoundation :: _ -> _ -> (forall backend m a. (MonadUnliftIO m, BackendCompatible backend SqlBackend, MonadLogger m) => ReaderT backend m a -> m a) -> _
-- information, see: mkFoundation appSettings' appDatabaseConnPool appDatabaseAccess appSmtpPool appLdapPool appCryptoIDKey appSessionStore appSecretBoxKey appWidgetMemcached appJSONWebKeySet appClusterID appMemcached appUploadCache appVerpSecret appAuthKey = UniWorX {..}
-- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html -- The UniWorX {..} syntax is an example of record wild cards. For more
tempFoundation = mkFoundation -- information, see:
(error "appSettings' forced in tempFoundation") -- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html
(error "connPool forced in tempFoundation") tempFoundation = mkFoundation
(error "smtpPool forced in tempFoundation") (error "appSettings' forced in tempFoundation")
(error "ldapPool forced in tempFoundation") (error "databaseConnPool forced in tempFoundation")
(error "cryptoIDKey forced in tempFoundation") (error "databaseAccess forced in tempFoundation")
(error "sessionStore forced in tempFoundation") (error "smtpPool forced in tempFoundation")
(error "secretBoxKey forced in tempFoundation") (error "ldapPool forced in tempFoundation")
(error "widgetMemcached forced in tempFoundation") (error "cryptoIDKey forced in tempFoundation")
(error "JSONWebKeySet forced in tempFoundation") (error "sessionStore forced in tempFoundation")
(error "ClusterID forced in tempFoundation") (error "secretBoxKey forced in tempFoundation")
(error "memcached forced in tempFoundation") (error "widgetMemcached forced in tempFoundation")
(error "MinioConn forced in tempFoundation") (error "JSONWebKeySet forced in tempFoundation")
(error "VerpSecret forced in tempFoundation") (error "ClusterID forced in tempFoundation")
(error "AuthKey forced in tempFoundation") (error "memcached forced in tempFoundation")
(error "MinioConn forced in tempFoundation")
(error "VerpSecret forced in tempFoundation")
(error "AuthKey forced in tempFoundation")
runAppLoggingT tempFoundation $ do runAppLoggingT tempFoundation $ do
$logInfoS "InstanceID" $ UUID.toText appInstanceID $logInfoS "InstanceID" $ UUID.toText appInstanceID
@ -237,9 +240,14 @@ makeFoundation appSettings''@AppSettings{..} = do
-- Create the database connection pool -- Create the database connection pool
$logDebugS "setup" "PostgreSQL-Pool" $logDebugS "setup" "PostgreSQL-Pool"
sqlPool <- createPostgresqlPool appDatabaseConnPool <- createPostgresqlPool
(pgConnStr appDatabaseConf) (pgConnStr appDatabaseConf)
(pgPoolSize appDatabaseConf) (pgPoolSize appDatabaseConf)
let
appDatabaseAccess :: forall backend m a. (MonadUnliftIO m, BackendCompatible backend SqlBackend, MonadLogger m) => ReaderT backend m a -> m a
appDatabaseAccess
| appDatabasePool = flip runSqlPool appDatabaseConnPool . withReaderT projectBackend
| otherwise = withPostgresqlConn (pgConnStr appDatabaseConf) . runSqlConn . withReaderT projectBackend
ldapPool <- traverse mkFailoverLabeled <=< forOf (traverse . traverse) appLdapConf $ \conf@LdapConf{..} -> do ldapPool <- traverse mkFailoverLabeled <=< forOf (traverse . traverse) appLdapConf $ \conf@LdapConf{..} -> do
let ldapLabel = case ldapHost of let ldapLabel = case ldapHost of
@ -254,33 +262,33 @@ makeFoundation appSettings''@AppSettings{..} = do
if if
| appAutoDbMigrate -> do | appAutoDbMigrate -> do
$logDebugS "setup" "Migration" $logDebugS "setup" "Migration"
migrateAll `runSqlPool` sqlPool appDatabaseAccess migrateAll
| otherwise -> whenM (requiresMigration `runSqlPool` sqlPool) $ do | otherwise -> whenM (appDatabaseAccess requiresMigration) $ do
$logErrorS "setup" "Migration required" $logErrorS "setup" "Migration required"
liftIO . exitWith $ ExitFailure 130 liftIO . exitWith $ ExitFailure 130
$logDebugS "setup" "Cluster-Config" $logDebugS "setup" "Cluster-Config"
appCryptoIDKey <- clusterSetting (Proxy :: Proxy 'ClusterCryptoIDKey) `runSqlPool` sqlPool appCryptoIDKey <- appDatabaseAccess . clusterSetting $ Proxy @'ClusterCryptoIDKey
appSecretBoxKey <- clusterSetting (Proxy :: Proxy 'ClusterSecretBoxKey) `runSqlPool` sqlPool appSecretBoxKey <- appDatabaseAccess . clusterSetting $ Proxy @'ClusterSecretBoxKey
appJSONWebKeySet <- clusterSetting (Proxy :: Proxy 'ClusterJSONWebKeySet) `runSqlPool` sqlPool appJSONWebKeySet <- appDatabaseAccess . clusterSetting $ Proxy @'ClusterJSONWebKeySet
appClusterID <- clusterSetting (Proxy :: Proxy 'ClusterId) `runSqlPool` sqlPool appClusterID <- appDatabaseAccess . clusterSetting $ Proxy @'ClusterId
appVerpSecret <- clusterSetting (Proxy :: Proxy 'ClusterVerpSecret) `runSqlPool` sqlPool appVerpSecret <- appDatabaseAccess . clusterSetting $ Proxy @'ClusterVerpSecret
appAuthKey <- clusterSetting (Proxy :: Proxy 'ClusterAuthKey) `runSqlPool` sqlPool appAuthKey <- appDatabaseAccess . clusterSetting $ Proxy @'ClusterAuthKey
needsRechunk <- exists [FileContentChunkContentBased !=. True] `runSqlPool` sqlPool needsRechunk <- appDatabaseAccess @SqlReadBackend $ exists [FileContentChunkContentBased !=. True]
let appSettings' = appSettings'' let appSettings' = appSettings''
& _appRechunkFiles %~ guardOnM needsRechunk & _appRechunkFiles %~ guardOnM needsRechunk
appMemcached <- for appMemcachedConf $ \memcachedConf -> do appMemcached <- for appMemcachedConf $ \memcachedConf -> do
$logDebugS "setup" "Memcached" $logDebugS "setup" "Memcached"
memcachedKey <- clusterSetting (Proxy :: Proxy 'ClusterMemcachedKey) `runSqlPool` sqlPool memcachedKey <- appDatabaseAccess . clusterSetting $ Proxy @'ClusterMemcachedKey
memcached <- createMemcached memcachedConf memcached <- createMemcached memcachedConf
when appClearCache $ do when appClearCache $ do
$logWarnS "setup" "Clearing memcached" $logWarnS "setup" "Clearing memcached"
liftIO $ Memcached.flushAll memcached liftIO $ Memcached.flushAll memcached
return (memcachedKey, memcached) return (memcachedKey, memcached)
appSessionStore <- mkSessionStore appSettings'' sqlPool `runSqlPool` sqlPool appSessionStore <- appDatabaseAccess $ mkSessionStore appSettings'' appDatabaseConnPool
appUploadCache <- for appUploadCacheConf $ \minioConf -> liftIO $ do appUploadCache <- for appUploadCacheConf $ \minioConf -> liftIO $ do
conn <- Minio.connect minioConf conn <- Minio.connect minioConf
@ -293,7 +301,7 @@ makeFoundation appSettings''@AppSettings{..} = do
$logDebugS "Runtime configuration" $ tshow appSettings' $logDebugS "Runtime configuration" $ tshow appSettings'
let foundation = mkFoundation appSettings' sqlPool smtpPool ldapPool appCryptoIDKey appSessionStore appSecretBoxKey appWidgetMemcached appJSONWebKeySet appClusterID appMemcached appUploadCache appVerpSecret appAuthKey let foundation = mkFoundation appSettings' appDatabaseConnPool appDatabaseAccess smtpPool ldapPool appCryptoIDKey appSessionStore appSecretBoxKey appWidgetMemcached appJSONWebKeySet appClusterID appMemcached appUploadCache appVerpSecret appAuthKey
-- Return the foundation -- Return the foundation
$logDebugS "setup" "Done" $logDebugS "setup" "Done"
@ -644,7 +652,7 @@ shutdownApp :: (MonadIO m, MonadUnliftIO m) => UniWorX -> m ()
shutdownApp app = do shutdownApp app = do
stopJobCtl app stopJobCtl app
liftIO $ do liftIO $ do
destroyAllResources $ appConnPool app destroyAllResources $ appDatabaseConnPool app
for_ (appSmtpPool app) destroyAllResources for_ (appSmtpPool app) destroyAllResources
for_ (appLdapPool app) . mapFailover $ views _2 destroyAllResources for_ (appLdapPool app) . mapFailover $ views _2 destroyAllResources
for_ (appWidgetMemcached app) Memcached.close for_ (appWidgetMemcached app) Memcached.close

View File

@ -10,18 +10,18 @@ import Foundation.Type
import qualified Control.Retry as Retry import qualified Control.Retry as Retry
import GHC.IO.Exception (IOErrorType(OtherError)) import GHC.IO.Exception (IOErrorType(OtherError))
import Database.Persist.Sql (runSqlPool, SqlReadBackend(..)) import Database.Persist.Sql (SqlReadBackend(..))
import Database.Persist.Sql.Raw.QQ (executeQQ) import Database.Persist.Sql.Raw.QQ (executeQQ)
runSqlPoolRetry :: forall m a backend. runSqlPoolRetry :: forall m a backend.
( MonadUnliftIO m, BackendCompatible SqlBackend backend ( MonadUnliftIO m
, MonadLogger m, MonadMask m , MonadLogger m, MonadMask m
) )
=> ReaderT backend m a => (ReaderT backend m a -> m a)
-> Pool backend -> ReaderT backend m a
-> m a -> m a
runSqlPoolRetry action pool = do runSqlPoolRetry dbAccess action = do
let policy = Retry.fullJitterBackoff 1e3 & Retry.limitRetriesByCumulativeDelay 10e6 let policy = Retry.fullJitterBackoff 1e3 & Retry.limitRetriesByCumulativeDelay 10e6
handlers = Retry.skipAsyncExceptions `snoc` Retry.logRetries suggestRetry logRetry handlers = Retry.skipAsyncExceptions `snoc` Retry.logRetries suggestRetry logRetry
where suggestRetry :: IOException -> m Bool where suggestRetry :: IOException -> m Bool
@ -39,9 +39,10 @@ runSqlPoolRetry action pool = do
Retry.recovering policy handlers $ \Retry.RetryStatus{..} -> do Retry.recovering policy handlers $ \Retry.RetryStatus{..} -> do
$logDebugS "runSqlPoolRetry" $ "rsIterNumber = " <> tshow rsIterNumber $logDebugS "runSqlPoolRetry" $ "rsIterNumber = " <> tshow rsIterNumber
runSqlPool action pool dbAccess action
runDBRead :: ReaderT SqlReadBackend (HandlerFor UniWorX) a -> (HandlerFor UniWorX) a runDBRead :: ReaderT SqlReadBackend (HandlerFor UniWorX) a -> (HandlerFor UniWorX) a
runDBRead action = do runDBRead action = do
$logDebugS "YesodPersist" "runDBRead" $logDebugS "YesodPersist" "runDBRead"
runSqlPoolRetry (withReaderT SqlReadBackend $ [executeQQ|SET TRANSACTION READ ONLY|] *> action) . appConnPool =<< getYesod dbAccess <- getsYesod appDatabaseAccess
runSqlPoolRetry dbAccess . withReaderT SqlReadBackend $ [executeQQ|SET TRANSACTION READ ONLY|] *> action

View File

@ -6,12 +6,11 @@ module Foundation.Type
, SomeSessionStorage(..) , SomeSessionStorage(..)
, _SessionStorageMemcachedSql, _SessionStorageAcid , _SessionStorageMemcachedSql, _SessionStorageAcid
, SMTPPool , SMTPPool
, _appSettings', _appStatic, _appConnPool, _appSmtpPool, _appLdapPool, _appWidgetMemcached, _appHttpManager, _appLogger, _appLogSettings, _appCryptoIDKey, _appClusterID, _appInstanceID, _appJobState, _appSessionStore, _appSecretBoxKey, _appJSONWebKeySet, _appHealthReport, _appMemcached, _appUploadCache, _appVerpSecret, _appAuthKey , _appSettings', _appStatic, _appDatabaseConnPool, _appDatabaseAccess, _appSmtpPool, _appLdapPool, _appWidgetMemcached, _appHttpManager, _appLogger, _appLogSettings, _appCryptoIDKey, _appClusterID, _appInstanceID, _appJobState, _appSessionStore, _appSecretBoxKey, _appJSONWebKeySet, _appHealthReport, _appMemcached, _appUploadCache, _appVerpSecret, _appAuthKey
, DB, Form, MsgRenderer, MailM, DBFile , DB, Form, MsgRenderer, MailM, DBFile
) where ) where
import Import.NoFoundation import Import.NoFoundation
import Database.Persist.Sql (ConnectionPool)
import Jobs.Types import Jobs.Types
@ -43,7 +42,8 @@ makePrisms ''SomeSessionStorage
data UniWorX = UniWorX data UniWorX = UniWorX
{ appSettings' :: AppSettings { appSettings' :: AppSettings
, appStatic :: EmbeddedStatic -- ^ Settings for static file serving. , appStatic :: EmbeddedStatic -- ^ Settings for static file serving.
, appConnPool :: ConnectionPool -- ^ Database connection pool. , appDatabaseConnPool :: Pool SqlBackend
, appDatabaseAccess :: forall backend m a. (MonadUnliftIO m, BackendCompatible backend SqlBackend, MonadLogger m) => ReaderT backend m a -> m a
, appSmtpPool :: Maybe SMTPPool , appSmtpPool :: Maybe SMTPPool
, appLdapPool :: Maybe (Failover (LdapConf, LdapPool)) , appLdapPool :: Maybe (Failover (LdapConf, LdapPool))
, appWidgetMemcached :: Maybe Memcached.Connection -- ^ Actually a proper pool , appWidgetMemcached :: Maybe Memcached.Connection -- ^ Actually a proper pool

View File

@ -25,14 +25,15 @@ runDB action = do
| dryRun = action <* transactionUndo | dryRun = action <* transactionUndo
| otherwise = action | otherwise = action
runSqlPoolRetry action' . appConnPool =<< getYesod dbAccess <- getsYesod appDatabaseAccess
runSqlPoolRetry dbAccess action'
getDBRunner :: ( YesodPersistBackend UniWorX ~ SqlBackend getDBRunner :: ( YesodPersistBackend UniWorX ~ SqlBackend
, BearerAuthSite UniWorX , BearerAuthSite UniWorX
) )
=> HandlerFor UniWorX (DBRunner UniWorX, HandlerFor UniWorX ()) => HandlerFor UniWorX (DBRunner UniWorX, HandlerFor UniWorX ())
getDBRunner = do getDBRunner = do
(DBRunner{..}, cleanup) <- defaultGetDBRunner appConnPool (DBRunner{..}, cleanup) <- defaultGetDBRunner appDatabaseConnPool
return . (, cleanup) $ DBRunner return . (, cleanup) $ DBRunner
(\action -> do (\action -> do
dryRun <- isDryRun dryRun <- isDryRun

View File

@ -90,6 +90,7 @@ data AppSettings = AppSettings
, appWellKnownDir :: FilePath , appWellKnownDir :: FilePath
, appWellKnownLinkFile :: FilePath , appWellKnownLinkFile :: FilePath
, appDatabaseConf :: PostgresConf , appDatabaseConf :: PostgresConf
, appDatabasePool :: Bool
-- ^ Configuration settings for accessing the database. -- ^ Configuration settings for accessing the database.
, appAutoDbMigrate :: Bool , appAutoDbMigrate :: Bool
, appLdapConf :: Maybe (PointedList LdapConf) , appLdapConf :: Maybe (PointedList LdapConf)
@ -516,6 +517,7 @@ instance FromJSON AppSettings where
appWellKnownLinkFile <- o .: "well-known-link-file" appWellKnownLinkFile <- o .: "well-known-link-file"
appWebpackEntrypoints <- o .: "webpack-manifest" appWebpackEntrypoints <- o .: "webpack-manifest"
appDatabaseConf <- o .: "database" appDatabaseConf <- o .: "database"
appDatabasePool <- o .:? "database-pool" .!= True
appAutoDbMigrate <- o .: "auto-db-migrate" appAutoDbMigrate <- o .: "auto-db-migrate"
let nonEmptyHost LdapConf{..} = case ldapHost of let nonEmptyHost LdapConf{..} = case ldapHost of
Ldap.Tls host _ -> not $ null host Ldap.Tls host _ -> not $ null host

View File

@ -48,7 +48,7 @@ main = do
[executeQQ|drop owned by current_user|] :: ReaderT SqlBackend _ () [executeQQ|drop owned by current_user|] :: ReaderT SqlBackend _ ()
DBTruncate -> db' $ do DBTruncate -> db' $ do
foundation <- getYesod foundation <- getYesod
liftIO . destroyAllResources $ appConnPool foundation liftIO . destroyAllResources $ appDatabaseConnPool foundation
truncateDb truncateDb
DBMigrate -> db' $ return () DBMigrate -> db' $ return ()
DBFill -> db' $ fillDb DBFill -> db' $ fillDb

View File

@ -78,7 +78,7 @@ runDB query = do
liftIO $ runDBWithApp app query liftIO $ runDBWithApp app query
runDBWithApp :: MonadIO m => UniWorX -> SqlPersistM a -> m a runDBWithApp :: MonadIO m => UniWorX -> SqlPersistM a -> m a
runDBWithApp app query = liftIO $ runSqlPersistMPool query (appConnPool app) runDBWithApp app query = liftIO $ runSqlPersistMPool query (appDatabaseConnPool app)
runHandler :: Handler a -> YesodExample UniWorX a runHandler :: Handler a -> YesodExample UniWorX a
runHandler handler = do runHandler handler = do