feat: clear-log-table to clear large log tables

This commit is contained in:
Ankush Menat 2022-06-11 16:04:43 +05:30
parent 95eb6cd085
commit 8b8552b0e4
2 changed files with 90 additions and 0 deletions

View file

@ -1088,6 +1088,81 @@ def build_search_index(context):
frappe.destroy()
LOG_DOCTYPES = [
"Scheduled Job Log",
"Activity Log",
"Route History",
"Email Queue",
"Error Snapshot",
"Error Log",
]
@click.command("clear-log-table")
@click.option("--doctype", default="text", type=click.Choice(LOG_DOCTYPES), help="Log DocType")
@click.option("--days", type=int, help="Keep records for days")
@click.option("--no-backup", is_flag=True, default=False, help="Do not backup the table")
@pass_context
def clear_log_table(context, doctype, days, no_backup):
"""If any logtype table grows too large then clearing it with DELETE query
is not feasible in reasonable time. This command copies recent data to new
table and replaces current table with new smaller table.
ref: https://mariadb.com/kb/en/big-deletes/#deleting-more-than-half-a-table
"""
from frappe.utils.backups import scheduled_backup
if not context.sites:
raise SiteNotSpecifiedError
if doctype not in LOG_DOCTYPES:
raise frappe.ValidationError(f"Unsupported logging DocType: {doctype}")
for site in context.sites:
frappe.init(site=site)
frappe.connect()
if frappe.db.db_type != "mariadb":
click.echo("Postgres database isn't supported by this command")
sys.exit(1)
if not no_backup:
scheduled_backup(
ignore_conf=False,
include_doctypes=doctype,
ignore_files=True,
force=True,
)
click.echo(f"Backed up {doctype}")
original = f"`tab{doctype}`"
temporary = f"`tab{doctype} temp_table`"
backup = f"`tab{doctype} backup_table`"
try:
frappe.db.sql(f"CREATE TABLE {temporary} LIKE {original}")
click.echo(f"Copying {doctype} records from last {days} days to temporary table.")
# Copy all recent data to new table
frappe.db.sql(
f"""INSERT INTO {temporary}
SELECT * FROM {original}
WHERE {original}.`modified` > NOW() - INTERVAL '{days}' DAY"""
)
frappe.db.sql(f"RENAME TABLE {original} TO {backup}, {temporary} TO {original}")
except Exception as e:
# Discard created tables
frappe.db.rollback()
frappe.db.sql(f"DROP TABLE IF EXISTS {temporary}")
click.echo(f"Log cleanup for {doctype} failed:\n{e}")
sys.exit(1)
else:
frappe.db.commit()
frappe.db.sql(f"DROP TABLE {backup}")
click.secho(f"Cleared {doctype} records older than {days} days", fg="green")
@click.command("trim-database")
@click.option("--dry-run", is_flag=True, default=False, help="Show what would be deleted")
@click.option(
@ -1260,4 +1335,5 @@ commands = [
partial_restore,
trim_tables,
trim_database,
clear_log_table,
]

View file

@ -27,6 +27,8 @@ import frappe.commands.site
import frappe.commands.utils
import frappe.recorder
from frappe.installer import add_to_installed_apps, remove_app
from frappe.query_builder.utils import db_type_is
from frappe.tests.test_query_builder import run_only_if
from frappe.utils import add_to_date, get_bench_path, get_bench_relative_path, now
from frappe.utils.backups import fetch_latest_backups
@ -518,6 +520,18 @@ class TestBackups(BaseTestCommands):
self.assertIsNotNone(after_backup["public"])
self.assertIsNotNone(after_backup["private"])
@run_only_if(db_type_is.MARIADB)
def test_clear_log_table(self):
d = frappe.get_doc(doctype="Error Log", title="Something").insert()
d.db_set("modified", "2010-01-01", update_modified=False)
frappe.db.commit()
self.execute("bench --site {site} clear-log-table --days=30 --doctype='Error Log'")
self.assertEqual(self.returncode, 0)
frappe.db.commit()
self.assertFalse(frappe.db.exists("Error Log", d.name))
def test_backup_with_custom_path(self):
"""Backup to a custom path (--backup-path)"""
backup_path = os.path.join(self.home, "backups")