Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable event retention #131

Merged
merged 5 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 108 additions & 13 deletions ayon_server/background/clean_up.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import asyncio
import datetime
import time

from nxtools import log_traceback, logging

from ayon_server.background.background_worker import BackgroundWorker
from ayon_server.config import ayonconfig
from ayon_server.helpers.project_files import delete_unused_files
from ayon_server.helpers.project_list import get_project_list
from ayon_server.lib.postgres import Postgres
Expand Down Expand Up @@ -47,29 +52,119 @@ async def clear_actions() -> None:
await Postgres.execute(query)


async def clear_logs() -> None:
"""Purge old logs."""

log_retention = ayonconfig.log_retention_days * 24 * 3600

now = datetime.datetime.now()
last_log_to_keep = now - datetime.timedelta(seconds=log_retention)
delete_from = now - datetime.timedelta(seconds=log_retention * 2)

# Delete all logs older than the last log to keep

try:
res = await Postgres.fetch(
"""
WITH deleted AS (
DELETE FROM events WHERE
topic IN ('log.info', 'log.error', 'log.warning')
AND created_at > $1
AND created_at < $2
RETURNING *
) SELECT count(*) as del FROM deleted;
""",
delete_from,
last_log_to_keep,
timeout=500,
)

if res:
deleted = res[0]["del"]
if deleted:
logging.debug(f"Deleted {deleted} old log entries")
except Exception:
log_traceback()


async def clear_events() -> None:
"""Purge old events.

Delete events older than the value specified in ayon-config.
This is opt-in and by default, old events are not deleted.
"""

if ayonconfig.event_retention_days is None:
return

num_days = ayonconfig.event_retention_days

query = f"""
WITH dependent_events AS (
SELECT DISTINCT id FROM events
WHERE updated_at >= now() - interval '{num_days + 1} days'
AND depends_on IS NOT NULL
),

deleted AS (
DELETE FROM events WHERE
updated_at < now() - interval '{num_days} days'
AND id NOT IN (SELECT id FROM dependent_events)
RETURNING id
)

SELECT count(*) as del FROM deleted;
"""

start_time = time.monotonic()
res = await Postgres.fetch(query, timeout=500)
elapsed = time.monotonic() - start_time
if res:
deleted = res[0]["del"]
if deleted:
logging.debug(f"Deleted {deleted} old events")
if elapsed > 1:
logging.debug(f"Event clean-up took {elapsed:.2f} seconds")


class AyonCleanUp(BackgroundWorker):
"""Background task for periodic clean-up of stuff."""

async def run(self):
# Execute the first clean-up after a minute, when
# everything is settled down.
# Execute the first clean-up after a minue,
# when everything is settled after the start-up.

await asyncio.sleep(60)

while True:
try:
await self.clean_all()
except Exception as e:
print(f"Error in clean-up: {e}")
await self.clear_all()
await asyncio.sleep(3600)

async def clean_all(self):
projects = await get_project_list()
for project in projects:
await clear_thumbnails(project.name)
await delete_unused_files(project.name)

await clear_actions()
async def clear_all(self):
try:
projects = await get_project_list()
except Exception:
# This should not happen, but if it does, log it and continue
# We don't want to stop the clean-up process because of this
log_traceback("Clean-up: Error getting project list")
else:
# For each project, clean up thumbnails and unused files
for project in projects:
for prj_func in (clear_thumbnails, delete_unused_files):
try:
await prj_func(project.name)
except Exception:
log_traceback(
f"Error in {prj_func.__name__} for {project.name}"
)

# This clears not project-specific items (events)

for func in (clear_actions, clear_logs, clear_events):
try:
await func()
except Exception:
log_traceback(f"Error in {func.__name__}")


clean_up = AyonCleanUp()
57 changes: 0 additions & 57 deletions ayon_server/background/log_cleaner.py

This file was deleted.

2 changes: 0 additions & 2 deletions ayon_server/background/workers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from .background_worker import BackgroundWorker
from .clean_up import clean_up
from .log_cleaner import log_cleaner
from .log_collector import log_collector
from .metrics_collector import metrics_collector

Expand All @@ -12,7 +11,6 @@ def __init__(self):
self.tasks: list[BackgroundWorker] = [
background_installer,
log_collector,
log_cleaner,
metrics_collector,
clean_up,
]
Expand Down
6 changes: 6 additions & 0 deletions ayon_server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ class AyonConfig(BaseModel):
description="Number of days to keep logs in the event log",
)

event_retention_days: int | None = Field(
default=None,
description="Number of days to keep events in the event log",
example=90,
)

ynput_cloud_api_url: str | None = Field(
"https://im.ynput.cloud",
description="YnputConnect URL",
Expand Down