Change Notifications

変更通知

Say you are building a message service with CouchDB. Each user has an inbox database and other users send messages by dropping them into the inbox database. When users want to read all messages received, they can just open their inbox databases and see all messages.

CouchDBを使ってメッセージサービスを構築する場合を考えてみましょう。それぞれのユーザーは、受信箱となるデータベースを持っており、他のユーザーがメッセージを送信するには、メッセージをそのユーザーの受信箱におくことになります。受信したメッセージすべてをユーザーが読みたいと思ったときは、単に受信箱データベースを開き、すべてのメッセージを見れば良いわけです。

So far, so simple, but now you’ve got your users hitting the Refresh button all the time once they’ve looked at their messages to see if there are new messages. This is commonly referred to as polling. A lot of users are generating a lot of requests that, most of the time, don’t show anything new, just the list of all the messages they already know about.

ここまでは単純です。しかし、あなたは、送信相手に、一度全てのメッセージを見終わった後で、常に新しいメッセージがないかどうかを確認するために、常に更新ボタンを押させることになります。これは一般的にポーリングと呼ばれるものです。多くのユーザーが、ほとんどの場合、新しいメッセージを表示することなく、既に知っている全てのメッセージのリストを表示するだけのリクエストを多数生成することになります。

Wouldn’t it be nice to ask CouchDB to give you notice when a new message arrives? The _changes database API does just that.

新しいメッセージが到着したときには、CouchDBに通知をさせればよいのではないでしょうか?_changes APIがちょうどこの仕組みを行います。

The scenario just described can be seen as the cache invalidation problem; that is, when do I know that what I am displaying right now is no longer an apt representation of the underlying data store? Any sort of cache invalidation, not only backend/frontend-related, can be built using _changes.

上述のシナリオはキャッシュの無効化問題(すなわち、今表示しているものが、もはや内部データストアを適切に表わしていない、ということをいつ知るのか?)として取り扱うことができます。_changesは、バックエンドとフロントエンドの間だけではなく、あらゆるキャッシュの無効化に使うことができます。

_changes is also designed and suited to extract an activity stream from a database, whether for simple display or, equally important, to act on a new document (or a document change) when it occurs.

_changesはデータベースの活動傾向を抽出するため使うことにも適しています。新しいドキュメントの追加(あるいはドキュメントの更新)が発生したときに、その活動を単に表示することも同様に重要です。

The beauty of systems that use the changes API is that they are decoupled. A program that is interested only in latest updates doesn’t need to know about programs that create new documents and vice versa.

_changes APIを使った複数のシステムの美しさは、それらが分離されていることです。最新の更新についてのみ関心のあるプログラムは、新しいドキュメントを作るプログラムについて知る必要はありません。逆もまた然りです。

Here’s what a changes item looks like:

以下に、変更リスト(changes)の要素がどのように見えるかを示します。

{"seq":12,"id":"foo","changes":[{"rev":"1-23202479633c2b380f79507a776743d5"}]}

There are three fields:

3つのフィールドがあります。

seq
The update_seq of the database that was created when the document with the id got created or changed.
idのドキュメントが作成された、あるいは更新されたときの、データベースのupdate_seqの値です。
id
The document ID.
ドキュメントIDです
changes
An array of fields, which by default includes the document’s revision ID, but can also include information about document conflicts and other things.
通常、ドキュメントのリビジョンを格納している配列です。ドキュメントのコンフリクトがあった場合は、その情報も格納します。

The changes API is available for each database. You can get changes that happen in a single database per request. But you can easily send multiple requests to multiple databases’ changes API if you need that.

_changes APIはそれぞれのデータベースごとに利用可能です。あなたは、1つのデータベースについて1つのリクエストで変更リストを取得することができます。もし必要であれば、複数のリクエストを複数のデータベースの_changes APIに送信することも簡単に可能です。

Let’s create a database that we can use as an example later in this chapter:

それではこの章で使うデータベースを作りましょう。

> HOST="http://127.0.0.1:5984"
> curl -X PUT $HOST/db
{"ok":true}

There are three ways to request notifications: polling (the default), long polling and continuous. Each is useful in a different scenario, and we’ll discuss all of them in detail.

通知をリクエストするには3つの方法(ポーリング(これが通常です)、ロングポーリングコンティニュアス)があります。それぞれ異なるシナリオで有効です。これから全ての方法について議論しましょう。

Polling for Changes

変更リストのポーリング

In the previous example, we tried to avoid the polling method, but it is very simple and in some cases the only one suitable for a problem. Because it is the simplest case, it is the default for the changes API.

上述のサンプルでは、ポーリングを避けようとしましたが、ポーリングは非常に単純で、いくつかのケースにおいては唯一の解決策になります。もっとも単純なケースなので、_changes APIではデフォルトとなっています。

Let’s see what the changes for our test database look like. First, the request (we’re using curl again):

テストデータベースの変更リストがどのように見えるか見てみましょう。最初に(curlを使って)リクエストします。

curl -X GET $HOST/db/_changes

The result is simple:

結果は単純です。

{"results":[

],
"last_seq":0}

There’s nothing there because we didn’t put anything in yet—no surprise. But you can guess where we’d see results—when they start to come in. Let’s create a document:

驚くことはありません、まだ何もおいていないので、何もないのです。しかし、いつ、どこで結果が見えるようになるか見当がつけられるでしょう。では、ドキュメントを作りましょう。

curl -X PUT $HOST/db/test -d '{"name":"Anna"}'

CouchDB replies:

CouchDBは次のように返します。

{"ok":true,"id":"test","rev":"1-aaa8e2a031bca334f50b48b6682fb486"}

Now let’s run the changes request again:

ここで、_changesをもう一度リクエストしましょう。

{"results":[
{"seq":1,"id":"test","changes":[{"rev":"1-aaa8e2a031bca334f50b48b6682fb486"}]}
],
"last_seq":1}

We get a notification about our new document. This is pretty neat! But wait—when we created the document and got information like the revision ID, why would we want to make a request to the changes API to get it again? Remember that the purpose of the changes API is to allow you to build decoupled systems. The program that creates the document is very likely not the same program that requests changes for the database, since it already knows what it put in there (although this is blurry, the same program could be interested in changes made by others).

新しいドキュメントの通知をうけとることができました。 非常に良いでしょう! でも、ちょっと待ってください。私たちはドキュメントを作ったときに、既にリビジョンなどの情報は知っています。どうして_changes APIを使ってもう一度取得したかったのでしょうか?_changes APIの目的が、分離システムを構成できるようにすることであったことを思い出してください。ドキュメントを作成したプログラムは、変更リストを要求するプログラムとは、十中八九、同じものではありません。なぜなら既にドキュメントがデータベースにあることを知っているからです。(曖昧になってしまいますが、同じプログラムでも、他人によって変更が行われた場合は、同じプログラムが変更リストに関心を持つこともありえます。)

Behind the scenes, we created another document. Let’s see what the changes for the database look like now:

戻って、別のドキュメントを作りましょう。すると変更リストは次のようになります。

{"results":[
{"seq":1,"id":"test","changes":[{"rev":"1-aaa8e2a031bca334f50b48b6682fb486"}]},
{"seq":2,"id":"test2","changes":[{"rev":"1-e18422e6a82d0f2157d74b5dcf457997"}]}
],
"last_seq":2}

See how we get a new line in the result that represents the new document? In addition, the first document we put in there got listed again. The default result for the changes API is the history of all changes that the database has seen.

新しいドキュメントを表す、新しい行が追加されていることを確認してください。加えて、最初のドキュメントも、リストに含まれています。_changes API のデフォルトの結果は、データベースができてからの全ての変更リストとなります。

We’ve already seen the change for "seq":1, and we’re no longer really interested in it. We can tell the changes API about that by using the since=1 query parameter:

私たちは、"seq":1の変更リストを既に手に入れていますから、もうそれは必要ないでしょう。クエリパラメーターにsince=1をつけることで_changes APIにこのことを教えることができます。

curl -X GET $HOST/db/_changes?since=1

This returns all changes after the seq specified by since:

_changes APIは、sinceによって指定されたseq以降のすべての変更リストを返すでしょう。

{"results":[
{"seq":2,"id":"test2","changes":[{"rev":"1-e18422e6a82d0f2157d74b5dcf457997"}]}
],
"last_seq":2}

While we’re discussing options, use style=all_docs to get more revision and conflict information in the changes array for each result row. If you want to specify the default explicitly, the value is main_only.

オプションについて説明を加えておきます。style=all_docsを使うことで、変更リスト(changes)のそれぞれの結果の行に、リビジョンとコンフリクト情報をより詳細にいれることができます。デフォルトを明示したければ、styleの値は、main_onlyです。

Long Polling

ロングポーリング

The technique of long polling was invented for web browsers to remove one of the problems with the regular polling approach: it doesn’t run any requests if nothing changed. Long polling works like this: when making a request to the long polling API, you open an HTTP connection to CouchDB until a new row appears in the changes result, and both you and CouchDB keep the HTTP connection open. As soon as a result appears, the connection is closed.

ロングポーリングの技術は、ウェブブラウザが通常のポーリングアプローチで起こりうる問題の一つを解決するために発明されました。何も変更がなければリクエストを発生させないようにするのです。ロングポーリングは次のように動作します。ロングポーリング用のAPIにリクエストを行うとき、CouchDBに対して、新しい行が変更リストの結果に表れるまで、あなたもCouchDBもHTTP接続を開きっぱなしにします。新しい行が現れたらすぐに接続を閉じます。

This works well for low-frequency updates. If a lot of changes occur for a client, you find yourself opening many new requests, and the usefulness of this approach over regular polling declines. Another general consequence of this technique is that for each client requesting a long polling change notification, CouchDB will have to keep an HTTP connection open. CouchDB is well capable of doing so, as it is designed to handle many concurrent requests. But you need to make sure your operating system allows CouchDB to use at least as many sockets as you have long polling clients (and a few spare for regular requests, of course).

これは、更新が頻繁ではないときにうまく動作します。もし、多くの変更が発生するようであれば、多くの新しいリクエストが開始されることに気がつくでしょう。そして、通常のポーリングに対するロングポーリングの優位性は損なわれてしまいます。もう一つ、この技術における一般的な影響は、それぞれのクライアントがロングポーリング用の変更通知にリクエストを出している間、CouchDBはHTTP接続を開きっぱなしにしなければならないことでしょう。CouchDBは多くの同時リクエストを処理できるように設計されています。しかし、あなたは、オペレーティングシステムがCouchDBに対してロングポーリングを使用するクライアント数分の(そして、もちろん通常のリクエストを処理するためのいくつかの分の)ソケットを与えられるかどうか確認する必要があります。

To make a long polling request, add the feed=longpoll query parameter. For this listing, we added timestamps to show you when things happen.

ロングポーリングを使うには、feed=longpollというクエリパラメーターを付与します。いつ発生したかが分かるように、次のコードにはタイムスタンプを追加しました。

00:00: > curl -X GET "$HOST/db/_changes?feed=longpoll&since=2"
00:00: {"results":[
00:10: {"seq":3,"id":"test3","changes":[{"rev":"1-02c6b758b08360abefc383d74ed5973d"}]}
00:10: ],
00:10: "last_seq":3}

At 00:10, we create another document behind your back again, and CouchDB promptly sends us the change. Note that we used since=2 to avoid getting any of the previous notifications. Also note that we have to use double quotes for the curl command because we are using an ampersand, which is a special character for our shell.

00:10の時点で、別のドキュメントを作成すると、CouchDBは即座に変更を送信してくれます。since=2を使用し、以前の通知を取得しないようにしていることに留意してください。そして、curlコマンドにダブルクオートをつけている点も気をつけてください。アンパサンド(&)はシェルの特殊文字だからです。

The style option works for long polling requests just like for regular polling requests.

ロングポーリングリクエストのstyleオプションは、通常のポーリングリクエストと同様です。

Networks are a tricky beast, and sometimes you don’t know whether there are no changes coming or your network connection went stale. If you add another query parameter, heartbeat=N, where N is a number, CouchDB will send you a newline character each N milliseconds. As long as you are receiving newline characters, you know there are no new change notifications, but CouchDB is still ready to send you the next one when it occurs.

ネットワークは油断のならない猛獣です。あなたは、時々、変更リストが返ってこないのか、接続が悪くなっているのかが分からなくなります。heatbeat=N(Nは数値)を加えれば、CouchDBが改行文字をNミリ秒ごとに送信してくれるでしょう。改行文字を受信し続けている限りは、新しい変更通知がないことがわかります。この場合でも、CouchDBは、新しい変更が発生したらそれをあなたに送信する準備ができています。

Continuous Changes

コンティニュアス変更通知

Long polling is great, but you still end up opening an HTTP request for each change notification. For web browsers, this is the only way to avoid the problems of regular polling. But web browsers are not the only client software that can be used to talk to CouchDB. If you are using Python, Ruby, Java, or any other language really, you have yet another option.

ロングポーリングは素晴らしいものですが、それでもなお、それぞれの変更通知についてHTTP接続を開く必要があります。ウェブブラウザにとっては、通常のポーリングの問題を回避する唯一の方法です。しかし、CouchDBと対話するために使われるクライアントソフトウェアはブラウザだけではありません。Python、Ruby、Javaあるいはその他の言語を使っているのであれば、もう一つ別の方法があります。

The continuous changes API allows you to receive change notifications as they come in using a single HTTP connection. You make a request to the continuous changes API and both you and CouchDB will hold the connection open “forever.” CouchDB will send you newlines for notifications when the occur and—as opposed to long polling—will keep the HTTP connection open, waiting to send the next notification.

コンティニュアス変更通知APIを使うことで、単一のHTTP接続で変更通知を受け取ることができます。コンティニュアス変更通知APIに対してリクエストを発行すると、あなたとCouchDBは接続を永遠に開いたままにします。そしてCouchDBは、新しい行を送信することで変更通知を行い、ーロングポーリングの時とは反対にーHTTP接続を開いたままにし、次の通知を送信するまで待機します。

This is great for both infrequent and frequent notifications, and it has the same consequence as long polling: you’re going to have a lot of long-living HTTP connections. But again, CouchDB easily supports these.

これは変更通知が頻繁に起こるとき、あるいはたまにしか起こらないときのいずれにも有効です。そしてロングポーリングと同様の影響を与えます。つまり、多くの長寿命のHTTP接続を持つことです。しかし、CouchDBはこれを簡単にサポートします。

Use the feed=continuous parameter to make a continuous changes API request. Following is the result, again with timestamps. At 00:10 and 00:15, we’ll create a new document each:

コンティニュアス変更通知APIはリクエストにfeed=continuousというパラメーターを用います。もう一度、タイムスタンプをつけた結果を示します。00:10の時点と00:15の時点でそれぞれ新しいドキュメントを作ります。

00:00: > curl -X GET "$HOST/db/_changes?feed=continuous&since=3"
00:10: {"seq":4,"id":"test4","changes":[{"rev":"1-02c6b758b08360abefc383d74ed5973d"}]}
00:15: {"seq":5,"id":"test5","changes":[{"rev":"1-02c6b758b08360abefc383d74ed5973d"}]}

Note that the continuous changes API result doesn’t include a wrapping JSON object with a results member with the individual notification results as array items; it includes only a raw line per notification. Also note that the lines are no longer separated by a comma. Whereas the regular and long polling APIs result is a full valid JSON object when the HTTP request returns, the continuous changes API sends individual rows as valid JSON objects. The difference makes it easier for clients to parse the respective results. The style and heartbeat parameters work as expected with the continuous changes API.

コンティニュアス変更通知APIの結果は、各変更通知が配列として含まれているresultsメンバーをラップしたJSONオブジェクトを含んではいないことを確認してください。変更通知は、通知の度に、行に無加工で含まれています。そして、各行はコンマ(,)で分割されません。通常のポーリング、あるいはロングポーリングでは、HTTPリクエストが返す結果は完全で有効なJSONオブジェクトですが、コンティニュアス変更通知では、個々の行が有効なJSONオブジェクトを送信します。この違いにより、クライアントは個別の結果をパースしやすくなっています。

Filters

フィルター

The change notification API and its three modes of operation already give you a lot of options requesting and processing changes in CouchDB. Filters for changes give you an additional level of flexibility. Let’s say the messages from our first scenario have priorities, and a user is interested only in notifications about messages with a high priority.

_changes APIとその3つの動作モードはCouchDBの変更をリクエストし、処理する方法に様々なオプションを与えてくれるものです。変更に対するフィルタは柔軟性を付与してくれます。最初のシナリオのメッセージにおいて、メッセージが優先度を持っていて、ユーザーは優先度が「高(high)」のメッセージの通知のみに関心があるとしましょう。

Enter filters. Similar to view functions, a filter is a JavaScript function that gets stored in a design document and is later executed by CouchDB. They live in special member filters under a name of your choice. Here is an example:

フィルタに入りましょう。ビュー関数と同じように、1つのフィルタは1つのJavaScript関数で、デザインドキュメントに格納され、CouchDBによって後で実行されます。filtersというスペシャルメンバに保存され、好きな名前をつけることができます。例を示します。

{
  "_id": "_design/app",
  "_rev": "1-b20db05077a51944afd11dcb3a6f18f1",
  "filters": {
    "important": "function(doc, req) { if(doc.priority == 'high') { return true; }
    else { return false; }}"
  }
}

To query the changes API with this filter, use the filter=designdocname/filtername query parameter:

_changes APIに対して、このフィルタを使ってクエリを行うには、filter=designdocname/filternameをクエリパラメーターに使います。

curl "$HOST/db/_changes?filter=app/important"

The result now includes only rows for document updates for which the filter function returns true—in our case, where the priority property of our document has the value high. This is pretty neat, but CouchDB takes it up another notch.

これは、フィルタ関数がtrueを返す行のドキュメントの更新のみを結果に含めます。私たちのケースでは、priorityプロパティの値がhighの場合です。これは非常に素晴らしいことです。しかしCouchDBはさらに加えます。

Let’s take the initial example application where users can send messages to each other. Instead of having a database per user that acts as the inbox, we now use a single database as the inbox for all users. How can a user register for changes that represent a new message being put in her inbox?

最初の例である、ユーザーが互いにメッセージを送信しあうアプリケーションを取り上げましょう。ユーザー毎に1つのデータベースを持つのではなく、単一のデータベースを全てのユーザーの受信箱とします。ユーザーは受信箱におかれようとしている新しいメッセージを表す変更リストをどうやって取得することができるのでしょうか?

We can make the filter function using a request parameter:

リクエストパラメーターを使うフィルタ関数を登録することができます。

function(doc, req)
{
  if(doc.name == req.query.name) {
    return true;
  }

  return false;
}

If you now run a request adding a ?name=Steve parameter, the filter function will only return result rows for documents that have the name field set to “Steve.” If you are running a request for a different user, just change the request parameter (name=Joe).

リクエストに、?name=Steveというパラメーターを付け加えれば、フィルタ関数はnameフィールドが「Steve」にセットされたドキュメントのみを結果に返すようになります。別のユーザーに対するリクエストを発行する場合は、リクエストパラメーターを(name=Joeのように)変更するだけです。

Now, adding a query parameter to a filtered changes request is easy. What would hinder Steve from passing in name=Joe as the parameter and seeing Joe’s inbox? Not much. Can CouchDB help with this? We wouldn’t bring this up if it couldn’t, would we?

クエリパラメーターをフィルタされた_changesリクエストに追加することは簡単ですが、Steveがname=Joeというパラメーターを渡して、Joeの受信箱を見ようとしないでしょうか?そんなことはないのかもしれませんが、CouchDBはこれに対応できますか?できなければこんなこといいませんよ。私たちですよ?

The req parameter of the filter function includes a member userCtx, the user context. This includes information about the user that has already been authenticated over HTTP earlier in the phase of the request. Specifically, req.userCtx.name includes the username of the user who makes the filtered changes request. We can be sure that the user is who he says he is because he has been authenticated against one of the authenticating schemes in CouchDB. With this, we don’t even need the dynamic filter parameter (although it can still be useful in other situations).

フィルタ関数のreqパラメーターはuserCtxというユーザーコンテキストを表すメンバーを持ちます。ユーザーコンテキストにはHTTPリクエストの前のフェーズで認証されたユーザーの情報を含みます。特にreq.userCtx.nameにはフィルターを使った_changesリクエストを発行したユーザーの名前が入っています。私たちは、CouchDBの認証スキームの1つを用いることで、ユーザーの身元を保証することができます。これを使えば、動的なフィルタパラメーターは必要すらなくなるでしょう(もちろん、別の場面で役立ちますが)。

If you have configured CouchDB to use authentication for requests, a user will have to make an authenticated request and the result is available in our filter function:

CouchDBがリクエストに対する認証を行うように構成すれば、ユーザーは認証済リクエストを発行しなければならなくなり、結果が次のフィルタ関数で利用可能になります。

function(doc, req)
{
  if(doc.name) {
    if(doc.name == req.userCtx.name) {
      return true;
    }
  }

  return false;
}

Wrapping Up

まとめ

The changes API lets you build sophisticated notification schemes useful in many scenarios with isolated and asynchronous components yet working to the same beat. In combination with replication, this API is the foundation for building distributed, highly available, and high-performance CouchDB clusters.

_changes API を用いることで、多くのシナリオで有効な、洗練された通知スキームを構築することができます。そして、複数のコンポーネントを、独立、非同期にしつつも、同じ拍子で動作させることができます。レプリケーションと組み合わせることで、このAPIは、分散された、高可用性があり、ハイパフォーマンスなCouchDBクラスターを構築する基盤となります。