调用此POST方法时,我们仍然会遇到重复条目的罕见情况。
我之前曾询问有关堆栈溢出的建议并获得了solution,它使用parent/child
方法来保留强一致性查询。
我已将所有数据迁移到该表单中,并让它再运行3个月。 但问题从未解决过。
这个条件if recordsdb.count() == 1:
问题就在这里
为了更新条目应该是真的,但是HRD可能并不总是找到最新的条目而是创建一个新的条目。
正如您所看到的,我们按照建议通过父/子方法编写/阅读记录:
new_record = FeelTrackerRecord(parent=user.key,...)
然而,在检索时,人力资源开发司仍然不会总是获取最新的条目:
recordsdb = FeelTrackerRecord.query(ancestor = user.key).filter(FeelTrackerRecord.record_date == ... )
所以我们非常坚持这一点,并且不知道如何解决它。
@requires_auth
def post(self, ios_sync_timestamp):
user = User.query(User.email == request.authorization.username).fetch(1)[0]
if user:
json_records = request.json['records']
for json_record in json_records:
recordsdb = FeelTrackerRecord.query(ancestor = user.key).filter(FeelTrackerRecord.record_date == date_parser.parse(json_record['record_date']))
if recordsdb.count() == 1:
rec = recordsdb.fetch(1)[0]
if 'timestamp' in json_record:
if rec.timestamp < json_record['timestamp']:
rec.rating = json_record['rating']
rec.notes = json_record['notes']
rec.timestamp = json_record['timestamp']
rec.is_deleted = json_record['is_deleted']
rec.put()
elif recordsdb.count() == 0:
new_record = FeelTrackerRecord(parent=user.key,
user=user.key,
record_date = date_parser.parse(json_record['record_date']),
rating = json_record['rating'],
notes = json_record['notes'],
timestamp = json_record['timestamp'])
new_record.put()
else:
raise Exception('Got more than two records for the same record date - among REST post')
user.last_sync_timestamp = create_timestamp(datetime.datetime.today())
user.put()
return '', 201
else:
return '', 401
可能的解决方案:
我必须解决这个问题的最后一个想法是,摒弃父/子策略并使用user.key
加号date-string
作为密钥的一部分。
存储
new_record = FeelTrackerRecord(id=str(user.key) + json_record['record_date'], ...)
new_record.put()
装载
key = ndb.Key(FeelTrackerRecord, str(user.key) + json_record['record_date'])
record = key.get();
现在我可以检查记录是否为None,我将创建一个新条目,否则我将更新它。希望HRD没有理由不再找到记录了。 您怎么看?这是一个有保障的解决方案吗?
答案 0 :(得分:2)
可能的解决方案似乎与原始代码具有相同的问题。想象一下,如果两台服务器几乎同时执行相同的指令,则会出现竞争情况。由于谷歌过度配置,这肯定会偶尔发生。
更强大的解决方案应该使用Transactions并在并发导致一致性违规时进行回滚。用户实体应该是其自己的实体组的父级。增加事务中User实体中的记录计数器字段。仅在事务成功完成时才创建新的FeelTrackerRecord。因此,FeelTrackerRecord实体必须具有User作为父级。
修改:对于您的代码,以下行将在user = User.query(...:
之前执行Transaction txn = datastore.beginTransaction();
try {
以下行将在user.put()之后:
txn.commit();
} finally {
if (txn.isActive()) {
txn.rollback();
}
}
这可能会忽略一些流控制嵌套细节,这是这个答案试图描述的概念。
使用活动事务,如果有多个进程(例如,由于过度配置而在多个服务器上同时执行同一个POST),第一个进程将通过其put和commit成功,而第二个进程将抛出记录的ConcurrentModificationException。
编辑2 :递增计数器的事务(并且可能抛出异常)也必须创建新记录。这样,如果抛出异常,则不会创建新记录。