Summary
When the purge method is called on an ActiveStorage attachment, the Blob record is deleted, but the underlying file remains in the storage service unless the disk or cloud provider’s deletion event is triggered. In the scenario above, the purge loop removed the Blob rows but left the physical files intact, resulting in wasted space.
Root Cause
purgedeletes theBlobmetadata only.- The actual binary blob storage isn’t automatically cleaned up if the attachment hasn’t been fully detached.
- The attachment may still reference the storage key, or the service doesn’t fire the delete event when the metadata row is removed.
Why This Happens in Real Systems
- Lazy deletion: Many systems separate metadata from storage to allow background jobs or caching.
- Service constraints: Cloud providers (S3, GCS, etc.) require explicit delete calls.
- Race conditions: A concurrent read may keep the file referenced, preventing automatic removal.
- Configuration: Rails may be set to keep files locally or in an S3 bucket without lifecycle policies.
Real-World Impact
- Increased storage costs: Unused files occupy expensive disk or cloud space.
- Security risk: Old files may contain sensitive data that remains publicly accessible.
- Cluttered backups: Backup jobs include large, unnecessary files, slowing restores.
- Compliance violations: Regulations (GDPR, HIPAA) require deletion of obsolete data.
Example or Code (if necessary and relevant)
# Correct way to fully remove a file from storage:
attachment = listing.public_image_attachment
attachment.purge_later # Enqueue background job
How Senior Engineers Fix It
- Use
purge_later: Delegates deletion to a background worker that calls the storage provider’s delete API. - Ensure attachment is detached:
listing.public_image.detachremoves the association before purging. - Configure service lifecycle: Set an S3 lifecycle policy to delete objects older than X days.
- Verify storage service callbacks: Confirm that the storage adapter is correctly configured to trigger file deletions.
- Run a reconciliation job: Scan for orphaned files (storage key exists but no Blob) and delete them.
Why Juniors Miss It
- They assume
purgedeletes everything. - They overlook the distinction between metadata (Blob) and storage (file).
- They rarely audit storage after bulk operations, missing the hidden cost.
- They often ignore service lifecycle configuration, thinking Rails handles everything.