aboutsummaryrefslogtreecommitdiff
path: root/lib/message.cc
diff options
context:
space:
mode:
authorAustin Clements <amdragon@mit.edu>2014-10-13 02:20:01 -0400
committerDavid Bremner <david@tethera.net>2015-08-13 23:52:51 +0200
commit7f57b747b95eece465d10fd0acba20cc3dd868f1 (patch)
tree50532a98d16d159ac81f3f11b8ab14c63ac7a71b /lib/message.cc
parentc9e1c4f1c495d7cc24a64b73edb4b7b71791a87f (diff)
lib: Add per-message last modification tracking
This adds a new document value that stores the revision of the last modification to message metadata, where the revision number increases monotonically with each database commit. An alternative would be to store the wall-clock time of the last modification of each message. In principle this is simpler and has the advantage that any process can determine the current timestamp without support from libnotmuch. However, even assuming a computer's clock never goes backward and ignoring clock skew in networked environments, this has a fatal flaw. Xapian uses (optimistic) snapshot isolation, which means reads can be concurrent with writes. Given this, consider the following time line with a write and two read transactions: write |-X-A--------------| read 1 |---B---| read 2 |---| The write transaction modifies message X and records the wall-clock time of the modification at A. The writer hangs around for a while and later commits its change. Read 1 is concurrent with the write, so it doesn't see the change to X. It does some query and records the wall-clock time of its results at B. Transaction read 2 later starts after the write commits and queries for changes since wall-clock time B (say the reads are performing an incremental backup). Even though read 1 could not see the change to X, read 2 is told (correctly) that X has not changed since B, the time of the last read. In fact, X changed before wall-clock time A, but the change was not visible until *after* wall-clock time B, so read 2 misses the change to X. This is tricky to solve in full-blown snapshot isolation, but because Xapian serializes writes, we can use a simple, monotonically increasing database revision number. Furthermore, maintaining this revision number requires no more IO than a wall-clock time solution because Xapian already maintains statistics on the upper (and lower) bound of each value stream.
Diffstat (limited to 'lib/message.cc')
-rw-r--r--lib/message.cc22
1 files changed, 22 insertions, 0 deletions
diff --git a/lib/message.cc b/lib/message.cc
index 1ddce3c6..26b5e76e 100644
--- a/lib/message.cc
+++ b/lib/message.cc
@@ -998,6 +998,16 @@ _notmuch_message_set_header_values (notmuch_message_t *message,
message->modified = TRUE;
}
+/* Upgrade a message to support NOTMUCH_FEATURE_LAST_MOD. The caller
+ * must call _notmuch_message_sync. */
+void
+_notmuch_message_upgrade_last_mod (notmuch_message_t *message)
+{
+ /* _notmuch_message_sync will update the last modification
+ * revision; we just have to ask it to. */
+ message->modified = TRUE;
+}
+
/* Synchronize changes made to message->doc out into the database. */
void
_notmuch_message_sync (notmuch_message_t *message)
@@ -1010,6 +1020,18 @@ _notmuch_message_sync (notmuch_message_t *message)
if (! message->modified)
return;
+ /* Update the last modification of this message. */
+ if (message->notmuch->features & NOTMUCH_FEATURE_LAST_MOD)
+ /* sortable_serialise gives a reasonably compact encoding,
+ * which directly translates to reduced IO when scanning the
+ * value stream. Since it's built for doubles, we only get 53
+ * effective bits, but that's still enough for the database to
+ * last a few centuries at 1 million revisions per second. */
+ message->doc.add_value (NOTMUCH_VALUE_LAST_MOD,
+ Xapian::sortable_serialise (
+ _notmuch_database_new_revision (
+ message->notmuch)));
+
db = static_cast <Xapian::WritableDatabase *> (message->notmuch->xapian_db);
db->replace_document (message->doc_id, message->doc);
message->modified = FALSE;