上面的 Index 类在所有实体中查找 user_id,自动维护 index_user_id 表的索引。我们的数据库是切分的,参数 shard_on 是用来确定索引是存储在哪个分片上(这种情况下使用 entity["user_id"] % num_shards)。
你可以使用索引实例(见上面的 user_id_index.get_all)查询一个索引,使用 python 写的数据存储代码将表 index_user_id 和表 entities 合并。首先在所有数据库分片中查询表 index_user_id 获取实体 ID 列,然后在 entities 提出数据。
新建一个索引,比如,在属性 link 上,我们可以创建一个新表:
| CREATE TABLE index_link ( link VARCHAR(735) NOT NULL, entity_id BINARY(16) NOT NULL UNIQUE, PRIMARY KEY (link, entity_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
我们可以修改数据存储的初始化代码以包含我们的新索引:
| user_id_index = friendfeed.datastore.Index( table="index_user_id", properties=["user_id"], shard_on="user_id") link_index = friendfeed.datastore.Index( table="index_link", properties=["link"], shard_on="link") datastore = friendfeed.datastore.DataStore( mysql_shards=["127.0.0.1:3306", "127.0.0.1:3307"], indexes=[user_id_index, link_index]) |
我可以异步构建索引(特别是实时传输服务):
| ./rundatastorecleaner.py --index=index_link |
一致性与原子性
由于采用分区的数据库,实体的索引可能存储在与实体不同的分区中,这引起了一致性问题。如果进程在写入所有索引表前崩溃了会怎样?
许多有野心的 FriendFeed 工程师倾向于构建一个事务性协议,但我们希望尽可能地保持系统的简洁。我们决定放宽限制:
保存在主实体表中的属性集是规范完整的 索引不会对真实实体值产生影响因此,往数据库中写入实体时我们采用如下步骤:
使用 InnoDB 的 ACID 属性将实体写入 entities 表。 将索引写入所有分区中的索引表。
我们要记住从索引表中取出的数据可能是不准确的(例如如果写操作没有完成步骤2可能会影响旧属性值)。为确保采用上面的限制能返回正确的实体,我们用索引表来决定要读取哪些实体,但不要相信索引的完整性,要使用查询条件对这些实体进行再过滤:
1.根据查询条件从索引表中取得 entity_id
2.根据 entity_id 从 entities 表中读取实体
3.根据实体的真实属性(用 Python)过滤掉不符合查询条件的实体
为保证索引的持久性和一致性,上文提到的“清洁工”进程要持续运行,写入丢失的索引,清理失效的旧索引。它优先清理最近更新的实体,所以实际上维护索引的一致性非常快(几秒钟).










