feat: pruning of unreferenced files

This commit is contained in:
Gregor Kleen 2019-11-04 17:20:26 +01:00
parent 766ca63b40
commit ff161b2e04
12 changed files with 114 additions and 32 deletions

View File

@ -4,39 +4,39 @@ set -e
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en .stack-work.lock "$0" "$@" || : [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en .stack-work.lock "$0" "$@" || :
case $1 in if [[ -n "${1}" ]]; then
"") target=".stack-work-${1}"
exec -- stack clean else
;; target=".stack-work"
*) fi
target=".stack-work-${1}" shift
shift
if [[ ! -d "${target}" ]]; then if [[ ! -d "${target}" ]]; then
printf "%s does not exist or is no directory\n" "${target}" >&2 printf "%s does not exist or is no directory\n" "${target}" >&2
exit 1 exit 1
fi fi
if [[ -e .stack-work-clean ]]; then
printf ".stack-work-clean exists\n" >&2
exit 1
fi
move-back() { if [[ "${target}" != ".stack-work" ]]; then
if [[ -d .stack-work ]]; then if [[ -e .stack-work-clean ]]; then
mv -v .stack-work "${target}" printf ".stack-work-clean exists\n" >&2
else exit 1
mkdir -v "${target}" fi
fi
[[ -d .stack-work-clean ]] && mv -v .stack-work-clean .stack-work
}
mv -v .stack-work .stack-work-clean move-back() {
mv -v "${target}" .stack-work if [[ -d .stack-work ]]; then
trap move-back EXIT mv -v .stack-work "${target}"
else
mkdir -v "${target}"
fi
[[ -d .stack-work-clean ]] && mv -v .stack-work-clean .stack-work
}
( mv -v .stack-work .stack-work-clean
set -ex mv -v "${target}" .stack-work
stack clean $@ trap move-back EXIT
) fi
;;
esac (
set -ex
stack clean $@
)

View File

@ -30,6 +30,8 @@ session-timeout: 7200
jwt-expiration: 604800 jwt-expiration: 604800
jwt-encoding: HS256 jwt-encoding: HS256
maximum-content-length: "_env:MAX_UPLOAD_SIZE:134217728" maximum-content-length: "_env:MAX_UPLOAD_SIZE:134217728"
session-files-expire: 3600
prune-unreferenced-files: 86400
health-check-interval: health-check-interval:
matching-cluster-config: "_env:HEALTHCHECK_INTERVAL_MATCHING_CLUSTER_CONFIG:600" matching-cluster-config: "_env:HEALTHCHECK_INTERVAL_MATCHING_CLUSTER_CONFIG:600"
http-reachable: "_env:HEALTHCHECK_INTERVAL_HTTP_REACHABLE:600" http-reachable: "_env:HEALTHCHECK_INTERVAL_HTTP_REACHABLE:600"

View File

@ -6,3 +6,9 @@ File
content ByteString Maybe -- Nothing iff this is a directory content ByteString Maybe -- Nothing iff this is a directory
modified UTCTime modified UTCTime
deriving Show Eq Generic deriving Show Eq Generic
SessionFile
user UserId
reference SessionFileReference
file FileId
touched UTCTime

View File

@ -153,6 +153,8 @@ import Colonnade.Instances as Import ()
import Data.Bool.Instances as Import () import Data.Bool.Instances as Import ()
import Data.Encoding.Instances as Import () import Data.Encoding.Instances as Import ()
import Crypto.Hash as Import (Digest, SHA3_256)
import Control.Lens as Import import Control.Lens as Import
hiding ( (<.>) hiding ( (<.>)
, universe , universe

View File

@ -71,6 +71,7 @@ import Jobs.Handler.TransactionLog
import Jobs.Handler.SynchroniseLdap import Jobs.Handler.SynchroniseLdap
import Jobs.Handler.PruneInvitations import Jobs.Handler.PruneInvitations
import Jobs.Handler.ChangeUserDisplayEmail import Jobs.Handler.ChangeUserDisplayEmail
import Jobs.Handler.PruneFiles
import Jobs.HealthReport import Jobs.HealthReport

View File

@ -49,6 +49,15 @@ determineCrontab = execWriterT $ do
, cronRateLimit = appJobCronInterval , cronRateLimit = appJobCronInterval
, cronNotAfter = Right CronNotScheduled , cronNotAfter = Right CronNotScheduled
} }
whenIsJust appPruneUnreferencedFiles $ \pInterval ->
tell $ HashMap.singleton
(JobCtlQueue JobPruneUnreferencedFiles)
Cron
{ cronInitial = CronAsap
, cronRepeat = CronRepeatScheduled CronAsap
, cronRateLimit = pInterval
, cronNotAfter = Right CronNotScheduled
}
oldestInvitationMUTC <- lift $ preview (_head . _entityVal . _invitationExpiresAt . _Just) <$> selectList [InvitationExpiresAt !=. Nothing] [Asc InvitationExpiresAt, LimitTo 1] oldestInvitationMUTC <- lift $ preview (_head . _entityVal . _invitationExpiresAt . _Just) <$> selectList [InvitationExpiresAt !=. Nothing] [Asc InvitationExpiresAt, LimitTo 1]
whenIsJust oldestInvitationMUTC $ \oldestInvUTC -> tell $ HashMap.singleton whenIsJust oldestInvitationMUTC $ \oldestInvUTC -> tell $ HashMap.singleton
@ -60,6 +69,16 @@ determineCrontab = execWriterT $ do
, cronNotAfter = Right CronNotScheduled , cronNotAfter = Right CronNotScheduled
} }
oldestSessionFile <- lift $ preview (_head . _entityVal . _sessionFileTouched) <$> selectList [] [Asc SessionFileTouched, LimitTo 1]
whenIsJust oldestSessionFile $ \oldest -> tell $ HashMap.singleton
(JobCtlQueue JobPruneSessionFiles)
Cron
{ cronInitial = CronTimestamp . utcToLocalTime $ addUTCTime appSessionFilesExpire oldest
, cronRepeat = CronRepeatOnChange
, cronRateLimit = appSessionFilesExpire / 2
, cronNotAfter = Right CronNotScheduled
}
tell . flip foldMap universeF $ \kind -> tell . flip foldMap universeF $ \kind ->
case appHealthCheckInterval kind of case appHealthCheckInterval kind of
Just int -> HashMap.singleton Just int -> HashMap.singleton

View File

@ -0,0 +1,38 @@
module Jobs.Handler.PruneFiles
( dispatchJobPruneSessionFiles
, dispatchJobPruneUnreferencedFiles
) where
import Import hiding (matching)
import Database.Persist.Sql (deleteWhereCount)
import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E
dispatchJobPruneSessionFiles :: Handler ()
dispatchJobPruneSessionFiles = do
now <- liftIO getCurrentTime
expires <- getsYesod $ view _appSessionFilesExpire
n <- runDB $ deleteWhereCount [ SessionFileTouched <. addUTCTime (- expires) now ]
$logInfoS "PruneSessionFiles" [st|Deleted #{n} expired session files|]
dispatchJobPruneUnreferencedFiles :: Handler ()
dispatchJobPruneUnreferencedFiles = do
n <- runDB . E.deleteCount . E.from $ \file ->
E.where_ . E.not_ . E.any E.exists $ references file
$logInfoS "PruneUnreferencedFiles" [st|Deleted #{n} unreferenced files|]
where
references :: E.SqlExpr (Entity File) -> [E.SqlQuery ()]
references ((E.^. FileId) -> fId) =
[ E.from $ \matching -> E.where_ $ matching E.^. AllocationMatchingLog E.==. fId
, E.from $ \appInstr -> E.where_ $ appInstr E.^. CourseAppInstructionFileFile E.==. fId
, E.from $ \appFile -> E.where_ $ appFile E.^. CourseApplicationFileFile E.==. fId
, E.from $ \matFile -> E.where_ $ matFile E.^. MaterialFileFile E.==. fId
, E.from $ \newsFile -> E.where_ $ newsFile E.^. CourseNewsFileFile E.==. fId
, E.from $ \sessFile -> E.where_ $ sessFile E.^. SessionFileFile E.==. fId
, E.from $ \sheetFile -> E.where_ $ sheetFile E.^. SheetFileFile E.==. fId
, E.from $ \subFile -> E.where_ $ subFile E.^. SubmissionFileFile E.==. fId
]

View File

@ -67,6 +67,8 @@ data Job = JobSendNotification { jRecipient :: UserId, jNotification :: Notifica
| JobChangeUserDisplayEmail { jUser :: UserId | JobChangeUserDisplayEmail { jUser :: UserId
, jDisplayEmail :: UserEmail , jDisplayEmail :: UserEmail
} }
| JobPruneSessionFiles
| JobPruneUnreferencedFiles
deriving (Eq, Ord, Show, Read, Generic, Typeable) deriving (Eq, Ord, Show, Read, Generic, Typeable)
data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId } data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId }
| NotificationSheetActive { nSheet :: SheetId } | NotificationSheetActive { nSheet :: SheetId }

View File

@ -51,3 +51,5 @@ type InstanceId = UUID
type ClusterId = UUID type ClusterId = UUID
type TokenId = UUID type TokenId = UUID
type TermCandidateIncidence = UUID type TermCandidateIncidence = UUID
type SessionFileReference = Digest SHA3_256

View File

@ -126,6 +126,9 @@ data AppSettings = AppSettings
, appSynchroniseLdapUsersWithin :: Maybe NominalDiffTime , appSynchroniseLdapUsersWithin :: Maybe NominalDiffTime
, appSynchroniseLdapUsersInterval :: NominalDiffTime , appSynchroniseLdapUsersInterval :: NominalDiffTime
, appSessionFilesExpire :: NominalDiffTime
, appPruneUnreferencedFiles :: Maybe NominalDiffTime
, appInitialLogSettings :: LogSettings , appInitialLogSettings :: LogSettings
, appTransactionLogIPRetentionTime :: NominalDiffTime , appTransactionLogIPRetentionTime :: NominalDiffTime
@ -417,6 +420,9 @@ instance FromJSON AppSettings where
appSynchroniseLdapUsersWithin <- o .:? "synchronise-ldap-users-within" appSynchroniseLdapUsersWithin <- o .:? "synchronise-ldap-users-within"
appSynchroniseLdapUsersInterval <- o .: "synchronise-ldap-users-interval" appSynchroniseLdapUsersInterval <- o .: "synchronise-ldap-users-interval"
appSessionFilesExpire <- o .: "session-files-expire"
appPruneUnreferencedFiles <- o .:? "prune-unreferenced-files"
appMaximumContentLength <- o .: "maximum-content-length" appMaximumContentLength <- o .: "maximum-content-length"
appReloadTemplates <- o .:? "reload-templates" .!= defaultDev appReloadTemplates <- o .:? "reload-templates" .!= defaultDev

View File

@ -210,6 +210,8 @@ makeLenses_ ''CourseNewsFile
makeLenses_ ''AllocationCourse makeLenses_ ''AllocationCourse
makeLenses_ ''Tutorial makeLenses_ ''Tutorial
makeLenses_ ''SessionFile
-- makeClassy_ ''Load -- makeClassy_ ''Load

View File

@ -802,3 +802,5 @@ fillDb = do
} }
insert_ $ AllocationCourse funAlloc pmo 100 insert_ $ AllocationCourse funAlloc pmo 100
insert_ $ AllocationCourse funAlloc ffp 2 insert_ $ AllocationCourse funAlloc ffp 2
void $ insertFile "H10-2.hs" -- unreferenced