D 的个人博客

全职做开源,自由职业者

  menu

GAE 配额优化

自从 GAE 毕业后,新的计费模型开始了实行。

新版的计费模型主要瓶颈在于数据库操作次数,特别是读操作(Datastore Read Operations)以及“密集”操作(Datastore Small Operations)。

配额

在官方文档 Limits 中对于数据操作的描述如下:

High-Level OperationLow-Level Operations Required
Entity Get (per entity) 1 Read
New Entity Put (per entity, regardless of entity size) 2 Writes + 2 Writes per indexed property value + 1 Write per composite index value
Existing Entity Put (per entity) 1 Write + 4 Writes per modified indexed property value + 2 Writes per modified composite index value
Entity Delete (per entity) 2 Writes + 2 Writes per indexed property value + 1 Write per composite index value
Query 1 Read + 1 Read per entity returned
Query (keys only) 1 Read + 1 Small per entity returned
Key allocation (per key) 1 Small

* Hight-Level 对应的其实是 Low-level APIs 调用

目前的免费配额情况:

Datastore Write Operations 0.05 Million Ops
Datastore Read Operations 0.05 Million Ops
Datastore Small Operations 0.05 Million Ops

从中我们可以得知,正真的瓶颈是 Read 操作,因为一天只能有 5 万次调用。

特别是条件查询,假设返回结果集大小为 N,则实际是进行了 1 Read + N 次 Read 操作(1 Read + N per entity returned)。

读操作

例如 B3log Solo 首页渲染,文章列表部分用的是一个条件查询:

final Query query = new Query().setCurrentPageNum(currentPageNum).
                    setPageSize(pageSize).
                    addFilter(Article.ARTICLE_IS_PUBLISHED,
                              FilterOperator.EQUAL, PUBLISHED).
                    addSort(Article.ARTICLE_PUT_TOP, SortDirection.DESCENDING);

查询页号为 currentPageNum 的、页最多显示 pageSize 条记录、文章必须是已发布的、按置顶排序的文章列表。

假设满足条件的文章为 15 篇,则这次查询一共调用 16 次 Read 操作。

分页

分页需求是大多数应用的基础需求之一,分页所必须的参数是:

  • 页大小
  • 总记录数

然后我们可以计算出这两个参数计算出总页数(pageCount = ceil(sum/pageSize)),以便进行分页控制。

其中总记录数就是符合查询条件的结果集大小。GAE 上我们可以使用 count API

但是,count API 有个严重损耗配额的问题,count 执行相当于使用 keys 进行查询遍历。

假设满足查询条件的总记录数为 N,那么一次 count 调用相当于执行了 1 Read + N Small 次操作。

而通常情况下,N 会比较大。比如前面文章列表查询的例子中,如果我有 1,000 篇发布了的文章,那么一次 count 调用相当于使用了 1 Read + 1000 Small 操作配额。

也就是说,在不考虑缓存的情况下,访问有 1,000 篇文章博客的首页至少需要花费 2% 的 Small 配额,50 次请求今天的免费配额就玩完了。

优化策略

《GAE Java 应用性能优化》 一文中提到了缓存 HTML 页面以及缓存数据查询结果,这是比较通用的做法。而应用本身也需要考虑优化设计:

维护条件查询的总记录计数

还是以前面文章列表查询的例子来看,如果应用维护了已发布文章的总数,那也就不必调用 count 接口了,而只需要查询这个总数(1 Read)。

隐式分页

也可以换个思路,对于查询量大的地方使用隐式分页,也就是不计算完整的分页结果,而只是提供“下一页”/“上一页”的请求入口。

结论

新的计费模型让一些没有足够优化的 GAE 应用瞬间倒下,即使是在实现上进行了足够优化的应用,也必须在功能上进行一定的精简,否则要想完全使用免费配额也是不可能的。

在应用设计之初就应该考虑尽量少的依赖数据 API(例如 count),能在应用中维护的数据就应该维护住,虽然这可能比较繁琐,但这也为将来的优化提供了数据支撑。

另外,我觉得我们也应该为使用 GAE 而付费 ;-)