设计文档管理
IBM Cloudant的可扩展 JSON 数据存储有多种查询机制、 所有这些机制都会生成与核心数据分开创建和维护的索引。
文章由IBM Cloudant的开发人员倡导者 Glynn Bird 投稿,glynn@cloudant.com.
保存文档时不会立即执行索引。 取而代之的是 索引会安排在稍后进行,从而提供更快的非阻塞写入吞吐量、 非阻塞写入吞吐量。
- MapReduce视图是数据集的索引。 键值对存储在 BTree 中,以便按键或键的范围进行高效检索。
- 通过使用ApacheLucene 来构建搜索索引,以便进行自由文本搜索、分面和复杂的临时查询。
IBM® Cloudant® for IBM Cloud® 的搜索索引和 MapReduce 视图通过向数据库添加设计文档进行配置。 设计文档是 JSON 文档,其中包含关于如何构建视图或索引的说明。 下面举一个简单的例子。 假设您有一个简单的数据文档集合、 类似于下面的示例。
请参阅简单数据文档示例:
{
"_id": "23966717-5A6F-E581-AF79-BB55D6BBB613",
"_rev": "1-96daf2e7c7c0c277d0a63c49b57919bc",
"doc_name": "Markdown Reference",
"body": "Lorem Ipsum",
"ts": 1422358827
}
每个数据文档都包含一个名称、一个主体和一个时间戳记。 创建 MapReduce 视图 以按时间戳记对文档进行排序。
您可以通过创建 Map 函数按时间戳记对文档进行排序。
请参阅返回文档的时间戳记字段的示例映射函数 (如果存在):
function(doc) {
if (doc.ts) {
emit( doc.ts, null);
}
}
该函数会发出文档的时间戳记,以便您可以将其用作索引的键。 由于我们对指数中的价值不感兴趣, 已发出 null
。 这样做的效果是提供了文档集内按时间排序的索引。
我们将调用此视图 by_ts
,并将其放入名为 fetch
的设计文档中。
请参阅使用映射函数定义视图的设计文档示例:
{
"_id": "_design/fetch",
"views": {
"by_ts": {
"map": "function(doc) {
if (doc.ts) {
emit( doc.ts, null);
}
}"
}
},
"language": "javascript"
}
其结果是将地图代码转换为与 JSON 兼容的字符串、 并包含在设计文档中。
一旦保存设计文档, IBM Cloudant 会触发服务器端进程来构建 fetch/by_ts
视图。 它通过遍历数据库中的每个文档来创建此视图、 并将每个文档发送到JavaScriptmap 函数。 函数返回发射的 key-value
信号对。 随着迭代的继续、 每个 key-value
对都存储在一个 B 树索引中。 第一次建立索引后、 后续的重新索引只针对新文档和更新文档执行。 对于删除的文档,会除去其索引。
这种节省时间的过程被称为增量MapReduce, 如下图所示:
值得记住以下几点:
- 索引构造是异步发生的。 IBM Cloudant 确认已保存设计文档。 要检查索引构造的进度,必须轮询 IBM Cloudant的
_active_tasks
端点。 - 数据越多、 索引就绪所需的时间就越长。
- 在初始索引构建过程中 任何针对索引的查询都会被阻止。
- 查询视图会触发任何未增量索引的文档的“映射”。 这种做法可确保您获得最新的数据。 有关这一规则的例外情况,请参阅下面的
stale
参数 讨论 中的例外情况。
同一设计文档中的多个视图
如果在同一设计文档中定义了多个视图、 就能同时高效地构建这些视图。 每个文件只读一次、 并通过每个视图的 Map 函数传递。 如果使用此方法,请记住修改设计文档会使文档中定义的所有现有 MapReduce 视图失效。 即使某些视图保持不变,此过程也会使 MapReduce 视图失效。
如果 MapReduce 视图必须相互独立地进行变更,请将其定义放在单独的设计文档中。
此行为不适用于 Lucene 搜索索引。 这种索引可以在同一设计文档中变更,而不会使同一文档中的其他未更改索引失效。
管理对设计文档的更改
想象一下,在未来的某个时刻,你决定改变视图的设计。 现在 而不是返回实际的时间戳结果、 我们只关心有多少文档符合条件。 为了实现这一计算 map 函数保持不变、 但现在使用的 reduce
的 _count
。
请参阅使用还原函数的设计文档示例:
{
"_id": "_design/fetch",
"_rev": "2-a2324c9e74a76d2a16179c56f5315dba",
"views": {
"by_ts": {
"map": "function(doc) {
if (doc.ts) {
emit( doc.ts, null);
}
}
}",
"reduce": "_count"
},
"language": "javascript"
}
保存此设计文档时, IBM Cloudant会使旧索引完全失效,并从头开始构建新索引、 依次遍历每个文档。 与原始构建一样,它所花费的时间取决于数据库中的文档数。 构建还会阻止该视图上的传入查询,直到其完成为止。
但有一个问题:
如果您有一个实时访问此视图的应用程序,那么可能会迂到部署难题:
- 代码的第 1 版、 版本的代码依赖于原始设计文档、 可能会因为旧视图失效而无法运行。
- 代码版本 2 使用新的设计文档。 此版本无法立即发布,因为新视图尚未完成构建。 请记住,如果数据库包含许多文档,那么构建过程需要更长时间。
- 影响代码的一个更微妙的问题是,版本 1 和 2 期望来自视图的不同结果数据: 版本 1 需要匹配文档的列表,而版本 2 需要“减少”的结果计数。
协调对设计文档的更改
您可以通过两种方式来处理此更改控制问题。
版本化的设计文档
我们的解决方案是使用版本化的设计文档名:
- 最初编写的代码使用名为
_design/fetchv1
的视图。 - 发布新版本时,将创建名为
_design/fetchv2
的新视图,并查询该视图以确保其构建。 - IBM Cloudant轮询
_active_tasks
直到建立新索引的工作完成。 - 现在,你可以发布依赖于第二个视图的代码了。
- 当我们确定不再需要
_design/fetchv1
时,请将其删除。
使用版本化设计文档是管理设计文档变更控制的一种简单方法,但必须记住稍后要删除旧版本。
Move and switch
设计文档
另一种方法则依赖于IBM Cloudant能够识别两个相同的设计文档、 而不会浪费时间和资源来重建已有的视图。 换句话说 如果你把设计文档 _design/fetch
并创建一个完全相同的 _design/fetch_OLD
、 那么这两个端点就可以互换使用,而不会触发任何重新索引。
要切换到新视图,请执行以下步骤:
- 创建要更改的设计文档的副本、 例如,在其名称中添加
_OLD
:_design/fetch_OLD
. - Put the new or "incoming" design document into the database by using a name with the suffix
_NEW
:_design/fetch_NEW
. - 查询
fetch_NEW
视图 以确保开始构建。 - 轮询
_active_tasks
端点,等待索引建立完毕。 - 将新设计文档的重复副本放入
_design/fetch
中。 - 删除设计文档
_design/fetch_NEW
。 - 删除设计文档
_design/fetch_OLD
。
Move and switch
tooling
命令行 Node.js couchmigrate
脚本会自动执行 Move and switch
过程。 使用以下命令即可安装:
npm install -g couchmigrate
要使用 couchmigrate
脚本 首先,通过设置名为 COUCH_URL
的环境变量,定义CouchDB/{{sitedata.keyword.cloudant_short_notm 实例的 URL。 运行以下命令以定义 {{site.data.keyword.cloudant_short_notm}} 实例的 URL:
export COUCH_URL=https://127.0.0.1:5984
URL 必须以 https://
开头,并且可以包含认证凭证。 运行以下命令以使用认证凭证定义 IBM Cloudant 实例的 URL:
export COUCH_URL="https://$ACCOUNT:$PASSWORD@$HOST.cloudant.com"
如果假定您具有 JSON 格式的设计文档 (存储在文件中),那么可以运行迁移命令。
在此示例中,
db
specifies the name of the database to change, and dd
specifies the path to the design document file. 运行 couchmigrate
命令:
couchmigrate --db mydb --dd /path/to/my/dd.json
脚本协调 Move and switch
过程、 等待视图创建完成后再返回。 如果传入设计文档与当前使用的设计文档相同,那么此脚本几乎会立即返回。
该脚本的源代码可在此处获取:
couchmigrate
.
“stale
”参数
如果索引已完成,但在数据库中添加了新记录,那么索引将安排为在后台更新。 数据库的状态如下图所示:
查询视图时,您有以下选项。
- 默认行为是确保索引是最新的、 数据库中的最新文档、 才返回答案。 查询视图时, IBM Cloudant 首先对 250 个新文档建立索引,然后返回答案。
- 替代方法是将
stale=ok
参数添加到 API 调用。 该参数表示return me the data that is already indexed. I don't care about the latest updates.
换言之,使用stale=ok
查询视图时, IBM Cloudant 将立即返回答案,而无需任何其他重建索引。 - 另一种替代方法是将
stale=update_after
参数添加到 API 调用。 该参数表示return me the data that is already indexed, and then reindex any new documents.
换言之,使用stale=update_after
查询视图时, IBM Cloudant 会立即返回答案,然后调度后台任务对新数据建立索引。
添加 stale=ok
或 stale=update_after
可以更快地从视图中获取答案、 但这样做的代价是失去了新鲜感。
默认行为是将负载平均分配给IBM Cloudant集群中的节点。 如果使用 stale=ok
或 stale=update_after
选项,这些选项可能会偏向于集群节点的子集,以便从最终一致的集群中返回一致的结果。 stale
参数不是所有用例的完美解决方案。 不过,如果您的应用程序乐于接受陈旧的结果,它也能对瞬息万变的数据集做出及时响应。 如果数据的更改率很小,那么添加 stale=ok
或 stale=update_after
不会带来性能方面的好处,并且可能会在更大的集群上不均匀地分配负载。
尽可能避免 stale=ok
或 stale=update_after
,因为缺省行为提供最新鲜的数据,并在集群中分发数据。 您可以通过在这些时间临时切换到 stale=ok
,使客户机应用程序知道正在进行大型数据处理任务 (例如,在定期批量数据更新期间)。 应用程序可以在之后还原为缺省行为。
stale
选项仍然可用,但更有用的选项 stable
和 update
可用,必须改为使用后两个选项。 有关更多信息,请参阅访问陈旧视图。