存档

‘GAE’ 分类的存档

GAE static 文件压缩

2009年3月14日 roboter 没有评论

《高性能网站建设指南》中的第四个原则就是需要压缩组件,为此考察了一下GAE的情况。在GAE FAQHow do I serve compressed content?声明如下:

Google App Engine does its best to serve gzipped content to browsers that support it. Taking advantage of this scheme is automatic and requires no modifications to applications.

We use a combination of request headers (Accept-Encoding, User-Agent) and response headers (Content-Type) to determine whether or not the end-user can take advantage of gzipped content. This approach avoids some well-known bugs with gzipped content in popular browsers. To force gzipped content to be served, clients may supply ‘gzip’ as the value of both the Accept-Encoding and User-Agent request headers. Content will never be gzipped if no Accept-Encoding header is present.

意思是GAE自动对js,css等文件进行了gzip压缩,只要客户端的浏览器支持gzip,则GAE将发送gzip压缩的文件,否则只发送原始的文件。这个过程不需要开发人员做任何处理。

经过测试,一个190K的js文件,使用firefox浏览返回的大小只有45K,减少了大概70%-80%。和gzip压缩比较起来,js内容精简的效果差了很多,js内容的精简一般只能减少20%左右。

分类: GAE 标签:

GAE 构建索引

2008年12月26日 roboter 没有评论

针对google的文章How Index Building Works进行了翻译,在读这篇文章的时候最好先了解Bigtable的相关内容,可以参看“大表(Bigtable):结构化数据的分布存储系统”。

每一个GAE应用都是依据索引进行查询,内置的索引可以处理简单的查询,包括在单个属性上的指定类型,过滤和排序,以及在很多属性上的“等于”过滤。更复杂的查询,需要定制developer-defined indexes

实际上,我们叫它为组合索引,以别于内在类型和单个属性的索引。我们这样说是因为这些索引数据是由每个索引列的多个值组成。

当你在应用中添加一个组合索引时,必须把你已经存在的数据填充到索引中,这时候的索引才能提供查询服务。同样,当删除一个索引时,索引列必须被删除。GAE在后台运行索引构建流程进行索引的填充和删除。

本文描述了索引如何排列,索引创建流程,以及常见问题。

索引排列

索引数据存储在四个bigtables中,被所有应用共享。前三个表存储内在类型,单个属性的升序,降序索引数据,最后一个表存储组合索引数据。

索引列的索引数据存储在bigtables的列名称中,包括app id, kind, key以及索引中额外的数据。内置的单个属性索引的额外数据包括属性名和值,内置的类型索引则没有额外数据。

组合索引中的额外数据包含了在索引定义中各个属性的值,如果指定了祖先的话,则包含祖先。属性名称省略,可以节约空间,因为索引定义中已经包含了属性名称。但是,组合索引列中包含了一个索引id,用来区分不同的组合索引恰好在同样的顺序下具有同样的属性值。

欲了解更多关于索引排列,请看Google I/O 下描述的Under the Covers of the Google App Engine Datastore

索引创建和删除

当你定义好一项新的索引,并要求我们创建时,我们按照下列步骤进行:

  1. 增加索引定义到应用中。
  2. 标识索引正在构建(Building),这样就告诉了数据存储,需要更新所有的写入,但是还不能用它进行查询。
  3. 映射存在的实体,将其填充到索引中。
  4. 标识索引正在服务(Serving),这样该索引就可以用于查询了。

删除索引几乎是相同的过程:

  1. 标识索引正在删除(Deleting),这样告诉数据存储,忽略该索引的查询和写入
  2. 删除索引列
  3. 删除索引的定义

当一个实体创建或删除,所有和该实体相关的索引列必须更新。但是,当一个实体更新时,如某些属性改变了,则仅仅更新和这些属性相关的索引。

另外,这些组合索引的处理是自动的。当一个实体需要保存或者删除,数据存储系统根据上面的规则找到相关索引,并进行更新。

由于索引更新的高效和自动化,索引构建流程可以简单重复的使用。要建立或者删除索引,流程会映射一次应用所有的实体。对于每个实体,流程使用数据存储系统在一个事务中进行读,写,保持不变。数据存储系统检测Building和Deleting状态的索引,更新相关的索引列。

并行工作

索引构建流程在并行地渐进建立和删除索引,这样可以使不同应用的索引可以在同一时间创建和删除。也可以让同一个应用里面的不同索引同时创建或者删除。最后,如果流程当掉之后,可以设立检查点恢复工作。

工作流程保存了一份在所有应用中将要删除或建立的索引列表。每一个应用的数据分成了多个碎片(shards)。当一个工作器空闲时,它查询这个列表,借用一份碎片的数据,映射碎片中的实体,象上面所说的那样填充索引列。如果在借用期到了,工作器还没有做完,则工作器放弃工作,丢弃部分完成的工作。该碎片(shards)可以再被其他工作器使用。

我们已经实现了几个不同的碎片数据机制:首先是Bigtable tablet,其次是在tablets内部,这两种方法说明如下。

以tablet进行分割

令人惊讶的是,分割一个应用的实体成为碎片不是很容易的。最理想的情况是,工作流程每N列询问一次Bigtable,从应用的第一个实体开始,到最后一个实体结束。这些将是分割点,用以区分每一份碎片。

然而,Bigtable并不打算提供这样的操作方式。于是我们提供了一个近似的方法,Bigtable按照相临的列把数据进行分块,这些块叫做tablet,它能够在运行的时候在不同的tablet服务器上进行分割,合并,移动等。Bigtable知道tablet的开始和结束列,所以我们把每一个tablet标为一个碎片(shard)。

不幸的是,tablet受限制于数据大小,所以列(实体)的数目在不同的tablet中是不同的。填充索引数据的时间更多依赖于实体的属性数目,而不是实体大小,所以不同的tablet 碎片花费的时间完全不同。在具有很多列的tablet中,工作器经常超过它的借用期,索引的删除(deleting)或者创建(building)状态会保持很长时间。

在tablets内进行分割

为了解决上述问题,我们最近增加了大碎片(shard)的分割功能。当一个工作器发现它在一块碎片上的借用期即将到期的时候,它放弃这个工作,将碎片分割成n个小碎片,每一个小碎片包含了原始碎片的列范围和两个碎片参数:n,每小片都相同。k,从0到n-1的范围。

当一个工作器取走带有分割参数的碎片时,它映射到碎片列范围中的所有实体,通常,它会列举每一个实体,计算实体key的hash值,仅仅当实体的hash值等于k%n时,它才填充索引。

交替设计可以用于填充每k个实体,这样变得很简单。但是有可能会遗漏某些实体,因为在这个处理过程中,应用可能会增加或者删除实体。

必须强调的是,在分割的时候,碎片的列范围是不会改变的。工作器依然扫描列范围内所有的实体,这个扫描相对于读写索引来说是很便宜的。进一步说,磁盘的大小比磁盘定位要便宜很多。所以Bigtable扫描比随机访问一个特定的列进行读写要便宜很多。

FAQs

下面是关于一些索引创建和删除的常规问题(如果你比较好奇怎样给你的应用指定索引,可以访问我们的文档

Q.为什么我的索引保持Building或Deleting状态那么长的时间?

A.在过去,通常是工作器中的碎片太大,以至于在碎片的借用期内无法完成。为了解决这个问题,我们首先增加了借用期限,现在我们可以将tablet分割成碎片,索引创建仍然需要一点时间,但是现在比以前快了许多。

Q.为什么我的索引标为Error状态?

A.大概是exploding indexes(爆炸索引),有时候,我们不得不手工标记索

分类: GAE 标签:

GAE大实体,激增的索引

2008年12月25日 roboter 没有评论

翻译自google的文章:http://code.google.com/intl/zh-CN/appengine/docs/datastore/queriesandindexes.html#Big_Entities_and_Exploding_Indexes

除了Text or Blob 属性外,实体中的如下属性都会至少加到一个索引表中,包括缺省提供的简单索引的属性,以及在index.yaml 文件中定义的索引中声明的属性。实体中的每属性都有一个值,属性所在的简单索引中会存储一次属性值,在自定义索引中具有该属性的每一个索引也都会存储一次。当属性改变的时候,这些索引实体都将被更新。所以,如果有很多索引指向一个属性,则执行put()更新数据所花费的时间会多一些。

为了阻止更新一个实体花费太多的时间,datastore限制了单个属性所能具有的索引数目。这个限额是很大的,大部分应用将不会注意到它。然而,有一些情况你可能会遇到他,例如,一个具有很多很多单值属性的实体可能会超越索引限额。

多值属性,如 listListProperty,在一个索引中会存储每个值。具有很多很多值的单个属性的实体也会超越索引限额。

指向多值属性,具有多个值的自定义索引,只需要很少的几个值就可以变得很大。为了完全记录这些属性,索引表必须包含每一个属性的每一个值的排列组合。例如,下面的索引包含了MyModel类型的实体,xy 两个属性:

indexes:
- kind: MyModel
properties:
- name: x
- name: y

下列代码创建了一个实体,x属性有2个值,y属性有2个值:

class MyModel(db.Expando):    pass

e2 = MyModel()e2.x = ['red', 'blue']e2.y = [1, 2]e2.put()

为了表现这些值,索引必须保存8个值,x和y的内置属性都需要有两个值,x和y的组合(2 X 2)。list值很多,则意味着索引需要存储非常多的值。你可以把一个指向多值属性、多个值的索引称作“爆炸索引”,只需要很少的一些值,就会变得很多。

如果一个put()操作导致索引实体超越限额,则调用失败,产生BadRequestError异常。如果有一个实体已经超越了限额,则创建新索引的时候,将会失败,在Admin Console上显示该index处于“Error”状态。

为了处理“Error”的索引,首先从你的index.yaml文件中删除该索引,运行appcfg.py vacuum_indexes。然后,改良索引定义以及相关的查询,或者删除导致“爆炸索引”的实体,最后,保存索引到你的index.yaml文件中,运行appcfg.py update_indexes。

避免“爆炸索引,你可以避免使用list属性的查询,象上面所说的,也包括了如下的查询:降序排列,多种排序方式,等于和不等于的组合过滤,以及祖先过滤。

分类: GAE 标签: