Google Cloud Datastore Transaction 使用注意事项

摘要:Datastore 作为 NoSQL,虽然支持 Transaction,但是在使用中需要有些注意。本文记录了使用 Objectify 来使用 DatastoreTransaction 功能 的注意事项。

什么是事务 Transaction

A transaction is an operation or set of operations that is atomic—either all of the operations in the transaction occur, or none of them occur. An application can perform multiple operations and calculations in a single transaction.

事务就是一个或一组原子性操作,这组操作要不就全部都执行,要不就都不执行,不存在部分执行的情况。

一般事务包含有四种特性,简称为 ACID 特性:

  1. 原子性Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  2. 一致性Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  3. 隔离性Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  4. 持久性Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

Datastore 事务概述

Datastore 支持事务操作,同时需要了解其限制为最长 60s 以及在 30s 后,如果有 10s 的闲置则就会过期。

Transactions have a maximum duration of 60 seconds with a 10 second idle expiration time after 30 seconds.

另外还有某些情况下会失败

An operation might fail when:

  1. Too many concurrent modifications are attempted on the same entity group.
  2. The transaction exceeds a resource limit.
  3. Cloud Datastore encounters an internal error.

Entity Group

Datastore 中的 Entity Group 是和事务息息相关的一个概念,因为这个限制了在事务中允许的操作。

我们把 Entity 成为实体,也就是在 Datastore 的数据项,形象的表示就是表示一种数据的一张数据表,比如 用户实体,Entity 就是 User,类似于 关系型数据库的一张表或者是数据库 E-R 图中的 E (Entity)类似。

所谓 Entity Group 就是, 实体组。因为 NoSQL 没有关系型数据库中的 R,所以 Datastore 使用了一个 Parent 来将实体组织成一种父子的层级关系。实体组就是 一组实体,他们线性依赖的一组实体 A->B->C…。

Datastore 事务关于 Entity Group 的限制

All the data accessed by a transaction must be contained in at most 25 entity groups.

If you want to use queries within a transaction, your data must be organized into entity groups in such a way that you can specify ancestor filters that will match the right data.

There is a write throughput limit of about one transaction per second within a single entity group. This limitation exists because Cloud Datastore performs masterless, synchronous replication of each entity group over a wide geographic area to provide high reliability and fault tolerance.

为什么会有这些限制呢? 我们思考一下最为 Google 的轻量级NoSQL的数据存储实际上支持强一致性的无主节点的完全分布式是一个非常吸引人的特性。要知道对于分布式数据库的强一致性不是非常容易实现的,而 Datastore 支持这种强一致性,必然会带来限制。我们可以先了解一下 Google Datastore 对于 Entity Group 是如何存储的:

Entity group relationships tell App Engine to store several entities in the same part of the distributed network.

也就是说这样一组实体会被存储在相同的分布式网络。这也是强一致性的基础,因为强一致性需要进行大量的同步操作(比如 Paxos一致性协议),从带来带宽和网络的负载。所以 Google 不可能允许无限制的进行强一致性的事务操作,而是限制了25个组,这样应该有效的减少跨区域网络的带宽压力。至于为什么是25个,应该是 Google 根据现有的能力而定义的一个阈值。

如何处理并发冲突

When a transaction starts, Google Cloud Datastore uses optimistic concurrency control by checking the last update time for the entity groups used in the transaction. Upon commiting a transaction for the entity groups, Google Cloud Datastore again checks he last update time for the entity groups used in the transaction. If it has hanged since the initial check, an exception is thrown .

我们简单介绍一下上面 Optimistic Concurrency Control (OCC)的逻辑

  1. Begin:首先记录事务开始的时间戳
  2. Modify:开始执行事务操作,包括读取数据库值以及修改变更
  3. Validate:最后进行校验,校验的内容包括其他事务操作进行了修改,同时本事务也使用了的数据。范围包含在本次事务开始后完成的事务。
  4. Commit/Rollback:如果有冲突或者中间有失败则需要解决,一般就是回滚重试。没有冲突就需要进行提交完成所有操作。

使用场景

保证所有操作全部执行否则都不执行

比如我们创建 User 的同时为 User 创建一个 Setting,如果 Setting 创建失败,则 User 也不能创建,需要整个过程重试。

这时候两个创建的操作就应该在一个事务中。

保证操作整个过程中涉及的数据不会变更

比如我们在用户注册的时候,需要验证用户名是否在系统中唯一,如果不唯一需要提示错误,但是如果两个用户几乎同时注册,前一个注册验证成功,但是还没有最后在数据库写入数据的过程中,另一个用户也开始用相同用户名注册,这时候也会验证,因为上一个还没写入数据,所以也会验证成功。这时候实际上就出现冲突。

我们需要将注册的流程的查询操作和创建操作放在一个事务中,这样在后一个用户注册虽然验证成功了,但是在创建用户后开始提交事务,将会发现用户数据库表发生了变化,这样也就影响了验证操作的结果,所以事务操作将会失败,需要重试。(这里按常理假设第一个注册的用户的流程在第二个注册流程过程已经完成提交事务,当然如何反过来同样,只不过变成第一个事务失败)

使用 Objectify 进行事务操作

大部分人在进行 Datastore 的操作中一般都会选用 Objectify。该库提供了更加友好的使用 Transaction 的接口,比如可以使用 Guice 通过注解来实现方法的事务化。
我们这里首先列出若干使用的问题

不要在事务中进行无 ancestor 的查询

这个是 Datastore 的要求,如果希望在事务中进行查询,必须带有 ancestor

如果进行非事务的查询,可以使用transactionless

这个方法提供不少方便,但是需要自行确定这个查询操作不会受到其他事务的影响,因为这个操作不会被事务进行检查。比如我们上面提到的那个注册的例子就不能用这个方法。

事务中进行查询需要查询内容的创建时间

如果数据是在事务过程中创建的,是无法通过普通的查询语句查出来的,因为在事物的过程中,实际数据并没有真正的写入数据库。所有如果需要使用在事务过程中创建的数据需要在程序中记录创建后的 Key,然后直接通过 Key 来获取数据本身。