helgesverre/laravel-prunekeeper
PruneKeeper gives Laravel apps a central home for model pruning: define retention rules, schedule prune commands, and track what gets cleaned up so your database stays lean without scattered ad-hoc cleanup scripts or guesswork.
Architecture fit
laravel-prunekeeper targets a gap most Laravel apps handle with bespoke commands: exporting records to a compressed archive before deleting them, so "pruning" is auditable rather than destructive-by-default. v2.0.0's move to static methods aligned with first-party package conventions (e.g. Prunekeeper::makePrunableQuery(), Prunekeeper::flushState()) makes it feel native to the framework, and the --compression CLI flag plus prunekeeper.compression.driver config gives both ops-time and deploy-time control. It fits cleanly alongside the scheduler and queue infrastructure the team already runs.
Integration feasibility
Composer install plus config publish is a low-ceremony integration. The real feasibility question is the PHP extension matrix: zip (default) needs ext-zip, gzip needs ext-zlib, bzip2 needs ext-bz2, and targz needs ext-phar and ext-zlib. That must be validated against local dev, CI containers, and production base images before we commit to a driver. Soft-delete support via makePrunableQuery() is a good sign the package anticipates real Eloquent patterns rather than assuming hard-deleted tables.
Technical risk
where clause is total. Dry-run behavior, row-count reporting, and export-before-delete ordering must be confirmed, not assumed.void instead of $this, which breaks fluent chaining) will break any early adopter on a 0.x/1.x release. No risk for greenfield adoption.flushState() exists specifically because the package holds static state. That is a leak vector in Octane, queue workers, and test suites if not called at the right boundaries.Key questions
--dry-run, and does it report matched row counts?withoutOverlapping() / onOneServer() in the scheduler?Stack fit
Standard Laravel: config file, artisan command, Eloquent query builder, scheduler. No new infrastructure required beyond a writable archive destination and the chosen compression extension. The --compression flag lets us differ per environment (e.g. zip where ext-zip is universal, targz in containers where ext-phar is bundled with PHP).
Migration path
^2.0, publish config, pin the version, no migration needed.void) and any instance-based calls to the static API. Budget a small refactor plus a full test pass; treat it as a major-version upgrade, not a patch.Compatibility
zip as the lowest-common-denominator.makePrunableQuery() scoping) — this is a policy decision, not a technical default.Sequencing
Maintenance Low surface area but non-zero: pinned composer constraint, a changelog watch (the package just demonstrated it will make breaking changes at major versions), and PHP extension upkeep in base images. Archives accumulate, so a retention/rotation policy for the archive destination is a standing operational duty we own, not the package.
Support
Community OSS, single maintainer, first stable release. Escalation path is GitHub issues. For a component on the data-deletion path, we should have an internal fallback: the ability to disable the schedule and prune manually with a one-off script if the package regresses. Recommend pinning to 2.0.* and reviewing 2.1+ before adopting.
Scaling
bzip2 gives the best ratio at the highest CPU cost, gzip/targz are the pragmatic middle, zip is fastest and most portable.onOneServer() or an equivalent lock to prevent duplicate concurrent pruning.Failure modes
| Failure | Expected behavior | Mitigation |
|---|---|---|
| Export fails | Delete should not proceed (safe) | Verify ordering; alert on command failure |
| Delete fails after export | Rows remain; next run re-exports duplicates | Idempotency check; dedupe on archive name/hash |
| Temp disk exhausted | Export aborts; temp cleanup added in 2.0.0 | Monitor temp volume; size based on staging soak |
| OOM on large tables | Command dies mid-run | Confirm streaming; cap batch size; alert on memory |
| Static state leakage (Octane/queues) | Stale config/state across requests | Call flushState() at job/request boundaries |
| Missing PHP extension | Driver unavailable at runtime | Fail fast at boot; CI check on extension matrix |
Ramp-up Engineering ramp-up is genuinely low — a Laravel developer can read the config and command in under an hour. The real cost is data governance, not code: agreeing retention windows per table, who approves a cutoff change, where archives live, how long they're kept, and whether they contain PII. Budget most of the adoption effort there. Recommend a named owner for the prune schedule and a documented rollback (disable schedule + restore from archive) before the first production deletion.
How can I help you explore Laravel packages today?