Engineer's Way

主にソフトウェア関連について色々書くブログです。

Node.jsのforEachとmapの速度を比較してみた

 

背景

仕事でNode.jsで書かれたコードのPull Requestを見ていて、forEachで新しい配列を生成しているのを見かけました。
そこで、forEachとmapのどちらが速いか少し気になったのでGoogleで調べてみましたが、調べ方が悪かったのかNode.jsでの比較結果を見つけられなかったので、自分で比較してみました。

比較方法

数値1が100万個入った配列をもとに、文字列が100万個入った別の配列を生成する処理を、forEachとmapのそれぞれで1000回実行。 (コード全文は#今回使ったコード参照)

  • forEachの処理
  const arr2 = [];
  arr1.forEach((elem) => {
    arr2.push(`${elem} hoge`);
  });
  • mapの処理
const arr2 = arr1.map((elem) => {`${elem} hoge`});

環境

  • Node.js:v8.9.1 LTS
  • MacOS High Sierra 10.13.2
  • プロセッサ名:Intel Core i7 2.9 GHz (4コア)
  • メモリ:16 GB 2133 MHz LPDDR3

結果

forEach map
max 270.58ms 330.12ms
min 165.22ms 250.97ms
average 210.99ms 278.82ms

おおよそforEachで処理する方が20%程度速いという結果になりました。
forEachとmapでは使いどころが違ってくるので、どちらが良いか悩む場面は少ないと思いますが、どっちでもいいような時はforEachの方が良いかもしれません。

今回使ったコード

Compare the throughput of Array.forEach and Array. ...

ElasticSearchで親-子-孫の3世代ドキュメントを作って、子や孫の内容で検索する

 

f:id:matsnow:20171204030753p:plain

前職の時、AWSのElasticSearchService (Ver5.3) を使っていましたが、日本語どころか英語でもあまり情報がなかったりするので苦労しました。
RDBでよくある、テーブル結合して検索ということがしたかったのですが、手間取ったので方法を残しておきます。

ElasticSearchで3世代ドキュメント構造を持ったindexを作る

たとえば、「商品カテゴリ - 商品 - 商品のレビュアー」みたいに、それぞれのデータに1:nの関係性を持たせたい場合、RDBなら外部キーを使ってJOINすればOKです。 ElasticSearchで、そういった構造を表現したい場合、_typeを使います。

親の方から1,2,3世代とするとき、1世代目、2世代目、3世代目に該当する_typeの名前を決めて、インデックスを設計します。 上の例の場合、名前をcategoryproductreviewerとするなら、以下のようなmappingのJSONをインデックスに適用しておきます。

PUT /market

{
  "mappings": {
    "category": {},
    "product": {
      "_parent": {
        "type": "category" 
      }
    },
    "reviewer": {
      "_parent": {
        "type": "product" 
      }
    }
  }
}

RDBで各テーブルのレコードに外部キーを持たせるのと違う点は、「あくまで1つのindexの中にフラットなデータとして入っている」ということです。 要は_idの重複などを気にしないといけません。

3世代ドキュメント構造を持ったindexにデータを放り込む

  1. 2世代目のドキュメントを放り込む時、_parent: 1世代目の_id(例:_parent: "c1")を追加で渡します。
  2. 3世代目のドキュメントを放り込む時、_parent: 2世代目の_id, _routing: 1世代目の_id(例:_parent: "p1", _routing: "c1")を追加で渡します。

これを_bulkAPIに渡すJSONとして表現するなら

{ "index": { "_index": "market", "_type": "category", "_id": "c1" } }
{"category_name": "clock" }
{ "index": { "_index": "market", "_type": "product", "parent": "c1", "_id": "p1" } }
{"product_name": "clock1" }
{ "index": { "_index": "market", "_type": "reviewer", "parent": "p1", "routing": "c1", "_id": "r1" } }
{"reviewer": "hoge" }

みたいになります。

こうすることで、index内が、図で表すと以下のようなドキュメント構造になります。 f:id:matsnow:20171204030054j:plain

子ドキュメントで検索し、親・孫ドキュメントを含めた一連のドキュメントを取得する

これがあまり情報がなかったのですが、以下のようなクエリを書けば子ドキュメントのデータで検索した上で、3世代分のドキュメントを結合して一気に返してもらうことができます。

{
    "query": {
        "has_child" : {
            "type": "product",
            "query": {
                "bool": {
                    "must": [
                      {
                         "term": {"product_id": "123"}
                      },
                      {
                         "has_child": {
                            "type": "reviewer",
                            "inner_hits": {},
                            "query": {"match_all": {} }
                         }
                      }
                   ]
                }
            },
            "inner_hits": {}
        }
    }
}

boolクエリで、2世代目への検索クエリ(term)と、3世代目の全件取得(has_child.query.match_all)をまとめるのがポイントです。 ※ もちろん、3世代目で更に絞り込みをしたければ、match_allの代わりに別のクエリを使います。

それと、inner_hitsを書くことで、2、3世代目のドキュメント一式を取得できます。 書き忘れると1世代目のドキュメントしか返ってきません。

注意点

公式見解として、今後は1つのindexに対して1つのtypeが推奨されています。
また、親子関係をもたせたいときは5.6から追加されたjoinフィールドを使って欲しいとのこと。
今後のロードマップによると、バージョン8.xあたりでtypeが事実上消滅するようです。(まだまだ先の話でしょうが。。)

www.elastic.co

AWSだと、12/6時点でもまだ5.5までしか選択できないですが、5.6以降がサポートされたら単一type & joinフィールドを使う方向で設計した方が良さそうですね。

Electronでパッケージを作ろうとした時に「May not delete」というエラーが出る

 

electron-packagerでElectronでパッケージを作ろうとした時、以下のエラーが発生しました。  

$  electron-packager . sample --platform=darwin --arch=x64 --overwrite --out build"
npm ERR! May not delete: /private/var/folders/q3/hg9ht5vx5rjddgghnk8z9w480000gn/T/electron-packager/darwin-x64/sample-darwin-x64/Electron.app/Contents/Resources/app/node_modules/.bin

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/hogehoge/.npm/_logs/2017-11-14T10_35_53_001Z-debug.log

npmのバージョンが問題だったらしく、5.3だとダメでした。
内部で実行しているnpm prune --productionがやらかしている模様。

github.com

なお、最新の5.5.1ならOKでした。