如何确保与非祖先查询隔离

时间:2018-03-12 04:57:43

标签: google-app-engine google-cloud-datastore app-engine-ndb

我想使用ndb创建用户,如下所示:

def create_user(self, google_id, ....):
  user_keys = UserInformation.query(UserInformation.google_id == google_id ).fetch(keys_only=True)

  if user_keys: # check whether user exist.
    # already created
    ...(SNIP)...
  else:
    # create new user entity.
    UserInformation(
      # primary key is incompletekey
      google_id = google_id,
      facebook_id = None,
      twitter_id = None,
      name = 
      ...(SNIP)...
    ).put()

如果在同一时间调用此函数两次,则会创建两个用户。("隔离"不确保在get()和put()之间)

所以,我在上面的函数中添加了@ ndb.transactional。 但是发生了以下错误。

  

BadRequestError:在事务中只允许祖先查询。

如何确保与非祖先查询隔离?

2 个答案:

答案 0 :(得分:2)

ndb库不允许在事务内部进行非祖先查询。因此,如果您进行create_user()事务处理,则会出现上述错误,因为您在其中调用了UserInformation.query()(没有祖先)。

如果你真的想要这样做,你必须通过指定一个共同的祖先将所有UserInformation个实体放在同一个实体组中,并使你的查询成为一个祖先。但这有性能影响,请参阅Ancestor relation in datastore

否则,即使您将函数拆分为2,一个非事务性使查询后跟一个只创建用户的事务性 - 这将避免错误 - 您仍将面临数据存储最终一致性,这是实际上是您的问题的根本原因:查询的结果可能不会立即返回最近添加的实体,因为更新对应于查询的索引需要一些时间。这导致为同一用户创建重复实体的空间。见Balancing Strong and Eventual Consistency with Google Cloud Datastore

一种可能的方法是稍后/周期性地检查是否存在重复并删除它们(最终将信息合并到单个实体中)。和/或将用户创建标记为“进行中”,记录新创建的实体的密钥并继续查询,直到密钥出现在查询结果中,最后将实体创建标记为“已完成”(您可能没有时间在同一个请求中这样做。)

另一种方法是(如果可能的话)确定算法以基于用户信息获得(唯一)密钥,并且仅检查具有这种密钥的实体是否存在而不是进行查询。密钥查找非常一致,可以在事务内部完成,这样就可以解决重复问题。例如,您可以使用google_id作为密钥ID。举个例子,因为这也不理想:您可能没有google_id的用户,用户可能想要更改他们的google_id而不会丢失其他信息等。也许还会跟踪正在进行的用户创建会话信息,以防止重复尝试在同一会话中创建相同的用户(但这不会有助于不同会话的尝试)。

答案 1 :(得分:0)

对于您的用例,也许您可​​以使用ndb模型的>>> l = [1, 2, 3] >>> '{%s}' % str(l).strip('[]') >>> {1, 2, 3} 方法,根据API docs

  

以交易方式检索现有实体或创建新实体。

所以你可以这样做:

get_or_insert

不会冒新用户的风险。

完整的文档:

  

classmethod get_or_insert(* args,** kwds)来源交易   检索现有实体或创建新实体。

     

Positional Args:name:要检索或创建的密钥名称。

     

关键字参数

     

namespace - 可选命名空间。 app - 可选的应用ID。

     

parent - 父实体密钥(如果有)。

     

context_options - ContextOptions对象(不是关键字args!)或无。

     

** kwds - 如果指定键名的实例尚未传递给模型类的构造函数的关键字参数   存在。如果具有提供的key_name和parent的实例已经存在   存在,这些参数将被丢弃。返回现有实例   具有指定键名和父键或新键的Model类   刚刚创建的。