I want to track changes to documents in a collection in Firestore. I use a lastModified
property to filter results. The reason I use this “lastModified” filter is so that each time the app starts the initial snapshot in the listener does not return all documents in the collection.
// The app is only interested in changes occurring after a certain date
let date: Date = readDateFromDatabase()
// When the app starts, begin listening for city updates.
listener = db.collection("cities")
.whereField("lastModified", isGreaterThanOrEqualTo: date)
.addSnapshotListener() { (snapshot, error)
// Process added, modified, and removed documents.
// Keep a record of the last modified date of the updates.
// Store an updated last modified date in the database using
// the oldest last modified date of the documents in the
// snapshot.
writeDateToDatabase()
}
Each time documents are processed in the closure, a new “lastModified” value is stored in the database. The next time the app starts, the snapshot listener is created with a query using this new “lastModified” value.
When a new city
is created, or one is updated, its “lastModified” property is updated to “now”. Since “now” should be greater than or equal to the filter date, all updates will be sent to the client.
However, if a really old city is deleted, then its “lastModified” property may be older than the filter date of a client that has received recent updates. The problem is that the deleted city’s “lastModified” property cannot be updated to “now” when it is being deleted.
Example
- Client 1 listens for updates ≥ d_1.
- Client 2 creates two cities at d_2, where d_1 < d_2.
- Client 1 receives both updates because d_1 < d_2.
- Client 1 stores d_2 as a future filter.
- Client 2 updates city 1 at d_3, where d_2 < d_3.
- Client 1 receives this update because d_1 < d_3.
- Client 1 stores d_3 as a future filter.
- ...Some time has passed.
- Client 1 app starts and listens for updates ≥ d_3.
- Client 2 deletes city 2 (created at d_2).
- Client 1 won’t receive this update because d_2 < d_3.
My best solution
Don’t delete cities, instead add an isDeleted
property. Then, when a city is marked as deleted, its “lastModified” property is updated to “now”. This update should be sent to all clients because the query filter date will always be before “now”. The main business logic of the app ignores cities where isDeleted
is true.
I feel like I don’t fully understand this problem. Is there a better way to solve my problem?
CodePudding user response:
The solution you've created is quite common and is known as a tombstone.
Since you no longer need the actual data of the document, you can delete its fields. But the document itself will need to remain to indicate that it's been deleted.
There may be other approaches, but they'll all end up similarly. As you have to somehow signal to each client (no matter when they connect/query) that the document is gone, keeping the document as a tombstone seems like a simple and good approach to me.