Tag: JavaScript

  • 1億件のデータをJavaScriptで処理した時の所要時間

    ※この記事は備忘に近いので、今後テストの度にこのページを改訂していく可能性があります。

    QA Heatmap Analyticsは膨大なデータを処理することを想定しているので、仮に月間1000万PV近いサイトを分析する場合、1億件以上のデータを処理しなくてはなりません。

    この処理に時間がかかるようでは、実用的ではなく、どのようなクライアントスペックが必要なのか、またどのくらい速度がかかるのかを簡単に検証したいと思いました。

    そこで、シンプルなテストで、まずはChromeブラウザがどれくらいの速度で1億件のデータを処理するのかを計測してみました。

    検証方法

    Chromeを使用し、ある程度QA Heatmap で実際に想定している配列データ(ほとんどInt型で構成。一部文字列。通常DBであれば1レコード最低70Byte程度は必要)を1億件構築し、簡単なテキスト検索を行ってみました。詳しくはテストコードをご覧ください。

    特に検証したかったこと。

    • 1億件の配列の作成にどれくらい時間がかかるか
    • 全てオンメモリするのか
    • ループの書き方でどのくらい速度の違いがあるか?

    検証環境

    • iMac OS macOS Big Sur
      • CPU 3.4GHz クアッドコア Intel Core i5
      • メモリ 40GB
      • ディスク HDD(ただしフュージョンドライブなので一部SSD)
    • Chrome バージョン: 87.0.4280.67(Official Build) (x86_64)
    • メモリ利用量の確認方法
      • Chromeタスクマネージャーでタブに割り当てられたメモリを確認

    結果

    速度について

    作業方法速度
    1億件の配列作成forループ5609ms
    作成直後に文字列検索forループ3964ms
    forEachメソッド※1回目メモリーリーク発生
    5296ms

    速いのはforループです。また5秒程度で1億件処理するのは、結構速いな、と思いました。しかしそれより問題なのは後述するメモリーリークです。

    メモリ利用量について

    フェーズメモリ量
    配列作成前18.4MB
    作成後2.6GB
    10分ブラウザ放置後1.9GB

    普通に計算すれば1億件で700MB程度は最低必須のデータです。こちらをforループで配列作成したところ、ある意味想定通り、数GBのメモリを確保しオンメモリしました。

    あと面白いのは、ブラウザを放置するとメモリ確保量が減っています。ヒープで余分に確保していたメモリなどを最適化できるのだと考えています。しかし今回はその程度のガベージコレクションがあっても、そもそも後述するメモリーリークが大問題なので、どうでもいい話だと思います。

    メモリーリークの発生!

    一回目のforループが終わったあと、すぐにforEachをかけると、22584ループ目で「Paused before potential out-of-memory crash」が発生しました。いわゆるメモリーリークです。

    下記のChromeDevToolの公式サイトを参考に原因の解明を行おうと思ったのですが、、

    ▼メモリ問題の解決(Chrome DevTools)
    https://developers.google.com/web/tools/chrome-devtools/memory-problems/

    なにせ1億件もあるとデバッグも大変で、Heapのスナップショットの取得、描画だけでも異様に時間がかかります。下記のように40GBのメモリを積んでいても関係ないくらいメモリを喰い、スワップ発生しまくりです。

    130GB!?恐ろしい。。

    結局、数時間まっても動かず、どちらにせよ、下記のブラウザのメモリ限界に達した可能性が極めて高く、所感にも記載しますが、そもそも数GBのメモリ確保は現実的ではないので原因の追及はやめることにしました。

    Chromeのメモリ最大容量は4GBらしい

    すごく参考になる検証をされている方がいたので下記にリンクを記載しますが、そもそもChromeのメモリの最大容量は4GBのようです。さらに他のブラウザも検証されていて、Safiriでは1.4GBあたりでクラッシュ。それを考えても数GBのメモリを確保して処理させるのは危険すぎます。

    ▼ブラウザで扱えるメモリ上限の確認 -nodachisoft
    https://nodachisoft.com/common/jp/article/jp000005/

    所感

    メモリ容量が大きいパソコンを使えば、想定通り1GBを超えてもオンメモリはできました。

    しかし、その後が悲惨で、そもそも各ブラウザのメモリの上限が1〜4GBでばらついていることを考えてもメモリリークしないように管理するのは至難の業だと思われます。

    結論として、使用するメモリは合計で1GB以内で想定した方がよさそうですし、できるだけ小さくする方が良いでしょう。

    そうなると、数億というデータ分析は、BigQueryや、TresureDataなどのCDP、クライアントであればTableauなどの専用ツールに任せるのがベターです。そもそも数億のデータを持ちうる企業でデータ活用の文化がないことは考えづらいので、生データをうまくひけるAPIを整備する方が現実的でしょう。

    ちなみにGoogleスプレッドシートの最大処理件数が500万行、Excelで100万行程度らしいので、やはり一億どころか一千万という単位からデータ活用は別次元で考えた方が良さそうです。

    なお今回のテストの副産物として、処理速度を見ると、巨大な配列になるほどforEachなど配列メソッドは遅くなりそうなので、なるべく避けていき、forループなど、ネイティブに近そうなシンプルな処理を組み合わせるのがよさそうだと思っています。

    過去にSafariのDate.Parse()の遅さ(微妙な差なのですがループが多いと効く)でハマったことがあり、なるべくブラウザ依存しなそうな処理を使い、メモリとCPUを想像しながらプログラムを書くことが求められそうです。

    テストコード

    <h1>1億件の配列作成</h1>
    <input type="button" value="start" onclick="datamake();">
    <br>forLoop<input type="text" onchange="findtext(this)">
    <br>forEach<input type="text" onchange="findtext2(this)">
    <script>
        let ary = [];
        function datamake() {
            let now = new Date();
            console.log(now.getTime());
            for (let iii = 0; iii < 100000000; iii++) {
                ary.push([1,	1,	1,	1,	190,	0,	0,	2,	'2020-11-15 13:30:23',	1,	0,	45,	0,	0]);
            }
            let end = new Date();
            console.log(end.getTime());
        }
        function findtext(obj) {
            let findtxt = obj.value;
    
            console.log('for loop');
            let now = new Date();
            console.log(now.getTime());
            let findidx = [];
            for (let iii = 0; iii < ary.length; iii++) {
                //indexOf が一番早い http://oredon.guitarkouza.net/blog/2017/03/javascript-string-match.php
                if (ary[iii][8].indexOf(findtxt) > -1) {
                    findidx.push(iii);
                }
            }
            let end = new Date();
            console.log(end.getTime());
            console.log(findidx.length);
    
        }
        function findtext2(obj) {
            let findtxt = obj.value;
            console.log('for Each');
            now = new Date();
            console.log(now.getTime());
            let findidx2 = [];
            ary.forEach( (rowary,idx) => {
                if (rowary[8].indexOf(findtxt) > -1) {
                    findidx2.push(idx);
                }
            });
            end = new Date();
            console.log(end.getTime());
            console.log(findidx2.length);
        }
    </script>
  • ExcelみたいなフィルターをJavaScriptで。QAフィルターを作成しました!

    2021年8月16日追記。Ver.2.0.0.0より、使い心地はそのままにQAフィルターのデザインが新しく使いやすくなっています。たとえば「どのLPがよく読まれていのか?」をすぐに判断できます。ぜひ活用ください。

    QA Heatmap Analytics Ver.1.0.1.0よりQA Table Generatorを使ったQAフィルターが使えるようになりました。
    表示されているデータの絞り込みがより直感的にできるようになっています。
    詳しくはこちらの動画をご覧ください。

    ※字幕をONにすると説明が表示されます

    解決したかった課題

    一般的なアクセス解析ツールで表示データを絞り込む時には、下記のような課題がありました。

    • 昇順降順ってどっちだっけ?
    • フィルタの存在に気づかない
    • 複数条件で絞り込む方法が直感的ではない。どこにあるかわからない

    優れているExcelのフィルタ

    このようなデータ絞り込み機能の課題を解決している最も優秀なツールはExcelだと思っています。
    Excelのフィルタ機能は直感的で、一度人から教われば誰でも使いこなすことができます。

    私はExcelの構築に関わったJoel Spolskyをとても尊敬しており、Excel自体も尊敬しているので、ぜひこの慣習にならいたいと思いました。

    とはいえ、Excelのフィルターにもいくつかの弱点があります。

    • フィルタ機能の存在を知らないと使えない
    • あまりに高機能で、少しわかりづらくなってきている
    • 複数条件で絞り込んだ時に、どの条件で絞り込んだか迷子になりやすい

    これら全ての課題を解決するために生み出されたのがQAフィルターです。

    QAフィルターの主な特徴

    • 知らない人でも直感的に触れる
    • 必要十分な作業だけ残したフィルタ
    • 全てのフィルタを同時にオープン・クローズできる
    • ウェブ上の操作に特化している(JSONやJavaScriptのデータ配列から動的にTableを生成する)

    現在の仕様

    現在の仕様では、クライアント環境に依存するデータ処理の負荷を下げるため、QA Filterに表示されている領域のみがフィルターの対象になります。例えば、3万件のデータがあるとして、1万件が初期表示されている場合、フィルターの対象はその1万件のみとなります。

    今後この仕様は改訂になる可能性があります。

    使ってみたい方へ

    ぜひQA Heatmap Analyticsプラグインを導入ください。

    プラグインの新規追加で「QA Heatmap」と検索するとみつかります。
    導入するとすぐに表示される「ダッシュボード」のリアルタイムビューにてQAフィルターを活用することができます。

    QAフィルターは100%GPLなので、どなたでも使えます。

    我々はこのQAフィルターをQA Heatmap Analyticsの統一の操作として活用していきたいと思っています。しかしQAフィルター自体は、汎用的なJavaScriptライブラリとして開発していきます。(その方が我々も使いやすいので)

    まだまだ汎用的ではなく突貫的に対応している箇所もありますので、しばらくはQA専用で開発していきますが、現時点でもGPLが適用されていますので、どなたでも利用は可能ですし、自由に改変もできます。※GPL適用が必須です。

    cssも現在はソースの中に記載しているので、「table.js」だけで利用できます。
    ソースはWordPress公式サイトのこちらにあります。

    https://plugins.svn.wordpress.org/qa-heatmap-analytics/trunk/js/

    なおライセンスはGPL2.0ですので、それに従う必要があります。
    GPLについて詳しく知りたい方はこちらの記事をご覧ください。

    ただ今のところQAフィルターはQA Heatmap Analytics向けに最適化していきますし、Font Awesomeを利用しているのでそのクレジットも必要です。ですから、しばらくして汎用化したら、そのタイミングでGitHubで公開し、ライセンス関連も整理したらMITライセンスによるオープンソース化も検討しています。

    もしバグなど見つけた方は、教えて頂けると助かります。(修正コードがある方は教えてもらえるともっと助かりますw)