Marco Eidinger
Swifty Tech by Marco Eidinger

Follow

Swifty Tech by Marco Eidinger

Follow
Keep your CoreData store small by vacuuming

Photo by Kowon vn on Unsplash

Keep your CoreData store small by vacuuming

Marco Eidinger's photo
Marco Eidinger
·Mar 14, 2023·

3 min read

Table of contents

  • What is VACUUM ?
  • Why to VACUUM ?
  • How to VACUUM your CoreData store?

In this blog post, I'll explain the concept of VACUUM and why & how you can apply this concept to your CoreData store.

As an iOS developer, you can use Apple's CoreData framework to store data as part of an object graph.

Core Data uses an SQLite store by default. SQLite has the concept of VACUUM and therefore you need to understand what this is and why you may need it.

What is VACUUM ?

SQLite has the VACUUM command to rebuild the database file and repacks it into a minimal amount of disk space.

Why to VACUUM ?

Reduce the database file size

Per default, SQLite databases do not automatically "free up" disk space when you delete data from tables or drop database objects like tables, views or indexes.

The database file size remains unchanged. Because SQLite just marks the deleted objects as free and reserves the space for future uses. As a result, the size of the database file always grows in size.

Increase performance

Frequent inserts, updates, and deletes can cause the database file to become fragmented and then accessing the database file becomes slower - also the database file is unnecessarily large because data is not stored contiguously within the database file.

How to VACUUM your CoreData store?

When you create a new iOS project using Core Data ...

then the underlying SQLite database will automatically be created with an incremental auto vacuum.

CoreData will ensure invoking the separate incremental_vacuum pragma to cause the auto-vacuum. This has the effect that you can observe that the database size will eventually shrink after multiple deletes and you can see the following entry in your Xcode debug console.

CoreData: debug: PostSaveMaintenance: incremental_vacuum with freelist_count

So your CoreData store gets vacuumed automatically without any adoption efforts.

Stack Overflow and Apple documentation may suggest setting the NSSQLiteManualVacuumOption as part of your setup code for your NSPersistentContainer but this is not needed anymore unless you use the Legacy Stack Setup.

I found an interesting command in the SQLite documentation.

When auto_vacuum is enabled for a database free pages may be reclaimed after deleting data, causing the file to shrink, without rebuilding the entire database using VACUUM. However, using auto_vacuum can lead to extra database file fragmentation.

And auto_vacuum does not compact partially filled pages of the database as VACUUM does.

Is fragmentation a performance problem for CoreData?

In my analysis & testing, I created and deleted thousands of entries over hundred times and I was not able to observe that fragmentation would significantly impact performance.

If you are worried about fragmentation and if you want to ensure that the database file shrinks to a time of your choosing then you can manually execute the SQLite VACUUM. Unfortunately, you cannot do this via CoreData APIs but you can interact with the SQLite database file directly with SQLite3.

Here is an extension that allows you to simply call viewContext.vacuum()

import CoreData
import SQLite3

extension NSManagedObjectContext {
    func vacuum() {
        for store in self.persistentStoreCoordinator?.persistentStores ?? [] {
            guard let url = store.url else { continue }

            print("vacuuming the database...")

            var db: OpaquePointer?
            guard sqlite3_open(url.path, &db) == SQLITE_OK else {
                print("error opening database")
                sqlite3_close(db)
                db = nil
                continue
            }

            if sqlite3_exec(db, "VACUUM;", nil, nil, nil) != SQLITE_OK {
                let errmsg = String(cString: sqlite3_errmsg(db)!)
                print("VACUUM error: \(errmsg)")
            }

            if sqlite3_close(db) != SQLITE_OK {
                print("error closing database")
            }

            db = nil
        }
    }
}

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!

See recent sponsors Learn more about Hashnode Sponsors
 
Share this