web-dev-qa-db-ja.com

処理方法Vue 2大規模データのメモリ使用量(〜50000オブジェクト)

私はVue 2.の半複雑なオブジェクトの大規模なコレクションのテーブルビューを実装しようとしています。基本的には、アイデアはDBからJSキャッシュに50 000〜100 000行を収集することですテーブル内の各行は切り替え可能です。つまり、行をクリックすると行が編集モードに変更され、Excelのような編集が可能になります。その特定のフィールド/セル。

各オブジェクトには約100〜150のフィールド/プロパティがありますが、テーブル内の特定の時点で表示されるのは特定の量だけです(テーブルの列はリアルタイムで切り替えることができます)。大規模なデータセットの場合、DBは約10〜100 MBのJSONデータをプッシュしているように見えますが、このユースケースでは許容できます。レンダリングに関しては、パフォーマンスは問題になりません。フィルターは十分に高速に動作し、限られた量の結果のみがDOMにレンダリングされます。

すでに機能し、フィルター、フィルターに対して最大100行のリスト(+「100以上を表示」など)が機能していますが、約8000個のオブジェクトを配列に読み込んだときにメモリ制限に達しました。これは2ギガバイトのRAMを予約しているようで、Chromeの後にJS-codeの実行をすべて停止します(奇妙なことに、警告やエラーは表示されませんが)。

行のメモリ使用量のベンチマークを行ったところ、約1000行が約300 MBのメモリを予約しているようです。これは、おそらくVue反応性オブザーバーによって予約されています。

3つの質問:

  1. 特定の配列リストオブジェクトの反応性を(インデックスなどによって)切り替える方法はありますか?そのため、配列自体の中のオブジェクトは、特に変更可能になるように呼び出されない限り、観察されない/不変です(つまり、ユーザーが行をクリックすると、モード)?
  2. 反応性がメモリ使用量のボトルネックになっているように見えるため、Vueの大きなデータセットの処理をどのように実装しますか? ここで探しているソリューションではないため、「バックエンド内の結果を制限する」ことを提案しないでください(小さな初期データセットを取得するための2つの部分のフィルタリングを作成する必要がある場合でもリアルタイムフィルタリング用)。基本的に、Vueでデータアーキテクチャを再考することで、「メモリの終わり」の境界を8 000-> 80 000からプッシュしようとしています。データセットがリアクティブとしてVueのデータ変数内に保存されている唯一の問題はありますか?
  3. 私が持っている1つのアイデアは、Object.freezeまたは同様のアプローチでその「アイテム」-datasetを非観察可能/非反応に変え、2つのデータセットをレンダリングするテーブルを持っていることです:1つは非反応用、もう1つは現在編集モード(行がクリックされると「editableItems」データセットにプッシュされます)...ここでの提案(より単純なものなので、1つの配列内のすべてを処理できますか?)

Angular 1で同様のアプリケーションを実行し、50 000行を非常にうまく処理したため、Vue 2内でも実行できるはずです。 ...反応性を処理する方法を見つけるだけの問題である必要があります。

12
Janne

編集12.03.2019-この回答の最後に追加のヒント

この質問をしてからしばらくして、プロジェクトのこの部分を最終的に最適化することができました。これらのパフォーマンスやメモリの問題を抱えている人のために、いくつかの指針を示したいと思います。

Vueのドキュメントでは実際には説明されていませんが、Andreyが指摘したように、コンポーネントオブジェクトをカスタムオブジェクトとオブジェクトリストのデータストレージとして使用できます。結局のところ、それは単なる通常のjavascriptオブジェクトです。

最適化後、リストコンポーネントのセットアップは次のようになります。

module.exports = {
    items: [],
    mixins: [sharedUtils],
    data: function() {
        return {
            columns: {
                all: []
    etc... Lot's of data & methods

Items-arrayは数千の複雑なオブジェクト(約80 MBのデータ、6 MBの圧縮)で満たされていますが、これは非反応として処理しています。これは私が思っていたよりも問題ではないことが判明しました-ユーザーがフィルターボタンや入力文字列をクリックするたびにこの配列のフィルタリングをトリガーする構造をすでに使用していたアイテムに対してv-forを直接使用する代わりに、フィルタリング(名前など)。基本的に、この「processFilters」メソッドは応答しないitems-arrayを通過し、filteredItemsを返します。これはデータコンテキストに格納されます。したがって、変更されると自動的にリアクティブになります。

<tr v-for="item in filteredItems" 

これにより、filteredItems内のすべてのアイテムはリアクティブになりますが、フィルターで除外されるとリアクティブになり、大量のメモリが節約されます。なんと1200MBは400MBに縮小しました。まさに私が探していたものでした。賢い!

対処する必要がある問題はほとんどありません。アイテムはデータコンテキストに存在しないため、テンプレート内で直接使用することはできません。これは、書く代わりに...

<div v-if="items.length > 0 && everythingElseIsReady">

...データプロップを分離するために、items-arrayの長さを保存する必要がありました。これは計算値でも修正できますが、これらのプロパティはそのままにしておきます。

メインデータ配列の反応性を放棄することは、結局のところそれほど悪いことではありません-最も重要な部分は、その基本配列内のアイテムに対して直接行われた変更がUIおよび/またはサブコンポーネント(douh)。これは、バックエンドからのすべての結果を保持する「隠しデータコンテナー」があり、その大きなコンテナーのより小さい(フィルターされた)プレゼンテーション配列があるようにコードを分離する限り、それほど問題になりません。優れたRESTアーキテクチャを使用することで、非リアクティブデータストレージ内にアイテムを保存した後、最新のリビジョンに更新されたことを確認することを忘れない限り、非リアクティブデータストレージを使用することもできます。

さらに、何百行に対してマイクロコンポーネントがいくつあるかというパフォーマンス面での重要性に困惑していました。レンダリングは明らかにヒットしますが、(入力セルのインスタンスが数千あるので)大きな小道具を何千回も渡そうとしても、メモリにヒットするようには見えませんでした。この種のオブジェクトの1つは、グローバルな翻訳キー/値ペアオブジェクトであり、20000行以上の翻訳された文字列がありますが、それでも問題ではありませんでした。 Javascriptはオブジェクト参照を使用し、Vue Coreは適切にコーディングされているようです。ただし、このような構成オブジェクトを小道具として使用する限り、数千のオブジェクトから同じものを参照するだけです。データセット。

最後に、メモリの制限に達することを恐れずに、複雑なCRUDオブジェクトで夢中になり始めると思います!

Nudgeを正しい方向に向けてくれたAndrey Popovに大いに感謝します!

ヒント(12.03.2019)

しばらくしてから、大規模で複雑なデータセットを使用してUIの構築を続けてきたので、いくつかの短いアイデアとヒントを削除することにしました。

  1. マスターレコード(つまり、個人または製品)と関連レコード(サブオブジェクト/リレーショナルオブジェクト)の管理方法を検討してください。異なるマスターレコードに対して同じサブオブジェクトを複数回表す場合があるため、サブコンポーネントに注入されるデータの量を制限してください。問題は、これらのオブジェクトが実際に参照オブジェクトではない可能性があることです!

都市オブジェクトを含む人物オブジェクトがある状況を考えます。複数の人が同じ都市に住んでいますが、バックエンドからJSONデータを取得すると、それらの重複した都市オブジェクトは実際には同一の都市(人の間で共有/参照された都市オブジェクト)、または同様のオブジェクトの複数の表現(データはまったく同じですが、内部ではそれぞれが個別のインスタンス/一意のオブジェクトです)。 50 000人がいて、それぞれに同じサブオブジェクト/プロパティ「city」が含まれているとします。{id:4、name: "Megatown"}、1人ではなく5万人の個々の都市インスタンスを取得しましたか? person1.city === person2.cityですか、それとも同じように見えても2つの異なるオブジェクトですか?

共有された都市オブジェクトを参照しているか、同様のサブオブジェクトの多数のインスタンスを使用しているかわからない場合は、単純にperson-list-component内で参照することができます。あなたの個人にはcity-idが含まれているため、別個のRESTメソッド(getCities)で都市のリストを取得し、UIレベルでペアリングを行います。この方法では、都市のリストは1つだけであり、そのリストから都市を解決し、それを人に注入して、1つの都市のみを参照できます。または、リストから都市を解決し、それをプロパティとして個人コンポーネントに渡すこともできます。

また、サブオブジェクトの目的は何かを必ず検討してください。リアクティブにする必要がありますか、それとも静的ですか?大量のメモリを節約するために、「person.city = city」と伝えることができます。これは、すべての個人コンポーネントに注入されますが、リアクティブにする必要がある場合は、Vue.set -methodを使用する必要があります。そして、各都市が独自のインスタンスである必要がある場合(各個人が同様の都市オブジェクトを持っているが、プロパティは個人ごとに編集可能である必要がある場合)、参照オブジェクトを使用していないことを確認する必要があることに注意してください!したがって、ほとんどの場合、都市オブジェクトを複製する必要があります。これにより、ブラウザのメモリが消費されます。

  1. マイクロコンポーネントには、読み取り専用状態とエディター状態の両方に個別のビュー状態が含まれる場合があります。これは非常に一般的です。それでも、毎回そのマイクロコンポーネントのインスタンスを実際に作成しているため、そのコンポーネントを何千回も初期化しています。

Excelのような表と表の行があるスプレッドシートがある状況を考えてください。各セルには、レイアウトから「読み取り専用」プロパティを取得するカスタム「my-input」コンポーネントが含まれています。 UIが読み取り専用状態の場合、そのmy-input-component内のラベル部分のみを表示しますが、それ以外の場合は、いくつかの特別な条件(datetime、number、text、 textarea、select-tagなど)。ここで、20列の100行があり、実際に2000個のmy-input-componentsを初期化するとします。さて、問題は、何が改善される可能性があるか(パフォーマンス面)ですか?

まあ、read-only-labelをmy-input-componentからlist-viewに分離して、readonly-version(label)OR編集可能なmy-input-この方法では、v-if条件があり、 'em(行のみまたはレイアウト全体が読み取り専用から編集可能な状態に移動するため)を初期化するように特に要求しない限り、これらの2000個のマイクロコンポーネントは初期化されません。 ...おそらく、Vueが2000個のコンポーネントを作成する必要がない場合、ブラウザのメモリに対する影響がどれほど大きいかを推測できます。

ページの読み込みが本当に遅い場合は、VUEではないかもしれません。HTMLにレンダリングされたHTMLタグの量を確認してください。これを示す最も簡単な方法の1つは、2000オプションでselect-tagを100回繰り返すか、単一の20000オプションselect-tagを使用することです。不要なラッピングdivなどを含むマイクロコンポーネント...深さとタグが少ないほど、ブラウザCPUからのレンダリングパフォーマンスが低くなります。

例を使用して、優れたHTMLタグアーキテクチャを学習してください。例として、Trello -servicesダッシュボードビューがどのようにプログラムされているかを調べることができます。それは、サブディビジョンの最小量で、かなり複雑なサービスの非常にシンプルで美しい表現です。


メモリ処理を改善する方法はたくさんありますが、最も重要なものは、元の答えで説明したように、「隠された」オブジェクトを可視オブジェクトから分離することに関連していると思います。 2番目の部分は、インスタンス化されたオブジェクトと参照されたオブジェクトの違いを理解することです。 3番目は、オブジェクト間の不必要なデータ受け渡しの量を制限することです。

個人的にはこれを試したことはありませんが、一見無限量のデータのラッパーになり、あらゆる量のデータを処理するVue-virtual-scrollerコンポーネントが存在します。 @ https://github.com/Akryum/vue-virtual-scroller の概念を確認し、問題が解決したかどうかをお知らせください。

これらのガイドラインが、コンポーネントを最適化するためのアイデアを提供してくれることを願っています。希望をあきらめないで、改善の余地は常にあります!

15
Janne

私が読んだすべてから、あなたはそのデータに対する反応性を必要としないだけであることがわかります。

テーブル内の各行は切り替え可能です。つまり、行をクリックすると行が編集モードに変わり、その特定のフィールド/セルのExcelのような編集が可能になります。

つまり、行は編集可能ではなく、ユーザーの操作なしにデータを変更することはできません。

各オブジェクトには約100〜150のフィールド/プロパティがありますが、テーブル内の特定の時点で表示されるのは特定の量だけです(テーブルの列はリアルタイムで切り替えることができます)。

フィールドはリアクティブのままにしますが、表示しません。


そして今、あなたの質問

特定の配列リストオブジェクトの反応性を(インデックスなどによって)切り替える方法はありますか?そのため、配列自体の中のオブジェクトは、特に変更可能になるように呼び出されない限り、観察されない/不変です(つまり、ユーザーが行をクリックすると、モード)?

一度に編集できるアイテムが1つしかない場合、すべてをリアクティブにするのはなぜですか? 1つの変数を使用して、その変更を簡単にリッスンできます。

反応性がメモリ使用量のボトルネックになっているように見えるため、Vueの大規模なデータセットの処理をどのように実装しますか?

それはすべて実装に関するものです-反応するアイテムの膨大なリストが必要な状況に陥ることはめったにありません。アイテムが多いほど、反応性を使用するためにより多くのイベントが発生する必要があります。 50,000個のアイテムがあり、変更するイベントがわずかしかない場合(ユーザーがデータを手動で変更する場合など)、thoseイベントを簡単にリッスンして作成できますVueすべてのデータを処理するのではなく、手動で反応性をチェックします。 Vuex で確認できます。

私が持っている1つのアイデアは、Object.freezeまたは同様のアプローチでその「アイテム」-datasetを非観察可能/非反応に変え、2つのデータセットをレンダリングするテーブルを持っていることです:1つは非反応用、もう1つは現在編集モード(行がクリックされると「editableItems」データセットにプッシュされます)

これは一種の正しい方向ですが、2つの配列をサポートする必要はありません。次のようなものを使用することを想像してください。

data: function() {
    return {
        editingItem: {}
        // when editing is enabled bind the input fields to this item
    }
},
created: function() {
    this.items = [] // your items, can be used in markdown in the loop, but won't be reactive!
},
watch: {
    editingItem: function(data) {
        // this method will be called whenever user edits the input fields
        // here you can do whatever you want
        // like get item's id, find it in the array and update it's properties
        // something like manual reactivity ;)
    }
}
4
Andrey Popov