我有Event
个文档,包含嵌入式Snapshots
。
如果符合以下情况,我想向Snapshot
添加Event
A
否则....创建一个新的Event
。
以下是我的findAndUpdate
查询可能更有意义:
Event.findAndModify(
query: {
start_timestamp: { $gte: newSnapshot.timestamp - 5min },
last_snapshot_timestamp: { $gte: newSnapshot.timestamp - 1min }
},
update: {
snapshots[newSnapshot.timestamp]: newSnapshot,
$max: { last_snapshot_timestamp: newSnapshot.timestamp },
$min: { start_timestamp: newSnapshot.timestamp }
},
upsert: true,
$setOnInsert: { ALL OUR NEW EVENT FIELDS } }
)
编辑:不幸的是,我无法在start_timestamp上创建唯一索引。快照带有不同的时间戳,我想将它们分组到一个事件中。即快照A在12:00:00进入,快照B在12:00:59进入。它们应该在同一个事件中,但它们可以在不同的时间写入DB,因为编写它们的工作者同时执行。假设另一个快照进入,在12:00:30,应该写入与上述两个相同的事件。最后,应将12:02:00的快照写入新事件。
我的问题是......这将在并发环境中正常工作。 findAndUpdate
是原子的吗?是否有可能创建两个事件,我应该创建一个事件,并将快照添加到它?
编辑:所以上述方法不能保证不会创建两个事件,正如@chainh所指出的那样。
所以我尝试了一种新的基于锁定的方法 - 您认为这会有效吗?
var acquireLock = function() {
var query = { "locked": false}
var update = { $set: { "locked": true } }
return Lock.findAndModify({
query: query,
update: update,
upsert: true
})
};
var releaseLock = function() {
var query = { "locked": true }
var update = { $set: { "locked": false } }
return Lock.findAndModify({
query: query,
update: update
})
};
var insertSnapshot = function(newSnapshot, upsert) {
Event.findAndModify(
query: {
start_timestamp: { $gte: newSnapshot.timestamp - 5min },
last_snapshot_timestamp: { $gte: newSnapshot.timestamp - 1min }
},
update: {
snapshots[newSnapshot.timestamp]: newSnapshot,
$max: { last_snapshot_timestamp: newSnapshot.timestamp },
$min: { start_timestamp: newSnapshot.timestamp }
},
upsert: upsert,
$setOnInsert: { ALL OUR NEW EVENT FIELDS } }
)
};
var safelyInsertEvent = function(snapshot) {
return insertSnapshot(snapshot, false)
.then(function(modifyRes) {
if (!modifyRes.succeeded) {
return acquireLock()
}
})
.then(function(lockRes) {
if (lockRes.succeeded) {
return insertSnapshot(snapshot, true)
} else {
throw new AcquiringLockError("Didn't acquire lock. Try again")
}
})
.then(function() {
return releaseLock()
})
.catch(AcquiringLockError, function(err) {
return safelyInsertEvent(snapshot)
})
};
锁定文档只包含一个字段(已锁定)。基本上上面的代码试图找到一个现有的事件并更新它。如果它有效,那么我们可以拯救。如果我们没有更新,我们知道我们没有现成的事件来保留快照。因此我们以原子方式获取锁定,如果成功,我们可以安全地插入新事件。如果获取该锁失败,我们只需再次尝试整个过程,并希望到那时我们有一个现有的事件可以坚持下去。
答案 0 :(得分:1)
findAndModify
可能会在并发环境下发起多个事件。除非您的事件文档包含具有唯一索引的字段,否则只有一个findAndModify
成功插入新事件,而其他findAndModify
将失败并重试将快照添加到新事件。有关详细信息,请参阅此jira票证:https://jira.mongodb.org/browse/DOCS-861
答案 1 :(得分:1)
根据您的密码:
Event.findAndModify(
query: {
start_timestamp: { $gte: newSnapshot.timestamp - 5min },
last_snapshot_timestamp: { $gte: newSnapshot.timestamp - 1min }
},
update: {
snapshots[newSnapshot.timestamp]: newSnapshot,
$max: { last_snapshot_timestamp: newSnapshot.timestamp },
$min: { start_timestamp: newSnapshot.timestamp }
},
upsert: true,
$setOnInsert: { ALL OUR NEW EVENT FIELDS } }
)
当成功将第一个事件文档插入数据库时,此事件文档的字段具有以下关系:
start_timestamp == last_snapshot_timestamp
在后续更新后,关系变为:
start_timestamp< last_snapshot_timestamp< last_snapshot_timestamp + 1min< start_timestamp + 5min
OR
start_timestamp< last_snapshot_timestamp< start_timestamp + 5min< last_snapshot_timestamp + 1min
因此,如果新快照想要连续插入此事件文档,则必须符合:
newSnapshot.timestamp< Math.min(last_snapshot_timestamp + 1,start_timestamp + 5)
假设数据库中有两个事件文档随时间推移:
Event1(start_timestamp1,last_snapshot_timestamp1),
Event2(start_timestamp2,last_snapshot_timestamp2)
通常,start_timestamp2> last_snapshot_timestamp1
现在,如果有新的快照出现,其时间戳小于start_timestamp1 (只是假设可以通过延迟或伪造),然后可以插入此快照 进入任一事件文件。所以,我怀疑你是否需要添加其他条件 查询部分,以确保last_snapshot_timestamp和start_timestamp之间的距离始终小于某个值(例如5分钟)?例如,我将查询更改为
query: {
start_timestamp: { $gte: newSnapshot.timestamp - 5min },
last_snapshot_timestamp: { $gte: newSnapshot.timestamp - 1min , $lte : newSnapshot.timestamp + 5}
}
好的,让我们继续......
如果我尝试解决此问题,我仍尝试在字段 start_timestamp 上构建唯一索引。
根据MongoDB手册,使用 findAndModify 或更新即可完成工作
原子。但令人头疼的是,当出现重复值时我应该如何处理因为
newSnapshot.timestamp失控,它可能会修改 start_timestamp
运营商 $ min 。
方法是:
由于它不需要返回事件文档,我使用更新而不是 findAndModify ,因为两者都是原子操作 在这种情况下,更新的写作更简单 我使用简单的JavaScript(在mongo shell上运行)来表达步骤(我不熟悉您使用的代码语法。:D),我认为您可以轻松理解。
var gap5 = 5 * 60 * 1000; // just suppose, you should change accordingly if the value is not true.
var gap1 = 1 * 60 * 1000;
var initialFields = {}; // ALL OUR NEW EVENT FIELDS
function insertSnapshotIfStartTimeStampNotExisted() {
var query = {
start_timestamp: { $gte: newSnapshot.timestamp - gap5 },
last_snapshot_timestamp: { $gte: newSnapshot.timestamp - gap1 }
};
var update = {
$push : {snapshots: newSnapshot}, // suppose snapshots is an array
$max: { last_snapshot_timestamp: newSnapshot.timestamp },
$min: { start_timestamp: newSnapshot.timestamp },
$setOnInsert : initialFields
},
var result = db.Event.update(query, update, {upsert : true});
if (result.nUpserted == 0 && result.nModified == 0) {
insertSnapshotIfStartTimeStampExisted(); // Event document existed with that start_timestamp
}
}
function insertSnapshotIfStartTimeStampExisted() {
var query = {
start_timestamp: newSnapshot.timestamp,
};
var update = {
$push : {snapshots: newSnapshot}
},
var result = db.Event.update(query, update, {upsert : false});
if (result.nModified == 0) {
insertSnapshotIfStartTimeStampNotExisted(); // If start_timestamp just gets modified; it's possible.
}
}
// entry
db.Event.ensureIndex({start_timestamp:1},{unique:true});
insertSnapshotIfStartTimeStampNotExisted();