2

I'm trying to set up a model so that when a table get added, it will have an expiration date, after it expires, the table will delete itself. I tried implementing it like this:

expires: '1m'

and with

expires: 10

I have a table set up like the following:

const verifySchema = new mongoose.Schema({
    _userId: {
        type: mongoose.Schema.Types.ObjectId,
        required: true,
        ref: 'User'
    },
    hash: { type: String, required: true },
    createdAt: { type: Date, required: true, default: Date.now, expires: '1m' }
});

The problem is, nothing happens after the minute. It doesn't get deleted in the database. Am I doing anything wrong?

How can I delete a table after a minute?

t.888
  • 3,862
  • 3
  • 25
  • 31
Jessica
  • 9,379
  • 14
  • 65
  • 136
  • I'm not sure the `1m` shorthand works. Try setting 1 minute in seconds as `expires: 60`. – t.888 May 19 '19 at 22:35
  • Also look at this question/answer: https://stackoverflow.com/q/14597241/1861779 – t.888 May 19 '19 at 22:37
  • 1
    Note: you may need to drop the index before changing the `expires` attribute: https://docs.mongodb.com/manual/core/index-ttl/#restrictions – t.888 May 19 '19 at 22:55
  • Remove your index and re-create it again. Code looks good. `expires: '1m'` works but remember that the TTL process runs once every 60 seconds so it is not perfectly on time always. I just tested it and it works as expected with `2m` etc. – Akrion May 20 '19 at 00:20
  • @t.888 Can you please explain what you mean by dropping the index? Also, I didn't change the expire attribute. It's whatever the default value is, and I never change it. Am I supposed to change it? – Jessica May 20 '19 at 02:06
  • @Steve, setting `expires` creates a TTL index on the collection defined by your schema; see here: https://docs.mongodb.com/manual/core/index-ttl/. You can try deleting that index using the MongoDB shell or MongoDB Compass: https://www.mongodb.com/products/compass. It's worth trying to delete that index and then restarting your application so that mongoose will recreate the index with the desired TTL (`expires: 60`). – t.888 May 20 '19 at 04:52
  • @t.888 I tried deleting it and restarting mongod. The new ones still don't get removed. I waiting a decent amount of time. Do you mind posting a working example – Jessica May 20 '19 at 05:09
  • And sorry for the horrible Grammer... On the mobile version. Lol – Jessica May 20 '19 at 05:25

1 Answers1

4

Here's a working sample using mongoose v5.5.9. It turns out the missing part is the schema entry index: { expires: '1m' } in the createdAt field.

const mongoose = require('mongoose')
// $ npm install uuid
const uuid = require('uuid')

const ObjectId = mongoose.Types.ObjectId

// Avoid deprecation warnings
mongoose.set('useNewUrlParser', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

// Create the schema.
const verifySchema = new mongoose.Schema({
  _userId: {
      type: ObjectId,
      required: true,
      ref: 'User'
  },

  hash: { type: String, required: true },

  createdAt: {
    type: Date,
    required: true,
    default: Date.now,
    index: { expires: '1m' }
  }
},
{
  collection: 'verify'
});

// Connect to mongodb.
mongoose.connect('mongodb://localhost/test').then(() => {
  // Create the model
  const Verify = mongoose.model('Verify', verifySchema)

  // Create a model instance.
  const v = new Verify({
    _userId: new ObjectId(),
    hash: uuid.v4()
  })

  // Save the model.
  v.save().then(() => {
    // Close the connection.
    mongoose.connection.close()
  })
})

You can check your indexes with MongoDB Compass, or using the shell:

> use test
> db.verify.getIndexes()

Look for the field value expireAfterSeconds which will indicate the TTL time in seconds that is set for the index. In order to change the TTL, you will need to drop the index on createdAt. In the shell, the command would be db.verify.dropIndex(<index_name>) or db.verify.dropIndexes() to drop all indexes on the collection.

For upserting documents, such as with findOneAndUpdate, you will need to pass setDefaultsOnInsert: true to the options like so:

// Connect to mongodb.
mongoose.connect('mongodb://localhost/test').then(() => {
  // Create the model
  const Verify = mongoose.model('Verify', verifySchema)

  const _userId = new ObjectId()
  const hash = uuid.v4()
  const options = { upsert: true, setDefaultsOnInsert: true }

  // Upsert the document.
  Verify.findOneAndUpdate( { _userId }, { hash }, options).then(() => {
    mongoose.connection.close()
  })
})

This is necessary or else the createdAt field, which contains the TTL index, won't get added to the document.

t.888
  • 3,862
  • 3
  • 25
  • 31
  • When I do this it doesn't remove itself after the expiration date. `const verify = await Verify.findOneAndUpdate( { _userId: user._id }, { hash: uuidv4() }, { upsert: true, new: true } );` – Jessica May 21 '19 at 00:24
  • @Steve The problem appears to be that the `createdAt` field doesn't get set by default with that command, so the TTL index isn't on the new document. Add `setDefaultsOnInsert: true` to the options and it should work: https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate – t.888 May 21 '19 at 14:47
  • I've updated the answer to contain an explanation for `findOneAndUpdate` behavior. – t.888 May 21 '19 at 14:57
  • Thank you very much @t.888 for your help!!!!! Really appreciate it, and definitely learned a lot!! – Jessica May 21 '19 at 15:04
  • 1
    My pleasure, @Steve, glad I could help! – t.888 May 21 '19 at 15:10
  • 1
    `setDefaultsOnInsert: true` for upserting was what I was missing, thanks – dsax7 Dec 22 '20 at 14:24