Understanding Contact Deletion in Mautic (and Its Impact on Performance)

Hello,

I’m using Mautic Versions 6.0.2 and 7.0.1

If you’ve spent any time working with Mautic—especially in a testing or staging environment—you may have noticed something unexpected when deleting contacts: they don’t fully disappear from the database.

Instead of being permanently removed, Mautic performs what’s known as a soft delete on the leads table.

What Actually Happens During Deletion?

When a contact is deleted in Mautic, the system does not remove the row entirely. Rather, it:

  • Sets most fields in the record to NULL

  • Retains certain date-related fields (such as timestamps)

  • Keeps the row itself in the database

This means the database continues to grow over time, even if you regularly delete contacts through the UI.

Why This Matters

In smaller or short-lived environments, this behavior may go unnoticed. However, in scenarios like mine—where a test instance is used repeatedly for importing and validating contacts and companies—this can become a problem.

Over time, repeated imports and deletions can lead to:

  • A large number of “empty” rows in the leads table

  • Increased table size

  • Slower query performance

  • General system slowdown

This is particularly noticeable when running bulk operations, segment updates, or imports.

Real-World Example

In my workflow, I use a dedicated Mautic test instance to validate contact and company imports before pushing data to production. After several cycles of importing and deleting test data, I began to notice a gradual slowdown in the system.

Given Mautic’s soft delete behavior, it’s likely that these accumulated, partially-null records are contributing to the performance degradation.

If you’re seeing unexplained slowdowns, it’s worth taking a closer look at your leads table—you might find that deleted contacts are still hanging around.

Does Mautic have any built-in scripts to hard delete these soft delete rows ?

And should Mautic actually use soft deletes on the leads table in the first place? The row is useless as all the field are NULL except for timestamps etc. Why keep it ?

Thanks

1 Like

Does it have an IP address? What I know fresh contacts that are identified on the site will have basically just IP address and date in the lead table. Maybe the deletion is bringing it back to an anonymous contact, but it keeps the history in case the contact subscribes again. That might be the logic, I dont know.

I know though that there are cleanup commands, and you can choose to delete inactive contacts and some other options. Did you play around already with the mautic:maintenance:cleanup command?

Hello mneumann,

Thanks for highlighting the Mautic clean-up console command. I plan to review the underlying code in more detail, as there appears to be very limited documentation explaining exactly what it does.

To better understand the behaviour, I created a dummy contact and deleted it immediately afterward. Inspecting the `leads` table confirmed that the record was both created and removed as expected, which is reassuring.

However, I’ve also observed records being created in the `leads` table where all fields are `NULL` except for timestamp values. This suggests these entries are likely generated by anonymous interactions—such as visits to tracked Mautic assets or folders—rather than intentional contact creation via the UI, imports, or campaigns.

Currently, there is no explicit flag or indicator within the `leads` table to distinguish these anonymous interactions from legitimate contact records. This lack of differentiation is problematic from both a data integrity and system performance perspective. There appears to be no ip_address field name in the leads table unless is is hidden in a differently named field.

From a logical standpoint, creating, updating, or deleting contact records via the UI, imports, or campaigns is entirely valid. However, when anonymous or potentially malicious traffic (e.g., automated probing or bot activity) results in large volumes of empty lead records, it can lead to unnecessary database growth. Over time, this could degrade performance and may even resemble a denial-of-service scenario if sustained at high volume.

While the available clean-up command may provide a way to remove these records retrospectively, it does not address the root issue. A more robust architectural approach would be to store anonymous interaction data in a separate table, distinct from the primary `leads` table. This would preserve the integrity of contact data while still allowing tracking of anonymous activity in a more appropriate structure.

I’ve raised this suggestion previously, but it has yet to gain traction. I’d like to reiterate that separating anonymous interactions from contact records would improve clarity, maintainability, and performance—particularly in environments experiencing high levels of unauthenticated traffic.

Thanks