IBM Cloud Docs
设计文档管理

设计文档管理

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, 如下图所示:

使用 1000 个文档创建数据库。 添加一个设计文档,触发一个或多个视图的构建。 视图以异步方式构建,直至完成。 又有 250 个文档的到来,使得索引再次不完整。 视图将在后台自动构建,或者在查询时自动构建。
Illustration of Incremental MapReduce

值得记住以下几点:

  • 索引构造是异步发生的。 IBM Cloudant 确认已保存设计文档。 要检查索引构造的进度,必须轮询 IBM Cloudant的 _active_tasks 端点。
  • 数据越多、 索引就绪所需的时间就越长。
  • 在初始索引构建过程中 任何针对索引的查询都会被阻止。
  • 查询视图会触发任何未增量索引的文档的“映射”。 这种做法可确保您获得最新的数据。 有关这一规则的例外情况,请参阅下面的 stale 参数 讨论 中的例外情况。

同一设计文档中的多个视图

如果在同一设计文档中定义了多个视图、 就能同时高效地构建这些视图。 每个文件只读一次、 并通过每个视图的 Map 函数传递。 如果使用此方法,请记住修改设计文档会使文档中定义的所有现有 MapReduce 视图失效。 即使某些视图保持不变,此过程也会使 MapReduce 视图失效。

如果 MapReduce 视图必须相互独立地进行变更,请将其定义放在单独的设计文档中。

此行为不适用于 Lucene 搜索索引。 这种索引可以在同一设计文档中变更,而不会使同一文档中的其他未更改索引失效。

使用 1000 个文档创建数据库。 添加了用于触发构建 2 视图和 2 搜索索引的设计文档。 视图以异步方式构建,直至完成。 设计文档的第二个版本的到达将使文档中的所有 MapReduce 视图和任何已更改的搜索索引失效。
Design document version change

管理对设计文档的更改

想象一下,在未来的某个时刻,你决定改变视图的设计。 现在 而不是返回实际的时间戳结果、 我们只关心有多少文档符合条件。 为了实现这一计算 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、 那么这两个端点就可以互换使用,而不会触发任何重新索引。

要切换到新视图,请执行以下步骤:

  1. 创建要更改的设计文档的副本、 例如,在其名称中添加 _OLD_design/fetch_OLD.
  2. Put the new or "incoming" design document into the database by using a name with the suffix _NEW: _design/fetch_NEW.
  3. 查询 fetch_NEW 视图 以确保开始构建。
  4. 轮询 _active_tasks 端点,等待索引建立完毕。
  5. 将新设计文档的重复副本放入 _design/fetch 中。
  6. 删除设计文档 _design/fetch_NEW
  7. 删除设计文档 _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”参数

如果索引已完成,但在数据库中添加了新记录,那么索引将安排为在后台更新。 数据库的状态如下图所示:

视图已完成。 又有 250 个文档的到来,使得索引再次不完整。
Index scheduled for update

查询视图时,您有以下选项。

  • 默认行为是确保索引是最新的、 数据库中的最新文档、 才返回答案。 查询视图时, 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=okstale=update_after 可以更快地从视图中获取答案、 但这样做的代价是失去了新鲜感。

默认行为是将负载平均分配给IBM Cloudant集群中的节点。 如果使用 stale=okstale=update_after 选项,这些选项可能会偏向于集群节点的子集,以便从最终一致的集群中返回一致的结果。 stale 参数不是所有用例的完美解决方案。 不过,如果您的应用程序乐于接受陈旧的结果,它也能对瞬息万变的数据集做出及时响应。 如果数据的更改率很小,那么添加 stale=okstale=update_after 不会带来性能方面的好处,并且可能会在更大的集群上不均匀地分配负载。

尽可能避免 stale=okstale=update_after,因为缺省行为提供最新鲜的数据,并在集群中分发数据。 您可以通过在这些时间临时切换到 stale=ok,使客户机应用程序知道正在进行大型数据处理任务 (例如,在定期批量数据更新期间)。 应用程序可以在之后还原为缺省行为。

stale 选项仍然可用,但更有用的选项 stableupdate 可用,必须改为使用后两个选项。 有关更多信息,请参阅访问陈旧视图