プログラム

loopbackで作成されたAPIのアクセスコントロールを設定する

の様なモデルのリレーションを定義し、APIも自動的に作成され /rempusers/:id/libraries といった形でアクセスできる様に
なったのですが、ログインして得られるアクセストークンを付与しても現状だと401応答が帰って来ます。

$ http http://localhost:5000/api/RempUsers/55486573fecad3d7215ead80/libraries?access_token=*****
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Credentials: true
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/json; charset=utf-8
Date: Sun, 10 May 2015 01:52:10 GMT
ETag: W/"DNoQruYc4EoovuVyGTv+hg=="
Transfer-Encoding: chunked
Vary: Origin, Accept-Encoding
X-Powered-By: Express
{
"error": {
"code": "AUTHORIZATION_REQUIRED",
"message": "Authorization Required",
"name": "Error",
"status": 401,
"statusCode": 401
}
}

これはloopbackで既定されているUserモデルを継承したモデルにはユーザ自身の情報のみが制御できる様なアクセスコントロールが設定されているため、
そのモデルとリレーションするモデルをAPI経由で呼びだそうとすると制限がかけられています。*1

この問題を解消するためのアクセスコントロールを設定します。

loopbackでアクセスコントロールを設定するためのscafoldは slc loopback:acl というコマンドで実現できます。

今回対象となるAPIのエントリポイントはモデルのメソッド的にはgetに対応するためmethod nameとしては __get__libraries を指定します。

$ slc loopback:acl
? Select the model to apply the ACL entry to: RempUser
? Select the ACL scope: A single method
? Enter the method name: __get__libraries
? Select the role: The user owning the object
? Select the permission to apply: Explicitly grant access

具体的にモデル間のリレーションを組んだ時にどの様なメソッドが自動生成したモデルに備わっているかはドキュメントを参照するとわかりやすいです。

アクセスコントロールを設定した後に再度アクセスしてみると、

$ http http://localhost:5000/api/RempUsers/55486573fecad3d7215ead80/libraries?access_token=****
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Connection: keep-alive
Content-Length: 329
Content-Type: application/json; charset=utf-8
Date: Sun, 10 May 2015 07:27:01 GMT
ETag: W/"149-11634253"
Vary: Origin, Accept-Encoding
X-Powered-By: Express
[]

今度は問題なく取得できています。

なんとなくloopbackの世界が少しだけ見えてきました。

参照

Accessing related models - LoopBack - Documentation

*1:表現が難しい....

loopbackでモデルのrelationを定義する

loopbackでモデルを作成するときslcコマンドで作成できるのだけど、
モデル間のリレーションを同様にscafoldするやり方のメモです。

ここでは1対多となるモデルのリレーションの定義をslcコマンドで行ってみます。
例としてRempUserというユーザが複数のプレイリストを持つケースを想定します。

ユーザのモデルは既に作成されているものとして、プレイリストを管理するモデルを復習がてら作成します。

$ slc loopback:model
? Enter the model name: Playlist
? Select the data-source to attach Playlist to: db (mongodb)
? Select model's base class: PersistedModel
? Expose Playlist via the REST API? Yes
? Custom plural form (used to build REST URL):
Let's add some Playlist properties now.
Enter an empty property name when done.
? Property name: title
invoke   loopback:property
? Property type: string
? Required? Yes
Let's add another Playlist property.
Enter an empty property name when done.
? Property name: published
invoke   loopback:property
? Property type: boolean
? Required? Yes
Let's add another Playlist property.
Enter an empty property name when done.
? Property name:

次に先ほど作成したプレイリストのモデルとユーザモデルの間のリレーションを定義します。

これもscafoldできて slc loopback:relation というコマンドで実現できます。

$ slc loopback:relation
? Select the model to create the relationship from: RempUser
? Relation type: has many
? Choose a model to create a relationship with: playlist
? Enter the property name for the relation: playlists
? Optionally enter a custom foreign key:
? Require a through model? No
$ slc loopback:relation
? Select the model to create the relationship from: playlist
? Relation type: belongs to
? Choose a model to create a relationship with: RempUser
? Enter the property name for the relation: RempUser
? Optionally enter a custom foreign key:

このコマンドで具体的に行われるのは common/models/*.json で保管されている各モデルの定義が記載されているJSONrelations という
要素に定義が追加されます。

実際に該当のJSONファイルを見てみると、例えばユーザモデルであれば、

{
"name": "RempUser",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {},
"validations": [],
"relations": {
"playlists": {
"type": "hasMany",
"model": "Playlist",
"foreignKey": ""
}
},
"acls": [],
"methods": []
}

といった具合にプレイリストモデルとのリレーションの定義が追加されていることがわかります。

また、loopbackの特徴であるモデル定義がそのままAPIとして反映されているので、loopbackのAPI Explorerを介して該当モデルの
エントリポイントを確認してみると、

といった形でCRUDできるAPIが作られています。便利。

ただ、今回の例の場合、新しく作成されている /RempUsers/:id/playlists といったエントリポイントにアクセスをしても HTTP/1.1 401 Unauthorized となってしまいます。

これは、loopbackで設けられているAPIに対するアクセスコントロールによるものなのですが、この辺りを解決するやり方はまた次回。

参考

StrongLoop | Defining and Mapping Data Relations with LoopBack Connected Models

loopbackでAPIにアクセスした際のACLの適用状況を確認する

loopbackで作成したAPIには、ユーザのロールに応じてアクセスコントロールを指定することができます。

Controlling data access - LoopBack - Documentation

例えば、

  • adminユーザのみアクセス可
  • このロールのユーザのみアクセス可
  • 認証済のユーザであればアクセス可

といった様なアクセスコントロールをslcコマンドでscaffoldすることができます。(やっていることはmodelの定義をしているJSONにアクセス権限の設定を付与している)

この設定を簡単にチェックするためにアプリケーションログとして標準出力するにはloopbackアプリケーション指定時に以下の環境変数を指定することで実現できます。

$ DEBUG=loopback:security:acl slc run

といった形でloopbackアプリを起動するとloobackで作成したAPIにアクセスした際にそのAPIへ適用されているアクセスコントロールの適用状況を確認することができます。

例えば RempUser というモデルがあり、/api/RempUsers/{id} というエントリポイントに対してユーザ情報を取得するためGETリクエストを発行した場合、発行と同時にアクセスコントロールの適用状況がログとして標準出力に出力されます。

$ http http://localhost:5000/api/RempUsers/1?access_token=pIIxAUP6qn2OtXntEGWO6KxAU71qDSS14A1qZamwTTk2xZEOppdtUVRM3agrdoQ8

といった形でリクエストを発行すると、以下の様なログが出力されます。

loopback:security:acl The following ACLs were searched:
loopback:security:acl ---ACL---
loopback:security:acl model RempUser
loopback:security:acl property findById
loopback:security:acl principalType ROLE
loopback:security:acl principalId $owner
loopback:security:acl accessType *
loopback:security:acl permission ALLOW
loopback:security:acl with score: 8016
loopback:security:acl ---ACL---
loopback:security:acl model RempUser
loopback:security:acl property *
loopback:security:acl principalType ROLE
loopback:security:acl principalId $everyone
loopback:security:acl accessType *
loopback:security:acl permission DENY
loopback:security:acl with score: 7495
loopback:security:acl ---Resolved---

この出力を確認することでloopbackが該当のエントリポイントに対してどういった判断が行われてアクセス可/不可の判定を行ったか確認することができます。

loopbackでMongoDBを利用する

loopbackでアプリケーションを作成した直後はデータストレージにmemoryが設定されているため、そのままだとアプリケーションが再起動するたびにmodelの内容が初期化されてしまうため、保管できる様にMongoDB*1を利用できる様にします。

作成したアプリケーションルートでnpmコマンドを叩きます。

$ npm install loopback-connector-mongodb --save

www.npmjs.com

MongoDBに接続するためのコネクタがpackage.jsonに追加されて必要なnpmモジュールがインストールされたのを確認した後、server/datasource.json にある
データベースへの接続設定を更新します。

{
"db": {
"name": "db",
"connector": "mongodb",
"host": "127.0.0.1",
"database": "hogehoge_development",
"username": "",
"password": "",
"port": 27017
}
}

必要な設定はこれだけになります。

先日作成したユーザ登録のAPIを呼び出してみます。

☁  http POST http://0.0.0.0:3000/api/users password=hogehoge email=hideack@remp.jp
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Connection: keep-alive
Content-Length: 59
Content-Type: application/json; charset=utf-8
Date: Mon, 04 May 2015 06:30:46 GMT
ETag: W/"3b-84c0720e"
Vary: Origin, Accept-Encoding
X-Powered-By: Express
{
"email": "hideack@remp.jp",
"id": "554712159967a7b3efce0f0c"
}

MongoDBで永続化されているかを確認してみます。

素朴に mongo コマンドでMongoDBに接続して確認します。

> show collections
User
system.indexes
> db.User.find()
{ "_id" : ObjectId("554712159967a7b3efce0f0c"), "password" : "$2a$10$AzdK8q7nUIHxcoiW5y17Lusb4eag7VOjwQI.e5ukjJRWKMWXSaP6a", "email" : "hideack@remp.jp" }
>

loopbackで作成したアプリケーションで操作されたmodelの内容がMongoDBで永続化される様になりました。

併せて読みたい

過去のloopbackに関する記事

*1:REMPもSTOBOもストレージはMongoDBです

Node Foremanで複数のプロセスを同時に起動する

Rubyで開発している時に開発環境で複数のプロセスをまとめて起動させたいときにforeman
利用することが多いですがNode.jsの実装でNode Foremanがあります。

www.npmjs.com

npmコマンドでNode Foremanをインストールします。

$ npm install -g foreman

コマンド自体は nf となります。

$ nf
_____
|   __|___ ___ ___ _____ ___ ___
|   __| . |  _| -_|     |   |   |
|__|  |___|_| |___|_|_|_|_^_|_|_|
Usage: nf [options] [command]
Commands:
start [options]        Start the jobs in the Procfile
run [options]          Run a one off process using the ENV variables
export [options]       Export to an upstart job independent of foreman
Options:
-h, --help             output usage information
-V, --version          output the version number
-j, --procfile <FILE>  load procfile FILE
-e, --env      <FILE>  use FILE to load environment
-p, --port     <PORT>  start indexing ports at number PORT

では、実際に複数のプロセスをNode foremanで起動してみます。

Procfile自体はRubyのforemanと同じなので準備します。ひとまず開発用ということで Procfile.development というファイル名で以下の様な内容を準備します。

サンプルとして先日作成したloopbackアプリ + MongoDBを動かしてみます。

web: node .
mongo: mongod --dbpath=./db

上記の様なProcfileを用意したらNode Foremanを起動します。

$ nf start -j Procfile.development

上記のコマンドでForemanを起動すると、複数プロセス(loopbackアプリとMongoDB)が起動します。

Node.jsのアプリでforemanを使いたいとき、npmの範囲でカバーできるので便利ですね。

loopbackでアプリケーションを作成してユーザ登録APIを作るまで

StrongLoopが開発したloopbackは、Node.jsで作られたフレームワーク(所謂mBaaSのオープンソース実装)で以下の様な特徴があります。

  • RESTなAPIをモデルと対応させて素早く作れる (モデルを作るとCRUDするAPIが出来上がる)
  • モデルはコネクタを介して様々なDBで永続化できる
  • API自体は application/jsonAPIとして操作でき、各種クライアント向けのSDKを備えている

といった特徴があります。

利用実績としては、GoDaddyでpublic reseller APIに利用されている様です。

StrongLoop | GoDaddy Selects StrongLoop to Power Reseller API Platform

SPAなアプリケーションを2011年からREMPSTOBO
そしてRendrを利用してCastoを作ってきましたが、もしかしたらloopbackを使ってAPI開発をよりスムーズに
解決できるのではないか。という期待を抱きつつ試しに触ってみました。

ひとまずゴールとしては、

loopbackでアプリケーションを作成し、APIを介してユーザを作るところまで

とします。

準備

とにもかくにも strongloop コマンドをインストール。*1

$ npm install -g strongloop

これで slc コマンドが使える様になってscaffoldできます。

アプリケーション作成

実際にアプリケーションを作成してみます。(謎のキャラクターが表示されます)

☁  slc loopback remp-api
_-----_
|       |    .--------------------------.
|--(o)--|    |  Let's create a LoopBack |
`---------´   |       application!       |
( _´U`_ )    '--------------------------'
/___A___\
|  ~  |
__'.___.'__
´   `  |° ´ Y `
? What's the name of your application? remp-api
? Enter name of the directory to contain the project: remp-api
create remp-api/
info change the working directory to remp-api
Generating .yo-rc.json
I'm all done. Running npm install for you to install the required dependencies. If this fails, try running the command yourself.
create .editorconfig
create .jshintignore
create .jshintrc
create README.md
(snip)
Next steps:
Change directory to your app
$ cd remp-api
Create a model in your app
$ slc loopback:model
Optional: Enable StrongOps monitoring
$ slc strongops
Run the app
$ slc run .

ユーザーモデルを作成

続いてユーザーを管理するためのモデルを作成してみます。

loopbackではいくつかのbase classが用意されていてその中にUserというクラスが存在するので今回はこれをこのまま利用します。

基本的なユーザ管理をする場合であればbase classのままでよいのですが、何か新たにプロパティ足したい場合は追加をすることもできます。今回はスキップ。

☁  slc loopback:model
? Enter the model name: user
? Select the data-source to attach user to: db (memory)
? Select model's base class: User
? Expose user via the REST API? Yes
? Custom plural form (used to build REST URL):
Let's add some user properties now.
Enter an empty property name when done.
? Property name:
☁  

上の操作で以下の様な要素を持ったユーザ管理のモデルが作成できます。

要素 -
realm (string, optional)
username (string, optional)
credentials (object, optional)
challenges (object, optional)
email (string)
emailVerified (boolean, optional)
verificationToken (string, optional)
status (string, optional)
created (string, optional)
lastUpdated (string, optional)
id (number, optional)

APIを呼び出してみる

では、モデルを作成 = API作成 ができたので実際にHTTPクライアントからAPIを呼び出してみます。

loopbackアプリケーションを起動します。

☁  node .
Browse your REST API at http://0.0.0.0:3000/explorer
Web server listening at: http://0.0.0.0:3000/

おもむろにloopbackアプリケーションを呼び出します。

先ほど作成したUserモデルに対応するAPIのエントリポイントは /api/users に対応しますので、そこへメールアドレスとパスワードをPOSTします。

☁  http -v POST http://0.0.0.0:3000/api/users password=hogehoge email=hideack@remp.jp
POST /api/users HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
Content-Length: 52
Content-Type: application/json; charset=utf-8
Host: 0.0.0.0:3000
User-Agent: HTTPie/0.8.0
{
"email": "hideack@remp.jp",
"password": "hogehoge"
}
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Connection: keep-alive
Content-Length: 34
Content-Type: application/json; charset=utf-8
Date: Sat, 02 May 2015 05:27:48 GMT
ETag: W/"22-3085cb67"
Vary: Origin, Accept-Encoding
X-Powered-By: Express
{
"email": "hideack@remp.jp",
"id": 2
}

ステータスコード200が帰って来てユーザを作成することができました。

ログインしてみる

では、先ほど作成したユーザでログインしてみます。ユーザ作成の際に利用したメールアドレスとパスワードを要素としてPOSTします。

ログイン用のエントリポイントは /api/users/login なので、ここにユーザ登録の際に設定したメールアドレスとパスワードを渡します。

☁  http -v POST http://0.0.0.0:3000/api/users/login email=hideack@remp.jp password=hogehoge
POST /api/users/login HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate
Content-Length: 52
Content-Type: application/json; charset=utf-8
Host: 0.0.0.0:3000
User-Agent: HTTPie/0.8.0
{
"email": "hideack@remp.jp",
"password": "hogehoge"
}
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Connection: keep-alive
Content-Length: 135
Content-Type: application/json; charset=utf-8
Date: Sat, 02 May 2015 05:30:16 GMT
ETag: W/"87-4fc8ab24"
Vary: Origin, Accept-Encoding
X-Powered-By: Express
{
"created": "2015-05-02T05:30:16.742Z",
"id": "r3YFqmceF1INk5FlstmZgO7p2y42TyLYPlzSchPIDIDBlAbuOHQurS5j5gOSH82h",
"ttl": 1209600,
"userId": 2
}

ステータスコード200が帰って来て、API操作用のトークンが戻ってきました。なお、パスワードが誤ると、

HTTP/1.1 401 Unauthorized
{
"error": {
"code": "LOGIN_FAILED",
"message": "login failed",
"name": "Error",
"stack": "Error: login fail...",
"status": 401,
"statusCode": 401
}
}

となって期待通りです。

ログイン時のトークンを利用してユーザ情報を取る

続けて先ほどログイン時に取得したトークンを利用してユーザの情報を取得してみます。

現状だとUserモデルしか存在しないのでできることが少ないですがログインしているユーザ自身の情報を取ってみます。

/api/users/2?access_token=(ログイン時に得たtoken) に対してGETします。

☁  ~  http http://0.0.0.0:3000/api/users/2\?access_token\=r3YFqmceF1INk5FlstmZgO7p2y42TyLYPlzSchPIDIDBlAbuOHQurS5j5gOSH82h
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Connection: keep-alive
Content-Length: 34
Content-Type: application/json; charset=utf-8
Date: Sat, 02 May 2015 05:45:01 GMT
ETag: W/"22-3085cb67"
Vary: Origin, Accept-Encoding
X-Powered-By: Express
{
"email": "hideack@remp.jp",
"id": 2
}

scafoldする際に特にプロパティを追加していないのでidとemailのみ返ります。

API Explorer

ここまでコマンドラインのHTTPクライアントでAPIを操作してみましたが、実はscafoldした時点でStrong Loop API ExplorerというWebアプリが同時に起動しています。

http://0.0.0.0:3000/explorer

へブラウザでアクセスすると以下の様な画面を閲覧できて実際にAPI操作も行うことができます。

まとめ

loopbackを利用して、アプリケーションの作成とユーザを管理するモデルの作成(それと同時にユーザ操作をするAPIの作成)を行ってみました。

気づかれたかもしれませんが、上までの操作だとモデルを保管する先のストレージを何も設定していない状態なのでアプリケーションの再起動で全て消えます...。

データベースの接続はまた次の機会に。

*1:内部的にYeomanを使っているのでYeomanを既に使っていたら npm install -g generator-loopback

RunscopeでAPIの応答を監視する

Runscopeとは

Runscopeは2013年にローンチしされたAPI監視サービスです。

www.runscope.com

メリットとしては、"本番環境で提供しているAPIのステータス監視及び応答内容監視ができる" があります。

単純な死活監視ではなく、APIが返答するJSONXML, HTTPステータスなどの応答内容の監視まで行うことで仮に提供中のAPIから更に外部のAPIと連携しているなどしていた場合、外部のAPIの異常を検知できずにサービスが稼働を続けるということを防ぐことができます。

30日間, 25万リクエストまでであれば無償で試すことができます。

設定

ログイン直後の上部メニューの "Radar Tests" → "Create Test" を指定し、テストグループを作成した後、作成されたテストで "Edit Test" をクリックします。

そうすると下記の様なUIが表示されるため、設定自体はものすごくシンプルでリクエスト対象のURLを指定します。

API呼び出し時に認証がかかる場合は、BASIC認証及び、OAuthのキーも設定することができます。

"Assertions"のタブで期待する応答を指定します。チェックできる項目は以下の内容です。

  • JSON Body
  • XML Body
  • Response Header
  • Text Body
  • Status Code
  • Response Time (ms)
  • Response Size (bytes)

それぞれの内容について期待値を指定しそれを満たしているか否かというチェックが行えます。

例えば、あるAPIを呼び出された時に....

といった所謂APIで監視したくなる要素についてチェックができる様になっています。

この様に各テストを追加していくと各URLのエントリポイントの状態がダッシュボード上で一覧することができます。

テストは毎分〜1日1回の間で実行間隔を設定できる様になっており、また実行結果はSlackに通知させることも可能です。


Runscopeを利用すると死活監視とAPIの応答内容監視も行うことができてサービス内(あるいは会社内等)で複数API外部から利用されたた際にどの様な状態を返しているか という点を主体において監視する際に役立ちそうな印象でした。

また、API内部で更に第三者が運営する別のAPIを呼び出す等の連携が行われている場合、その部分の監視も併せて行えていることになるため、副次的なメリットも大きいかと思いました。

nconfでYAMLファイルを扱える様にする

evacでは、アグリゲータとしての挙動をJSONファイルで定義する様にしていて、その内容の解釈にnconfを利用しているのだけど、YAMLもフォーマットとしてサポートさせたいので試してみたのでその際のメモ。

nconf自体はパーサーを外部に指定できる仕組みがあるので以下の様に組み合わせるとYAMLJSONを扱うのと同様に解釈できた。

YAMLのパーサーには以下を利用した。

nodeca/js-yaml · GitHub

YAMLファイルを適当に準備。

in:
staticWord:
text: "test word."
filter:
through:
out:
stdout:

これをnconfと組み合わせてYAMLを設定ファイルとして解釈させるには以下の様にすればよい。

var config = require('nconf');
var yaml = require('js-yaml');
config.use('file',{
file:'/home/hideack/foo.yaml',
format:{
parse: yaml.safeLoad,
stringify: yaml.safeDump
}
});
config.load(function (err, conf) {
console.log(conf);  //{ in: {staticWord: {text:'test word.'}},filter: {through: null},out: {stdout: null}}
});

JSONで設定ファイルだとちょっと...といったケースでYAMLを使いたいときに試してみると良さそう。

node.jsでGoogle analyticsレポートの値を取得する

Google analyticsのレポートの値を取得する方法をあれこれ調べていたのでひと通りまとめてみました。

npmのパッケージとしては、sfarthin/ga-analytics · GitHubというのがあるので、これを利用するとあっさりレポートの値は取れるのですが、Google analyticsAPIを利用するため、以下の手順が必要です。

  1. Google Developer Consoleでサービスアカウントを準備する
  2. Google analyticsにユーザを追加する
  3. Developer consoleで取得した.p12証明書をpem形式に変換する

上の手続きをひと通り行うとGoogle analyticsのレポートの値を取得できる様になります。


Google Developer Consoleでサービスアカウントを準備する

Google Developer Console にログインしてまずは新規プロジェクトを作成します。

次にGoogle analyticsAPIを選びます。APIの利用ON/OFFをONに切り替えます。

次に左側のメニューにある「APIと認証」の「認証情報」を選択して、新しいクライアントIDの作成を行います。

上記の手続きが完了すると以下の画面が表示されるので

  • ①クライアントID
  • ②メールアドレス
  • ③自動的にダウンロードされる.p12(PKCS12)形式の証明書
  • ④パスワード*1

を控えます。

Google analyticsにユーザを追加する

Google developer console 上からAPIを利用できる状態にしても、Google analyticsにユーザを追加しないと実際にGoogle analyticsのパラメータを取得することはできないので、Google analytics上からユーザを追加します。

Google analyticsの「アナリティクス設定」を開いて

上記の箇所に先のDeveloper consoleで追加されたメールアドレス(〜@developer.gserviceaccount.com) を入力してユーザとして追加します。

続けて実際に取得したいGoogle analyticsのレポートを識別するためにビューIDという値があるので

を開くと、以下の箇所にビューIDがあるのでこれを控えます。

  • ⑤取得したいレポートのビューID

Developer consoleで取得した.p12証明書をpem形式に変換する

OpenSSLのコマンドを利用して証明書の形式を変換します。パスワードの部分には④で表示されていたものを入力します。

openssl pkcs12 -in key.p12 -nocerts -passin pass:***** -nodes -out key.pem

ここで出力された証明書ファイルのパスを控えておきます。

  • ⑥PEM形式に変換された証明書ファイル

ga-analyticsを利用してGoogle analyticsの値を取得

ここまでで準備は完了したので実際にsfarthin/ga-analytics · GitHubを利用してレポートの値を取得してみます。

試しに恥ずかしながらこのブログのページビューを...。

$ npm install ga-analytics --save

として ga-analytics のインストールを行った上で

var gaAnalytics = require("ga-analytics");
var CLIENT_ID = '****.apps.googleusercontent.com';   // ①
var SERVICE_ACCOUNT_EMAIL = '****@developer.gserviceaccount.com';   // ②
var SERVICE_ACCOUNT_KEY_FILE = '/home/hideack/key.pem';   // ⑥
gaAnalytics({
metrics: "ga:pageviews",
clientId: CLIENT_ID,
serviceEmail: SERVICE_ACCOUNT_EMAIL,
key: SERVICE_ACCOUNT_KEY_FILE,
ids: "ga:*****"   // ⑤
}, function(err, res) {
if(err) throw err;
console.log(res.totalsForAllResults);
});

これで出力してみると。

{ 'ga:pageviews': '1973' }

といった形。実際にAnalyticsでレポートの値を確認すると同じ値が取れることがわかります。

*1:多分だけどこれ固定ですね...

転置インデックスを利用した検索とgrepによる検索を比較してみる

検索エンジン自作入門 ~手を動かしながら見渡す検索の舞台裏を読んでみたところから続く検索してみるシリーズ。

前回作成した検索の処理をもって転置インデックスを作成するところから、それを利用した検索までの一連の流れができたので次は実際にいくらか大きめのデータを入れて比較してみることにします。

いままで作成したnode.js + redisベースの実装はremieraという名前を付けてgithubに置いています。一応、コマンドライン引数でインデックス対象のファイルを指定したり検索ができる様にしています。

インデックス対象の文章はテキストファイルで与えられるものとして、1行が1文章として扱われる様になっています。

今回は大きめのデータのサンプルということで本に倣ってWikipediaのタイトル2,654,075件の文字列をサンプルとして投入します。

$ wc -l ~/tmp/wikipedia/jawiki-latest-all-titles
2654075
$ time remiera -i ~/tmp/wikipedia/jawiki-latest-all-titles
remiera -i ~/tmp/wikipedia/jawiki-latest-all-titles  1011.13s user 229.40s system 99% cpu 20:44.89 total

redis自体もコード自体も何もチューニングをしていない状態で20分程度要しました。

では、まずは比較として先ほどインデックス対象としたファイルを素朴にgrepで検索してみます。

$ time grep -n コロスケ ~/tmp/wikipedia/jawiki-latest-all-titles
969851:奈良崎コロスケ
2180964:コロスケ
2180965:コロスケ123123
2180966:コロスケ准将
2180967:コロスケ@1億円
grep -n コロスケ ~/tmp/wikipedia/jawiki-latest-all-titles  1.46s user 0.01s system 99% cpu 1.479 total

grepでは1.4秒強程度要しています。

次は転置インデックスを使って検索を行ってみます。

$ time remiera -s コロスケ
[ { no: '969850', pos: 3 },
{ no: '2180964', pos: 0 },
{ no: '2180965', pos: 0 },
{ no: '2180963', pos: 0 },
{ no: '2180966', pos: 0 } ]
remiera -s コロスケ  0.09s user 0.03s system 74% cpu 0.156 total

現状の実装では文書番号と出現位置で返るだけですが、0.09秒程度*1で結果が得られています。

インデックスする際に文書番号は0から始めているため 行番号 = 文書番号-1 となっています。これが先ほどのgrepした結果と検索結果とが一致していることもこの結果から確認できます。

一旦まとめ

ここまでやったこととして、検索エンジン自作入門 ~手を動かしながら見渡す検索の舞台裏を読みながら以下のことを試してみました。

ここまでで、本でまとめられている第4章までの内容をnode.js + redisで実装をしながら進めることができました。

更に次のステップとして更に大きな文書を投入しての計測や5章以降で触れられているパラメータの調整等々も試していきたいと思いますがこれはまた次に...。

あわせて読みたい

*1:本で提供されているwiserと比較したら大分遅いですね...