Uploaded image for project: 'Core Server'
  1. Core Server
  2. SERVER-102745

findAndModify on a time-series collection can fail when the modified measurement would be moved to a different chunk

    • Type: Icon: Bug Bug
    • Resolution: Unresolved
    • Priority: Icon: Major - P3 Major - P3
    • None
    • Affects Version/s: None
    • Component/s: None
    • None
    • Storage Execution
    • 0
    • None
    • 3
    • None
    • None
    • None
    • None
    • None
    • None

      When we perform a findAndModify (or any other user-initiated update) on a time-series collection, we move the matched measurement into a new bucket. If the time-series collection is sharded on time, and the rounded-down timestamp of the matched measurement belongs to a different chunk than the original bucket, the update will error with the following message: "Shard key update is not allowed without specifying the full shard key in the query". I believe that in the normal write path this would be handled and retried by the router.

      Note: findAndModify, and other non-metadata updates on time-series projects, are currently behind the feature flag for allowing updates on time-series projects

      Simple repro:

      import {ShardingTest} from "jstests/libs/shardingtest.js";
      
      const st = new ShardingTest({shards: 2, rs: {nodes: 1}});
      const donor = st.shard0;
      assert.commandWorked(
          st.s.adminCommand({enableSharding: jsTestName(), primaryShard: donor.shardName}));
      const db = st.s.getDB(jsTestName());
      
      assert.commandWorked(db.createCollection("ts", {timeseries: {timeField: "ts", metaField: "meta"}}));
      assert.commandWorked(db.ts.createIndex({"ts": 1}));
      
      assert.commandWorked(db.ts.insert({ts: new ISODate("2024-09-13T09:59:12.00Z"), x: 1}));
      assert.commandWorked(db.ts.insert({ts: new ISODate("2024-09-13T10:01:12.00Z"), x: 2}));
      assert.eq(db.system.buckets.ts.find().itcount(), 1);
      assert.eq(db.ts.find().itcount(), 2);
      
      assert.commandWorked(db.adminCommand({shardCollection: db.getName() + ".ts", key: {"ts": 1}}));
      // Split up chunks around this time. One of the measurements in the bucket is on the left of this
      // boundary, the other is on the right. Since the control.min.ts field is the one that determines
      // which chunk the bucket belongs to, our bucket should be in the first chunk.
      assert.commandWorked(db.adminCommand({
          split: db.getName() + ".system.buckets.ts",
          middle: {"control.min.ts": ISODate("2024-09-13T10:00:00.00Z")}
      }));
      assert.commandWorked(db.adminCommand({
          moveChunk: db.getName() + ".system.buckets.ts",
          find: {"control.min.ts": ISODate("2024-09-13T09:00:00.00Z")},
          to: st.shard0.shardName
      }));
      assert.commandWorked(db.adminCommand({
          moveChunk: db.getName() + ".system.buckets.ts",
          find: {"control.min.ts": ISODate("2024-09-13T11:00:00.00Z")},
          to: st.shard1.shardName
      }));
      
      // This update should fail with the expected error message
      assert.commandWorked(db.runCommand(
          {findAndModify: "ts", query: {x: 2}, new: true, update: {$inc: {updateCount: 1}}}));
      
      
      

       

       

            Assignee:
            Unassigned Unassigned
            Reporter:
            damian.wasilewicz@mongodb.com Damian Wasilewicz
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: