Search code examples
node.jsmongodbmongoosemongoose-schema

Mongoose Slice Not Limiting Array Size


I'm working on an application and am having an issue with Mongoose limiting array size on update. I'm not too familiar with MongoDB or Mongoose but I'm hoping this project will grow my knowledge. I tried a few other solutions I queried on SOF but none appeared to work for me. Here is my breakdown of the problem...

Library: Mongoose 5.7.7

Summary: In a document of logs, I wish to keep only the 10 latest logs with the older being pushed off the array.

Issue: With the current query I have, the slice appears not to be limiting the number of documents. The array just continues to grow.

ActivityLog.updateOne(
  { guild },
  {
    $push: {
      logs: {
        $each: [{ ...log }]
      },
      $slice: -10
    }
  },
  { upsert: true }
);

Solution

  • This might actually be more of a problem in your implementation. Here's the basic thing in practical usage to show that it does indeed work:

    const { Schema } = mongoose = require('mongoose');
    
    const uri = 'mongodb://localhost:27017/test';
    const options = { useNewUrlParser: true, useUnifiedTopology: true };
    
    mongoose.set("debug", true);
    mongoose.set("useFindAndModify", false);
    mongoose.set("useCreateIndex", true);
    
    const demoSchema = new Schema({
      _id: Number,
      fifo: [Number]
    },{ _id: false });
    
    const Demo = mongoose.model('Demo', demoSchema, 'demo');
    
    const log = data => console.log(JSON.stringify(data, undefined, 2));
    
    (async function() {
    
      try {
    
        const conn = await mongoose.connect(uri, options);
    
        await Promise.all(
          Object.values(conn.models).map(m => m.deleteMany())
        );
    
        let counter = 0;
    
        await new Promise((resolve, reject) =>
          setInterval(async () => {
            try {
              let result = await Demo.findByIdAndUpdate(
                1,
                { "$push": { "fifo": { "$each": [counter], "$slice": -3 } } },
                { upsert: true, new: true }
              );
    
              log(result);
            } catch(e) {
              reject(e)
            }
            counter++;
          }, 2000)
        );
    
    
      } catch (e) {
        console.error(e);
      } finally {
        mongoose.disconnect();
      }
    
    })()
    

    On a few iterations ( and using -3 for brevity here ) you would see:

    Mongoose: demo.deleteMany({}, {})
    Mongoose: demo.findOneAndUpdate({ _id: 1 }, { '$setOnInsert': { __v: 0 }, '$push': { fifo: { '$each': [ 0 ], '$slice': -3 } }}, { upsert: true, remove: false, projection: {}, returnOriginal: false })
    {
      "fifo": [
        0
      ],
      "_id": 1,
      "__v": 0
    }
    Mongoose: demo.findOneAndUpdate({ _id: 1 }, { '$setOnInsert': { __v: 0 }, '$push': { fifo: { '$each': [ 1 ], '$slice': -3 } }}, { upsert: true, remove: false, projection: {}, returnOriginal: false })
    {
      "fifo": [
        0,
        1
      ],
      "_id": 1,
      "__v": 0
    }
    Mongoose: demo.findOneAndUpdate({ _id: 1 }, { '$setOnInsert': { __v: 0 }, '$push': { fifo: { '$each': [ 2 ], '$slice': -3 } }}, { upsert: true, remove: false, projection: {}, returnOriginal: false })
    {
      "fifo": [
        0,
        1,
        2
      ],
      "_id": 1,
      "__v": 0
    }
    Mongoose: demo.findOneAndUpdate({ _id: 1 }, { '$setOnInsert': { __v: 0 }, '$push': { fifo: { '$each': [ 3 ], '$slice': -3 } }}, { upsert: true, remove: false, projection: {}, returnOriginal: false })
    {
      "fifo": [
        1,
        2,
        3
      ],
      "_id": 1,
      "__v": 0
    }
    Mongoose: demo.findOneAndUpdate({ _id: 1 }, { '$setOnInsert': { __v: 0 }, '$push': { fifo: { '$each': [ 4 ], '$slice': -3 } }}, { upsert: true, remove: false, projection: {}, returnOriginal: false })
    {
      "fifo": [
        2,
        3,
        4
      ],
      "_id": 1,
      "__v": 0
    }
    Mongoose: demo.findOneAndUpdate({ _id: 1 }, { '$setOnInsert': { __v: 0 }, '$push': { fifo: { '$each': [ 5 ], '$slice': -3 } }}, { upsert: true, remove: false, projection: {}, returnOriginal: false })
    {
      "fifo": [
        3,
        4,
        5
      ],
      "_id": 1,
      "__v": 0
    }
    Mongoose: demo.findOneAndUpdate({ _id: 1 }, { '$setOnInsert': { __v: 0 }, '$push': { fifo: { '$each': [ 6 ], '$slice': -3 } }}, { upsert: true, remove: false, projection: {}, returnOriginal: false })
    {
      "fifo": [
        4,
        5,
        6
      ],
      "_id": 1,
      "__v": 0
    }
    

    So this does indeed keep an array of the specified $slice length and from the end of the array as due to the negative, both growing the array to the set size and then removing all but the last added members.