ビューの作成 (MapReduce)
概要
ビューは、 IBM® Cloudant® for IBM Cloud® の二次データ構造で、文書属性から派生したキーと値のペアを格納することができます。 これらは、ドキュメントの投影に対するクエリや集約に使用できます。
主な目的は2つある:
- インデクシング(投影) : マップのみのビューを使用して、ドキュメントを新しいキースペースに投影します。 これにより、文書ID以外のフィールド(電子メール、タイムスタンプ、カテゴリーなど)による効率的な検索やソートが可能になります。
- 集計と分析 : MapReduce ビューを使用して、ドキュメント全体のデータを出力および集計します。たとえば、ドキュメントのタイプ別にカウントしたり、値を合計したり、平均を計算したりできます。
ビューの仕組み
ビューは_設計文書で_定義され、以下のような構成になっています:
- マップ関数 ( JavaScript ):すべてのドキュメントに対して実行され、ビューの_キーと_ _値を_形成する属性を決定します。 マップ関数は、ドキュメントごとに0行、1行、または多数の行を出力することができる。
- オプションのリデュース関数 :放出された値を集約するために使用され、カウント、合計、平均化などの操作をサポートします。
一度構築されたビューは、文書が変更されると IBM Cloudant によって自動的に維持・更新される。
パーティショニングされたデータベースの場合、設計ドキュメントで options.partitioned を true に設定すると、ビューは単一のパーティションで操作できます。
どのような場合にビューを使用するか
眺望は理想的である:
- ID以外のドキュメント属性に関する効率的なルックアップとレンジクエリ (例えば、顧客のEメールや注文ステータスによるドキュメントの検索)。
- カバーインデックス :ビューのキー/バリュー・データのみを使用して満たすことができるクエリで、完全なドキュメントをフェッチする必要性を回避する。
- 集計サマリー :キーごとにグループ化された合計、平均、またはカウントを生成します(年/月/日ごとの売上など)。
- 部分インデックス :文書のサブセットのみを含む(例えば、完了した電子商取引の注文のみのリスト)。
ビューを使用しない場合
のビューを避ける:
- アドホッククエリ- 代わりに Cloudant Search を使用してください。
- フリーテキスト検索またはワイルドカード検索- 代わりに Cloudant Search を使用してください。
単純なビュー
最も単純な形式のビューは、マップ関数です。 マップ関数では、データベース内に保管されている文書の分析 (マッピング) を表す出力データが生成されます。
例えば、どのユーザーがオンライン登録を完了済みで、検証済みの連絡先 (E メール) を持っているかを調べることができます。 この情報は、各文書を検査し、文書内の「email_verified」というフィールドを探して、「email」の値を取得することによって見つけることができます。 そのフィールドが存在し、値が「 true 」となっている場合、 ユーザーは登録を完了したことを意味するため、メールで連絡を取ることができます。 このフィールドが存在しないか、値が
true 以外である場合、そのユーザーは登録を完了していません。
ビュー関数で emit 関数を使用すると、ビューを使用して照会を実行する際に、応答で簡単にリストが生成されます。 このリストはキーと値のペアで構成されます。キーは特定の文書を識別するのに役立ち、値は必要かつ正確な詳細のみを提供します。 また、このリストには、メタデータ (返される key:value ペアの数など) も含まれます。
文書 _id は、key:value ペアの各結果レコードに自動的に含まれます。 文書 _id は、クライアントでの結果の処理が簡単になるように含まれています。
マップ関数を使用した単純なビューの例を以下に示します。
function(user) {
if(user.email_verified === true) {
emit(user.email, {name: user.name, email_verified: user.email_verified, joined: user.joined});
}
}
サンプル・データを使用した単純なビューの例を以下に示します。
[
{
"_id":"abc123",
"name": "Bob Smith",
"email": "bob.smith@aol.com",
"email_verified": true,
"joined": "2019-01-24T10:42:59.000Z"
},
{
"_id":"abc125",
"name": "Amelie Smith",
"email": "amelie.smith@aol.com",
"email_verified": true,
"joined": "2020-04-24T10:42:59.000Z"
}
]
単純なビュー照会を実行した場合の応答の例を以下に示します。
{
"total_rows": 2,
"offset": 0,
"rows": [
{
"id": "abc125",
"key": "amelie.smith@aol.com",
"value": {
"name": "Amelie Smith",
"email_verified": true,
"joined": "2020-04-24T10:42:59.000Z"
}
},
{
"id": "abc123",
"key": "bob.smith@aol.com",
"value": {
"name": "Bob Smith",
"email_verified": true,
"joined": "2019-01-24T10:42:59.000Z"
}
}
]
}
マップ関数の例
設計文書内のビューの定義では、キー情報に基づいて索引も作成されます。 索引を作成して使用すると、ビューからの文書のアクセスや検索または選択の速度が大幅に向上します。
以下のセクションでは、単純なキーと複雑なキーによる索引付けと、reduce 関数について説明します。
索引付け機能は、文書がその環境で使用されるメモリーの一部を形成するメモリー制約環境で動作します。 コードのスタックと文書は、メモリー内に収まる必要があります。 文書の最大サイズは 64 MB に制限されています。
フィールドの索引付け
次のマップ関数は、オブジェクトに name フィールドが含まれるかどうかを検査し、含まれる場合はこのフィールドの値を出力します。 このチェックにより、
name フィールドの値に対して照会を実行できます。
フィールドの索引付けの例を以下に示します。
function(doc) {
if (doc.name) {
emit("name", doc.name);
}
}
1 対多の関係の索引
emit に渡されたオブジェクトに _id フィールドが含まれている場合、include_docs が true に設定されたビュー照会には、特定の ID の文書が含まれます。
1 対多の関係の索引付けの例を以下に示します。
function(doc) {
if (doc.friends) {
for (friend in doc.friends) {
emit(doc._id, { "_id": friend });
}
}
}
複合キー
キーは単純値に制限されません。 任意の JSON 値を使用して、ソートに影響を与えることができます。
キーが配列である場合、ビュー結果は、キーのサブセクション別にグループ化できます。 例えば、キーの形式が [year, month, day] である場合、結果は、単一値に削減するか、年、月または日別に削減できます。
詳しくは、ビューの使用を参照してください。
価値の活用
MapReduce 定義の emit 関数の2番目のパラメーターは「値」で、結果のインデックスにキーとともに格納される。 この値には2つの用途がある:
- 選択のみのビューでは、クエリ時に
?include_docs=trueを使用する必要がないように、ドキュメントのサブセットを保存するために値を使用することができます。 これにより、インデックスが大きくなる代償として、クエリ時のパフォーマンスが向上します。 - リデューサを使用するビューの場合、値には通常、単一の数値、固定キーと数値を持つ小さなオブジェクト、または数値の短い配列が含まれます。 数値データは(
_sum)レデューサで合計されるか、_statsレデューサで統計データが作成される。
以下に例をいくつか示します:
// create a view to allow selection of orders by year/month/day,
// where a subset of the document is projected into the view's value.
function(doc) {
if (doc.type === 'order') {
const minidoc = {
customer_id: doc.customer_id,
date: doc.date,
status: doc.status
}
emit([doc.year, doc.month, doc.day], minidoc)
}
}
// create a view, designed for the _sum reducer which contains
// one row per order, in year/month/day order where the value
// is the order's total in USD. This can be summed at query-time
// with optional grouping by year, year/month or year/month/day.
function(doc) {
emit([doc.year, doc.month, doc.day], doc.order_total_usd)
}
// create a view, designed for the _sum reducer which contains
// one row per order, in year/month/day order where the value
// contains three numeric quantities (order total, tax and shipping)
// which will be summed at query-time with optional grouping by year,
// year/month or year/month/day.
function(doc) {
const value = {
total: doc.order_total_usd,
tax: doc.tax_usd,
shipping: doc.shipping_usd
}
emit([doc.year, doc.month, doc.day], value)
}
// create a view, designed for the _sum reducer which contains
// one row per order, with customer_id as the key. The numeric
// quantities are in array (order total and tax) which will be
// summed at query-time with optional grouping by customer_id
function(doc) {
emit(doc.customer_id, [doc.order_total_usd, doc.tax_usd])
}
ビューの値には、 order_id や
customer_id というのも、これはビューの縮小値を_縮小する_のではなく、 _拡大_することにつながるからだ。 このようなクエリは、タイムアウトしたり、拒否されることがある IBM Cloudant サービス。 数値リデューサーの場合、高基数データは通常ビューの "キー "の構成要素であり、"値 "は数値データ用に予約されています。
reduce 関数
options.partitioned が true に設定された設計文書には、カスタムの JavaScript reduce 関数を含めることはできません。 組み込みの reduce のみが許可されます。
リデューサーなし
設計文書内のビュー定義には reduce 属性を指定できません。これは、照会時の集約が実行されないことを示します。
{
"views": {
"getVerifiedEmails": {
"map": "function(user) { if(user.email_verified === true) { emit(user.email); } }"
}
}
}
上記のマップ関数により、選択にのみ適した 2 次索引が生成されます。 索引は、常にキー (emit 関数の最初のパラメーター)、この場合には user.email によって順序付けされます。 このビューは、既知のユーザー E メールまたはユーザー E メール・アドレスの範囲によって文書を取り出す場合に最適です。
組み込み reduce 関数
パフォーマンス上の理由から、単純ないくつかの reduce 関数が組み込まれています。 可能な場合には常に、独自に記述するのではなく、これらのうちいずれかの関数を使用する必要があります。
組み込み関数のいずれかを使用するには、 デザインドキュメント内のビューオブジェクトの「 reduce 」フィールドに、リデューサーの名前を入力してください。
カウント・レデューサー
_count リデューサは MapReduce ビューの行数をカウントし、オプションでそのカウント数を個別キーでグループ化します。
{
"views": {
"teamCount": {
"map": "function(doc) { if (doc.email_verified === true) { emit(doc.team, doc.name); } }",
"reduce": "_count"
}
}
}
前の MapReduce ビューは、ユーザーが所属する team をキーとするインデックスを作成しますが、検証済みメールアドレスを持つユーザーのみを含めます。 リデューサーは _count であるため、ビューはビュー内の行数、例えばデータベース内の検証済みユーザー数を出力する。
{"rows":[
{"key":null,"value":10010}
]}
?group=true を追加することで、カウントは明確なキーでグループ化され、データベースはチームメンバー別のカウントを出力する:
{"rows":[
{"key":"blue","value":1409},
{"key":"green","value":1439},
{"key":"indigo","value":1425},
{"key":"orange","value":1432},
{"key":"red","value":1414},
{"key":"violet","value":1443},
{"key":"yellow","value":1448}
]}
リデューサーをオフに切り替えることで、同じビューを単一チームのメンバー選択に使用することができる ?key="orange"&reduce=false&limit=5 :
{"total_rows":10010,"offset":4273,"rows":[
{"id":"783173e102613c78d02a2b3304001642","key":"orange","value":"Bethel Lusk"},
{"id":"783173e102613c78d02a2b3304001b40","key":"orange","value":"Ethyl Dionne"},
{"id":"783173e102613c78d02a2b3304009c66","key":"orange","value":"Fredda Hendrix"},
{"id":"783173e102613c78d02a2b330401800d","key":"orange","value":"Bibi Page"},
{"id":"783173e102613c78d02a2b3304018ae7","key":"orange","value":"Marylou Lavender-Milton"}
]}
サム・レデューサー
_sum リデューサーは、 MapReduce ビューの出力された数値を合計します。 ビューの値は、数値、数値の配列、または数値を含むオブジェクトです。 商品データベースについて、以下の MapReduce の定義を考えてみよう:
{
"views": {
"productPrices": {
"map": "function(doc) { emit(doc.type, { price: doc.price, tax: doc.tax }); }",
"reduce": "_sum"
}
}
}
このビューは製品タイプに基づいており、その値は「 price 」と「 tax 」という2つの値を含むオブジェクトです。 _sum リデューサーは、ビュー全体の price と tax の両方の値の合計を計算します:
{"rows":[
{"key":null,"value":{"price":144.97, "tax":7.32}}
]}
ビューのクエリ時に ?group=true を追加することで、出力は一意のキー(この場合は製品タイプ)によってグループ化され、合計されます:
{"rows":[
{"key":"kitchen","value":{"price":14.99,"tax":1.14}},
{"key":"garden","value":{"price":129.98,"tax":6.18}}
]}
スタッツ・レデューサー
_sum リデューサーのように、 _stats リデューサーは、数値、数値を持つオブジェクト、または数値の配列で動作し、カウント、合計、最小値と最大値、および分散や標準偏差の計算に便利な値の二乗和を返します:
{
"views": {
"salesByDate": {
"map": "function(doc) { emit(doc.date, [doc.price, doc.tax]); }",
"reduce": "_stats"
}
}
}
前述の定義では、インデックスの値として出力される配列内に見つかった数値について、統計値を算出します。 値は、マップ関数に指定されたものと同じ順序で、配列として返されます。
{"rows":[
{"key":"2025-01-01","value":[
{"sum":14.99,"count":1,"min":14.99,"max":14.99,"sumsqr":224.7001},
{"sum":1.14,"count":1,"min":1.14,"max":1.14,"sumsqr":1.2995}
]},
{"key":"2025-01-02","value":[
{"sum":129.98,"count":2,"min":29.99,"max":99.99,"sumsqr":10897.4002},
{"sum":6.18,"count":2,"min":1.62,"max":4.56,"sumsqr":23.418}
]}
]}
おおよそのカウントが明確なレデューサー
インデックスの値に作用する数値レデューサ _sum と _stats とは異なり、 _approx_count_distinct レデューサはビューの_キーを_使用します。 これは、 MapReduce のビューで発見された別個のキーの数を推定するもので、正確にカウントするアルゴリズムよりもはるかに少ないメモリしか消費しません:
{
"views": {
"estimateIpCount": {
"map": "function (doc) {\n emit(doc.ip, 1);\n}",
"reduce": "_approx_count_distinct"
}
}
}
前述のビューの定義は、サーバーログのデータベース内の別個のIPアドレスの数を推定することを目的としている。 ドキュメントの ip がインデックスの_キーとして_発行され、 _approx_count_distinct リデューサーが別個のキーの数を推定できるようになっている:
{"rows":[
{"key":null,"value":100528}
]}
上下レデューサー
_top_x および _bottom_x リデューサー( x は 1 から 100 までの数)はそれぞれ、ビューのグループ化におけるトップ x またはボトム x の_値の_配列を返します。 例えば、ゲームアプリケーションでは、ユーザーIDをキーとし、そのユーザーが達成したスコアを値とするビューを作成することができる。 このビューは、ベストスコアまたはワーストスコアのスコアボードを作成するために使用することができます:
{
"views": {
"bestScores": {
"map": "function(doc) { emit(doc.user_id, doc.score); }",
"reduce": "_top_3"
}
}
}
パラメータなしでビューに問い合わせると、ビュー全体の上位3つのスコアが返される:
{"rows":[
{"key":null,"value":[99,98,97]}
]}
グルーピング(?group=true)では、各ユーザーの上位3つのスコアが返される:
{"rows":[
{"key":"user082","value":[99,98,97]},
{"key":"user291","value":[85,72,42]},
{"key":"user452","value":[55,51,30]}
]}
最初と最後のレデューサー
_first/_last リデューサーは、それぞれビューグループの最初か最後の_キーの_ _値を_返します。 IoT、定期的に多くのデバイスからの測定値を保存するアプリケーションがある場合、デバイスIDと測定時間をキーにしたビューを作成することができます。 ビューの_値は_ドキュメント全体です:
{
"views": {
"latestReading": {
"map": "function(doc) { emit([doc.deviceid, doc.timestamp], doc); }",
"reduce": "_last"
}
}
}
このビューは、この形式のキーと値を生成し、ビューは deviceid と timestamp でソートされる。 各デバイスの "最初 "と "最後 "の測定値を考慮した行がハイライトされている:
| キー | 値 | 初読書 ( group_level=1 ) | 最終回 ( group_level=1 ) |
|---|---|---|---|
| ["A00","2025-01-01T10:00:00.000Z"] | {"_id": "A00:5000","reading": 65,"timestamp":2025-01-01T10:00:00.000Z","deviceid":"A00"} | x | |
| ["A00","2025-01-01T10:01:00.000Z"] | {"_id": "A00:5001","reading": 64,"timestamp":2025-01-01T10:01:00.000Z","deviceid":"A00"} | ||
| ["A00","2025-01-01T10:02:00.000Z"] | {"_id": "A00:5002","reading": 59,"timestamp":2025-01-01T10:02:00.000Z","deviceid":"A00"} | x | |
| ["A01","2025-01-01T10:00:00.000Z"] | {"_id": "A01:8000","reading": 12,"timestamp":2025-01-01T10:00:00.000Z","deviceid":"A01"} | x | |
| ["A01","2025-01-01T10:01:00.000Z"] | {"_id": "A01:8001","reading": 15,"timestamp":2025-01-01T10:01:00.000Z","deviceid":"A01"} | ||
| ["A01","2025-01-01T10:02:00.000Z"] | {"_id": "A01:8002","reading": 19,"timestamp":2025-01-01T10:02:00.000Z","deviceid":"A01"} | x | |
| ["A02","2025-01-01T10:00:00.000Z"] | {"_id": "A02:4000","reading": 55,"timestamp":2025-01-01T10:00:00.000Z","deviceid":"A02"} | x | |
| ["A02","2025-01-01T10:01:00.000Z"] | {"_id": "A02:4001","reading": 54,"timestamp":2025-01-01T10:01:00.000Z","deviceid":"A02"} | ||
| ["A02","2025-01-01T10:02:00.000Z"] | {"_id": "A01:4002","reading": 56,"timestamp":2025-01-01T10:02:00.000Z","deviceid":"A02"} | x |
_last リデューサーを使用して、 group_level=1 でビューにクエリを実行すると、データベース内のすべての device_id の最新の読み取り値が返されます:
{"rows":[
{"key":["A00"],"value":{"_id":"93117567370d41d091b8dd160a3adf3f","_rev":"1-bc05e93e592d5a5a18e240240b581a55","deviceid":"A00","reading":13.8986,"timestamp":"2025-03-26T04:44:08.917Z","status":"red"}},
{"key":["A01"],"value":{"_id":"c9f53ac9e4a8444487ed0eaa11dc1c78","_rev":"1-1fcf121c73db03e49bac4c1981518b19","deviceid":"A01","reading":59.8453,"timestamp":"2025-04-01T01:52:34.254Z","status":"green"}},
{"key":["A02"],"value":{"_id":"577b7108a8a1458f9a17194ed1da398a","_rev":"1-71d345a773ab31f35ceb998f3c107c41","deviceid":"A02","reading":2.6208,"timestamp":"2025-03-31T00:22:17.175Z","status":"green"}},
{"key":["A03"],"value":{"_id":"150839b1d363427496a4f4e2917b8b1d","_rev":"1-860a2ed5f1f48aa642495dfb21dff3ce","deviceid":"A03","reading":55.8677,"timestamp":"2025-03-22T10:15:57.890Z","status":"red"}},
{"key":["A04"],"value":{"_id":"d3317bdc1f7b4466ae6bf7a30ca9e328","_rev":"1-a58762532f6980ca5b06a8d01d113814","deviceid":"A04","reading":44.1822,"timestamp":"2025-03-23T10:56:10.639Z","status":"green"}},
{"key":["A05"],"value":{"_id":"946754d3762f44e297ca20d13bbceb5e","_rev":"1-41fa2bb3ef8782c90b28ee44628abeab","deviceid":"A05","reading":13.2874,"timestamp":"2025-03-27T13:59:04.723Z","status":"blue"}},
{"key":["A06"],"value":{"_id":"bbcd8a5c0ae948baae713a9fcb5262d5","_rev":"1-66883211ee20a0772374672aa175dc50","deviceid":"A06","reading":7.9525,"timestamp":"2025-04-01T15:13:04.305Z","status":"blue"}},
{"key":["A07"],"value":{"_id":"a600caccdb82400698b158ecebfaa6f2","_rev":"1-68975a8d7133a13c6cc8ae34d28ea1c6","deviceid":"A07","reading":89.4818,"timestamp":"2025-03-07T02:12:59.934Z","status":"blue"}},
{"key":["A08"],"value":{"_id":"be20bae911db4da685f891fd01e07d3a","_rev":"1-d69b559627cf01e80fe85ea2f54d2f4b","deviceid":"A08","reading":97.6739,"timestamp":"2025-03-29T13:46:31.689Z","status":"green"}},
{"key":["A09"],"value":{"_id":"96526e80f2ff48e89e9e42aa47abae24","_rev":"1-85e56901f7113d7d6ff8ba573c535eb3","deviceid":"A09","reading":26.1597,"timestamp":"2025-03-18T03:22:19.848Z","status":"blue"}}
]}
内蔵レデューサーの概要
| 関数 | 説明 |
|---|---|
_count |
特定のキーの行カウントを生成します。 値は、任意の有効な JSON にすることができます。 |
_stats |
sum、count、min、max および sum-squared の各値を含む JSON 構造を生成します。 値はすべて数値である必要があります。 |
_sum |
キーのすべての値の合計を生成します。 値は数値である必要があります。 |
_approx_count_distinct |
ビューインデックスに含まれる異なるキーの数を、 HyperLogLog アルゴリズムの変種を使用して、ビューインデックス内の異なるキーの数を概算します。 |
_top_x/_bottom_x |
x は 1 から 100 までの_数値_です。 |
_first/_last |
各ビューグループについて、最も低いソート_キー_ または最も高いソートキーの_値を_それぞれ返します。 |
カスタム reduce 関数
ほとんどの場合、マップ関数から出力されるビューのkey-value ペアに対して集約を実行する場合は、組み込みのリデューサーで十分です。 ただし、珍しいユース・ケースでは、組み込みのいずれかのリデューサーの名前の代わりに、JavaScript reduce 関数を指定できます。
カスタムのreduce関数は、組み込みのreducerよりもはるかに遅く、メンテナンスが難しいので、カスタムのreducerを書く前に、組み込みのreducerで満足できるユースケースかどうかを確認してください。
reduce 関数には、以下の順序で 3 つの引数が渡されます。
keysvaluesrereduce
ビューにカスタムの JavaScript reduce 関数が含まれている場合、そのビューの集約結果を生成するために使用されます。 reduce 関数には、中間値のセットが渡され、これらの値は単一値に結合されます。 reduce 関数では、その対応するマップ関数によって出力される結果、および reduce 関数自体によって返される結果を入力として受け入れる必要があります。 後者のケースは、「rereduce」と呼ばれます。
reduce 関数の記述は、次の例のようになります。
カスタム reduce 関数の例を以下に示します。
function (keys, values, rereduce) {
return sum(values);
}
reduce 関数では、2 つのケースを処理する必要があります。
-
rereduceが false の場合は、以下のようになります。keysは、要素が[key, id]の形式の配列になっている配列です。ここで、keyはマップ関数によって出力されるキーであり、idはキーの生成元となる文書を識別します。valuesは、keys内の各要素について出力される値の配列です。例えば、reduce([ [key1,id1], [key2,id2], [key3,id3] ], [value1,value2,value3], false)となります。
-
rereduceが true の場合は、以下のようになります。keysはnullです。valuesは、reduce 関数に対する以前の呼び出しによって返された値の配列です。例えば、reduce(null, [intermediate1,intermediate2,intermediate3], true)となります。
reduce 関数は、最終ビューの value フィールドと、reduce 関数に渡される values 配列のメンバーの両方に適した単一値を返す必要があります。
多くの場合、reduce 関数は、上記の例の合計関数のように、追加のコードなしで rereduce 呼び出しを処理するように作成することができます。 このような場合、
rereduce 引数は無視できます。
reduce 関数の結果を reduce 関数にフィードすることにより、MapReduce では、大規模なデータ・セットの分析をより高速に完了できる個別の並列タスクに分割できます。
組み込み reduce 関数を使用する際に入力が無効である場合、builtin_reduce_error エラーが返されます。 この障害に関する詳細情報は、reason フィールドにあります。 エラーの原因となった元のデータは、caused_by フィールドに返されます。
応答の例を以下に示します。
{
"rows": [
{
"key": null,
"value": {
"error": "builtin_reduce_error",
"reason": "The _sum function requires that map values be numbers, arrays of numbers, or objects. Objects can't be mixed with other data structures. Objects can be arbitrarily nested, if the values for all fields are themselves numbers, arrays of numbers, or objects.",
"caused_by": [
{
"a": 1
},
{
"a": 2
},
{
"a": 3
},
{
"a": 4
}
]
}
}
]
}
マップ関数と reduce 関数の制限
ここでは、マップ関数と reduce 関数の制限について説明します。
参照透過性
マップ関数は、参照に関して透過的である必要があります。 参照透過性とは、結果 (この場合は、文書とkey-value ペア) が変わることなく、式を同じ値で置き換えられることを意味します。 参照透過性のために、 IBM Cloudant ビューは増分的に更新でき、最後の更新以降の差分のみを再索引付けできます。
可換プロパティーおよび結合プロパティー
参照透過性に加えて、reduce 関数には、その入力に対する可換プロパティーおよび結合プロパティーも必要です。 これらのプロパティーにより、MapReduce 関数では、独自の出力を削減して同じ応答を生成できます。次に例を示します。
f(Key, Values) == f(Key, [ f(Key, Values) ] )
その結果、IBM Cloudant では、B ツリー索引の内部ノードに中間結果を保管できます。 これらの制限により、索引をマシン間で分散させて照会時に削減することもできます。
文書パーティショニング
シャーディングが行われるので、IBM Cloudant では、1 つの同じ reduce 呼び出しに対して 2 つの特定のマップ関数の出力が渡されることは保証されません。 順序付けに依存してはなりません。 使用する reduce 関数は、渡されるすべての値を考慮し、順序付けに関係なく正しい回答を返す必要があります。 また、 IBM Cloudant は、索引の作成時に reduce 関数を呼び出す必要がなかった場合でも、照会時にrereduce=true を使用して reduce 関数を呼び出すことが保証されます。 この場合、関数が正しく機能することが不可欠です(rereduce=trueは、 keys パラメーターがnullであり、値の配列が前の reduce 関数呼び出しからの結果で埋められることを意味します)。
削減された値のサイズ
IBM Cloudant では、ビュー索引および対応する reduce 値を計算し、各 B ツリー・ノード・ポインター内でこれらの値をキャッシュします。 さて、 IBM Cloudant は、B ツリーを更新するときに、削減された値を再利用できます。 reduce 関数から返されるデータ量に注意する必要があります。
返されるデータ・セットのサイズは小さいままで、速度は log(num_rows_processed) 未満となるのが最適です。 この制限を無視すると、 IBM Cloudant は自動的にエラーをスローしませんが、B ツリーのパフォーマンスは劇的に低下します。 ビューが小さいデータ・セットで適切に機能しているが、さらにデータが追加されたときに作業を停止する場合、ビューは、成長率特性に関する制限に違反している可能性があります。
実行環境
索引付け機能は、文書がその環境で使用されるメモリーの一部を形成するメモリー制約環境で動作します。 コードのスタックと文書は、メモリー内に収まる必要があります。 文書の最大サイズは 64 MB に制限されています。
options.partitioned が true の場合、JavaScript リデューサーは存在しません。
options.partitioned が true に設定された設計文書には、JavaScript reduce 関数を含めることはできず、組み込みの Erlang リデューサー (_stats など) のみを含めることができます。
ビュー定義の保管
各ビューは JavaScript 関数です。 ビューは設計文書に保管されます。 したがって、ビューを保存するには、 IBM Cloudant は、単に関数定義を設計文書内に保管します。 設計文書は、その他の文書と同様に 作成または更新できます。
ビューの定義を保存するには、
PUT ビュー定義の内容を ドキュメントに変換します。 _design
次の例では、getVerifiedEmails ビューがマップ関数として定義されており、設計文書の views フィールド内で使用可能です。
PUT メソッドを使用して、設計文書にビューを追加します。
PUT $SERVICE_URL/$DATABASE/_design/$DDOC HTTP/1.1
Content-Type: application/json
以下のサンプルでは、ビュー定義を持つデザイン・ドキュメントに新しいビュー getVerifiedEmails を追加しています allusers
{
"views": {
"getVerifiedEmails": {
"map": "function(user) { if(user.email_verified === true){ emit(doc.email, {name: user.name, email_verified: user.email_verified, joined: user.joined}) }} "
}
}
}
次は、要求の例です。
curl -X PUT "$SERVICE_URL/users/_design/allusers" --data '{
"views": {
"getVerifiedEmails": {
"map": "function(user) { if(user.email_verified === true){ emit(doc.email, {name: user.name, email_verified: user.email_verified, joined: user.joined}) }}"
}
}
}'
import com.ibm.cloud.cloudant.v1.Cloudant;
import com.ibm.cloud.cloudant.v1.model.DesignDocument;
import com.ibm.cloud.cloudant.v1.model.DesignDocumentViewsMapReduce;
import com.ibm.cloud.cloudant.v1.model.DocumentResult;
import com.ibm.cloud.cloudant.v1.model.PutDesignDocumentOptions;
import java.util.Collections;
Cloudant service = Cloudant.newInstance();
DesignDocumentViewsMapReduce emailViewMapReduce =
new DesignDocumentViewsMapReduce.Builder()
.map("function(user) { if(user.email_verified === true){ emit(doc.email,{name: user.name, email_verified: user.email_verified, joined: user.joined}) }")
.build();
DesignDocument designDocument = new DesignDocument();
designDocument.setViews(
Collections.singletonMap("getVerifiedEmails", emailViewMapReduce));
PutDesignDocumentOptions designDocumentOptions =
new PutDesignDocumentOptions.Builder()
.db("users")
.designDocument(designDocument)
.ddoc("allusers")
.build();
DocumentResult response =
service.putDesignDocument(designDocumentOptions).execute()
.getResult();
System.out.println(response);
import { CloudantV1 } from '@ibm-cloud/cloudant';
const service = CloudantV1.newInstance({});
const emailViewMapReduce: CloudantV1.DesignDocumentViewsMapReduce = {
map: 'function(user) { if(user.email_verified === true){ emit(doc.email, {name: user.name, email_verified: user.email_verified, joined: user.joined}) }}'
}
const designDocument: CloudantV1.DesignDocument = {
views: {'getVerifiedEmails': emailViewMapReduce}
}
service.putDesignDocument({
db: 'users',
designDocument: designDocument,
ddoc: 'allusers'
}).then(response => {
console.log(response.result);
});
from ibmcloudant.cloudant_v1 import CloudantV1
service = CloudantV1.new_instance()
email_view_map_reduce = DesignDocumentViewsMapReduce(
map='function(user) { if(user.email_verified === true){ emit(doc.email, {name: user.name, email_verified: user.email_verified, joined: user.joined}) }}'
)
design_document = DesignDocument(
views={'getVerifiedEmails': email_view_map_reduce}
)
response = service.put_design_document(
db='users',
design_document=design_document,
ddoc='allusers'
).get_result()
print(response)
emailViewMapReduce, err := service.NewDesignDocumentViewsMapReduce(
"function(user) { if(user.email_verified === true){ emit(doc.email, {name: user.name, email_verified: user.email_verified, joined: user.joined}) }}",
)
if err != nil {
panic(err)
}
designDocument := &cloudantv1.DesignDocument{
Views: map[string]cloudantv1.DesignDocumentViewsMapReduce{
"getVerifiedEmails": *emailViewMapReduce,
},
}
putDesignDocumentOptions := service.NewPutDesignDocumentOptions(
"users",
"allusers",
designDocument,
)
documentResult, _, err := service.PutDesignDocument(putDesignDocumentOptions)
if err != nil {
panic(err)
}
b, _ := json.MarshalIndent(documentResult, "", " ")
fmt.Println(string(b))
前の Go の例では、以下のインポート・ブロックが必要です。
import (
"encoding/json"
"fmt"
"github.com/IBM/cloudant-go-sdk/cloudantv1"
)
すべての Go の例では、serviceオブジェクトを初期化する必要があります。 詳しくは、API 資料の認証セクションで例を参照してください。