Cleaning up old data

Databases grow forever unless you tell them not to. Set TTLs where the store supports them, delete old rows on a schedule where it doesn't, and always back up before a mass delete.

Every app accumulates data it no longer needs: expired sessions, week-old logs, finished giveaways, cache that's gone cold. Left alone it grows without limit — disk fills, queries slow down, backups bloat. Retention is the deliberate opposite: deciding what has a natural expiry and removing it on purpose. How you do that depends entirely on the store, because some delete old data for you and some don't.

At a glance
You need Any database with data that piles up over time
Plan Any
Helps to know SQLite and Caching with Redis

First, decide what actually expires

Not all data should be cleaned. Ask of each kind: if I deleted this a month from now, would anyone miss it?

  • Yes, delete it eventually: sessions, cooldowns, rate-limit counters, logs, temporary uploads, one-off job records, old cache.
  • No, keep it: users, purchases, posts, settings, anything you can't recreate.

Retention only ever applies to the first list. When in doubt, keep it — and if you must clean something valuable, archive it somewhere first.

The easy case: stores with built-in expiry

Two of the databases you'll use on Falix delete old data for you if you tell them how.

Redis — expiry is the whole point. Set a lifetime when you write the key and it self-deletes; there's no cleanup job at all:

await client.set(`session:${token}`, userId, { EX: 3600 }); // gone in an hour

That covers sessions, cooldowns, and rate limits with zero maintenance. See Caching with Redis and Redis from Python.

MongoDB — a TTL index makes the database delete documents automatically once a date field is old enough. Create it once:

// delete each doc 24 hours after its createdAt
await db.collection('logs').createIndex(
  { createdAt: 1 },
  { expireAfterSeconds: 60 * 60 * 24 }
);

Insert documents with a createdAt date and Mongo sweeps out the expired ones on its own — retention with no code to run.

The manual case: SQL databases

SQLite, MySQL, and PostgreSQL have no automatic expiry. You clean up by deleting old rows yourself — which means two things: store a timestamp, and index it.

1. Give the table a timestamp so "old" is a question you can ask:

CREATE TABLE logs (
  id         INTEGER PRIMARY KEY,
  message    TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_logs_created ON logs (created_at);

The index matters: without it, every cleanup scans the whole table.

2. Delete past a cutoff:

-- SQLite / MySQL: remove rows older than 30 days
DELETE FROM logs WHERE created_at < DATETIME('now', '-30 days');   -- SQLite
DELETE FROM logs WHERE created_at < NOW() - INTERVAL 30 DAY;        -- MySQL

3. Delete in batches for big tables. One giant DELETE can lock the table and run for a long time. Cap it and repeat until nothing's left:

DELETE FROM logs WHERE created_at < DATETIME('now', '-30 days') LIMIT 5000;

⚠️ Heads up: A DELETE with a wrong (or missing) WHERE clause empties the table. Always take a database backup — or a server backup for SQLite — before a mass delete, and run the matching SELECT count(*) first to see exactly how many rows the WHERE will hit.

Automating the manual case honestly

Here's the honest Falix detail: the panel's Schedules run console commands and power actions, not SQL — so you can't point a schedule directly at a DELETE. Automate SQL cleanup from inside your own long-running app instead:

  • A Node app or bot: run the delete on a timer — a daily setInterval, or a node-cron job. A bot can use discord.js alongside a scheduled task.
  • A Python app or bot: a background loop, discord.ext.tasks, or a scheduler library.
  • Or on startup: a small cleanup query each time the app boots handles low-traffic cases well enough.

The pattern is always the same — the app that owns the data also prunes it, on whatever clock suits.

Retention at a glance

Store Built-in expiry? How you retain
Redis Yes EX / EXPIRE per key — self-deletes
MongoDB Yes A TTL index (expireAfterSeconds) on a date field
SQLite No Scheduled DELETE ... WHERE created_at < cutoff from your app
MySQL / PostgreSQL No Same — timestamped rows, indexed, deleted on a timer

Soft delete vs hard delete

Sometimes you want data gone from view but not truly destroyed — an "undo" window, or a record you might need for an audit. That's a soft delete: add a deleted_at column, set it instead of deleting the row, and filter it out in your queries (WHERE deleted_at IS NULL). A separate job hard-deletes rows whose deleted_at is old enough. Use it when "deleted" needs to be reversible; use a plain DELETE when it doesn't.

Reclaiming space

Deleting rows frees space inside the database, but the file on disk may not shrink right away:

  • SQLite: run VACUUM occasionally to rebuild the file at its smaller size (it needs room to do so, and briefly locks the database).
  • MySQL / PostgreSQL: the engine reuses freed space automatically; you rarely need to intervene on a managed database.

Verify it works

Run your delete, then a SELECT count(*) before and after to confirm the right number of rows went — and only those. For a TTL index or a Redis key, write a record with a short expiry, wait past it, and query again: it should be gone with no action from you. Watch your database size trend down over the following days once a retention job is running.

Troubleshooting

  • The delete ran but the file didn't shrink — that's normal; deleted space is reused. Run VACUUM on SQLite if you specifically need the file smaller.
  • Cleanup is slow or locks the app — you're missing an index on the timestamp, or deleting too much at once. Add the index and delete in LIMIT batches.
  • A TTL index isn't deleting anything — the field must be a real date type, not a string or a number, and the index must set expireAfterSeconds.
  • I deleted too much — restore the backup you took first. If you didn't take one, this is why the heads-up above exists.

Next steps

Was this guide helpful?