Good Night, Westley: Time-To-Live Collections
This is a cross posting from the blog of MongoDB developer Kristina Chodorow. While TTL collections won’t be available until version 2.2, you can test them out yourself in the latest development (unstable) release of MongoDB, version 2.1.2.
In The Princess Bride, every night the Dread Pirate Roberts tells Westley: “Good night, Westley. Good work. Sleep well. I’ll most likely kill you in the morning.” Let’s say the Dread Pirate Roberts wants to optimize this process, so he stores prisoners in a database. When he captures Westley, he can put:
> db.prisoners.insert({ ... name: "Westley", ... sentenceStart: new Date() ... })
However, now he has to run some sort of cron job that runs all the time in order to kill everyone who needs killing and keep his database up-to-date. Enter time-to-live (TTL) collections. TTL collections are going to be released in MongoDB 2.2 and they’re collections where documents expire in a more controlled way than with capped collections. What the Dread Pirate Roberts can do is:
> db.prisoners.ensureIndex({sentenceStart: 1}, {expireAfterSeconds: 24*60*60}
Now, MongoDB will regularly comb this index looking for docs to expire (so it’s actually more of a TTL index than a TTL collection). Let’s try it out ourselves. You’ll need to download the latest Development Release (Unstable) to use this feature. Start up the mongod and run the following in the Mongo shell:
> db.prisoners.ensureIndex({sentenceStart: 1}, {expireAfterSeconds: 30})
We’re on a schedule here, so our pirate ship is more brutal: death after 30 seconds. Let’s take aboard a prisoner and watch him die.
> var start = new Date() > db.prisoners.insert({name: "Haggard Richard", sentenceStart: start}) > while (true) { ... var count = db.prisoners.count(); ... print("# of prisoners: " + count + " (" + (new Date() - start) + "ms)"); ... if (count == 0) ... break; ... sleep(4000); } # of prisoners: 1 (2020ms) # of prisoners: 1 (6021ms) # of prisoners: 1 (10022ms) # of prisoners: 1 (14022ms) # of prisoners: 1 (18023ms) # of prisoners: 1 (22024ms) # of prisoners: 1 (26024ms) # of prisoners: 0 (30025ms)
…and he’s gone. Conversely, let’s say we want to play the “maybe I’ll kill you tomorrow” game and keep bumping Westley’s expiration date. We can do that by updating the TTL-indexed field:
> db.prisoners.insert({name: "Westley", sentenceStart: new Date()}) > for (i=0; i < 10; i++) { ... print("updating Westley's sentence (" + i + ")"); ... db.prisoners.update({name:"Westley"}, {$set:{sentenceStart:new Date}}); ... sleep(4000); ... } updating Westley's sentence (0) updating Westley's sentence (1) updating Westley's sentence (2) updating Westley's sentence (3) updating Westley's sentence (4) updating Westley's sentence (5) updating Westley's sentence (6) updating Westley's sentence (7) updating Westley's sentence (8) updating Westley's sentence (9) > db.prisoners.count() 1
…and Westley’s still there, even though it’s been more than 30 seconds. Once he gets promoted and becomes the Dread Pirate Roberts himself, he can remove himself from the execution rotation by changing his sentenceStartfield to a non-date (or removing it altogether):
> db.prisoners.update({name: "Westley"}, {$unset : {sentenceStart: 1}});
When not on pirate ships, developers generally use TTL collections for sessions and other cache expiration problems. If ye be wanting a less grim introduction to MongoDB’s TTL collections, there are some docs on it in the manual.