最近のWebサイトは通常、データベースやサードパーティのAPIなど、さまざまな場所からデータを取得します。たとえば、ユーザーを認証する場合、Webサイトはデータベースからユーザーレコードを検索し、API呼び出しを介して一部の外部サービスからのデータでそれを装飾する場合があります。データベースクエリのディスクアクセスやAPI呼び出しのインターネットラウンドトリップなど、これらのデータソースへの高額な呼び出しを最小限に抑えることは、高速で応答性の高いサイトを維持するために不可欠です。データキャッシングは、これを実現するために使用される一般的な最適化手法です。
プロセスは、作業データをメモリに保存します。 Webサーバーが単一のプロセス(Node.js / Expressなど)で実行されている場合、このデータは、同じプロセスで実行されているメモリキャッシュを使用して簡単にキャッシュできます。ただし、負荷分散されたWebサーバーは複数のプロセスにまたがっており、単一のプロセスで作業している場合でも、サーバーの再起動時にキャッシュを保持したい場合があります。これには、Redisなどのアウトプロセスキャッシュソリューションが必要です。つまり、データを何らかの方法でシリアル化し、キャッシュから読み取るときに逆シリアル化する必要があります。
シリアル化と逆シリアル化は、C#などの静的に型指定された言語で実現するのは比較的簡単です。ただし、JavaScriptの動的な性質により、問題は少し複雑になります。 ECMAScript 6(ES6)でクラスが導入されましたが、これらのクラス(およびそのタイプ)のフィールドは、初期化されるまで定義されません(クラスがインスタンス化されるときではない場合があります)。また、フィールドと関数の戻り値の型は定義されません。スキーマではまったく。さらに、クラスの構造は実行時に簡単に変更できます。フィールドの追加や削除、タイプの変更などが可能です。これはC#のリフレクションを使用して可能ですが、リフレクションはその言語の「ダークアート」を表します。開発者はそれが機能を壊すことを期待しています。
数年前、ApeeScapeコアチームで働いていたときに、この問題が発生しました。チーム向けにアジャイルダッシュボードを構築していましたが、これは高速である必要がありました。そうでなければ、開発者や製品の所有者はそれを使用しません。作業追跡システム、プロジェクト管理ツール、データベースなど、さまざまなソースからデータを取得しました。このサイトはNode.js / Expressで構築されており、これらのデータソースへの呼び出しを最小限に抑えるためのメモリキャッシュがありました。ただし、迅速で反復的な開発プロセスでは、1日に数回デプロイ(したがって再起動)し、キャッシュを無効にして、その利点の多くを失いました。
明らかな解決策は、次のようなアウトプロセスキャッシュでした。 Redis 。しかし、いくつかの調査の結果、JavaScript用の適切なシリアル化ライブラリが存在しないことがわかりました。組み込みのJSON.stringify / JSON.parseメソッドは、オブジェクトタイプのデータを返し、元のクラスのプロトタイプの関数をすべて失います。つまり、逆シリアル化されたオブジェクトをアプリケーション内で単に「インプレース」で使用することはできません。そのため、代替設計で機能するにはかなりのリファクタリングが必要になります。
JavaScriptで任意のデータのシリアル化と逆シリアル化をサポートし、逆シリアル化された表現と元のデータを交換可能に使用できるようにするには、次のプロパティを持つシリアル化ライブラリが必要でした。
このギャップを埋めるために、私は書くことにしました Tanagra.js 、JavaScript用の汎用シリアル化ライブラリ。ライブラリの名前は、私のお気に入りのエピソードの1つへの参照です。 スタートレック:次世代 、ここでの乗組員 企業 言語が理解できない不思議なエイリアンとのコミュニケーションを学ぶ必要があります。このシリアル化ライブラリは、このような問題を回避するために一般的なデータ形式をサポートしています。
Tanagra.js はシンプルで軽量になるように設計されており、現在Node.js(ブラウザでテストされていませんが、理論的には機能するはずです)とES6クラス(マップを含む)をサポートしています。メインの実装はJSONをサポートし、実験的なバージョンはGoogle ProtocolBuffersをサポートします。ライブラリには標準のJavaScript(現在ES6とNode.jsでテスト済み)のみが必要であり、実験的な機能に依存していません。 バベル トランスパイル、または TypeScript 。
c ++。oファイル
シリアル化可能なクラスは、クラスがエクスポートされるときにメソッド呼び出しでそのようにマークされます。
module.exports = serializable(Foo, myUniqueSerialisationKey)
メソッドはを返します プロキシ クラスに、コンストラクターをインターセプトし、一意の識別子を挿入します。 (指定されていない場合、これはデフォルトでクラス名になります。)このキーは残りのデータとともにシリアル化され、クラスはそれを静的フィールドとして公開します。クラスにネストされた型(つまり、シリアル化が必要な型を持つメンバー)が含まれている場合、それらはメソッド呼び出しでも指定されます。
module.exports = serializable(Foo, [Bar, Baz], myUniqueSerialisationKey)
(以前のバージョンのクラスのネストされた型も同様の方法で指定できるため、たとえば、Foo1をシリアル化すると、Foo2に逆シリアル化できます。)
シリアル化中に、ライブラリはクラスへのキーのグローバルマップを再帰的に構築し、逆シリアル化中にこれを使用します。 (キーは残りのデータとともにシリアル化されることに注意してください。)「トップレベル」クラスのタイプを知るために、ライブラリでは、これを逆シリアル化呼び出しで指定する必要があります。
const foo = decodeEntity(serializedFoo, Foo)
実験的な自動マッピングライブラリは、モジュールツリーをウォークし、クラス名からマッピングを生成しますが、これは一意の名前のクラスに対してのみ機能します。
プロジェクトはいくつかのモジュールに分かれています。
ライブラリは米国のスペルを使用していることに注意してください。
次の例では、シリアル化可能なクラスを宣言し、tanagra-jsonモジュールを使用してシリアル化/逆シリアル化します。
const serializable = require('tanagra-core').serializable class Foo { constructor(bar, baz1, baz2, fooBar1, fooBar2) { this.someNumber = 123 this.someString = 'hello, world!' this.bar = bar // a complex object with a prototype this.bazArray = [baz1, baz2] this.fooBarMap = new Map([ ['a', fooBar1], ['b', fooBar2] ]) } } // Mark class `Foo` as serializable and containing sub-types `Bar`, `Baz` and `FooBar` module.exports = serializable(Foo, [Bar, Baz, FooBar]) ... const json = require('tanagra-json') json.init() // or: // require('tanagra-protobuf') // await json.init() const foo = new Foo(bar, baz) const encoded = json.encodeEntity(foo) ... const decoded = json.decodeEntity(encoded, Foo)
2つのシリアライザーのパフォーマンスを比較しました( JSON シリアライザーと実験 protobufs シリアライザー)コントロール(ネイティブJSON.parseおよびJSON.stringify)を使用します。それぞれで合計10回の試行を行いました。
2017年にこれをテストしました Dell XPS15 Ubuntu 17.10を実行している、32Gbメモリを搭載したラップトップ。
次のネストされたオブジェクトをシリアル化しました。
foo: { 'string': 'Hello foo', 'number': 123123, 'bars': [ { 'string': 'Complex Bar 1', 'date': '2019-01-09T18:22:25.663Z', 'baz': { 'string': 'Simple Baz', 'number': 456456, 'map': Map { 'a' => 1, 'b' => 2, 'c' => 2 } } }, { 'string': 'Complex Bar 2', 'date': '2019-01-09T18:22:25.663Z', 'baz': { 'string': 'Simple Baz', 'number': 456456, 'map': Map { 'a' => 1, 'b' => 2, 'c' => 2 } } } ], 'bazs': Map { 'baz1' => Baz { string: 'baz1', number: 111, map: Map { 'a' => 1, 'b' => 2, 'c' => 2 } }, 'baz2' => Baz { string: 'baz2', number: 222, map: Map { 'a' => 1, 'b' => 2, 'c' => 2 } }, 'baz3' => Baz { string: 'baz3', number: 333, map: Map { 'a' => 1, 'b' => 2, 'c' => 2 } } }, }
シリアル化方式 | アベニュー株式会社最初の試行(ms) | StDev。株式会社最初の試行(ms) | アベニュー例最初の試行(ms) | StDev。例最初の試行(ms) |
JSON | 0.115 | 0.0903 | 0.0879 | 0.0256 |
Google Protobufs | 2.00 | 2,748 | 1.13 | 0.278 |
対照群 | 0.0155 | 0.00726 | 0.0139 | 0.00570 |
読んだ
シリアル化方式 | アベニュー株式会社最初の試行(ms) | StDev。株式会社最初の試行(ms) | アベニュー例最初の試行(ms) | StDev。例最初の試行(ms) |
JSON | 0.133 | 0.102 | 0.104 | 0.0429 |
Google Protobufs | 2.62 | 1.12 | 2.28 | 0.364 |
対照群 | 0.0135 | 0.00729 | 0.0115 | 0.00390 |
ザ・ JSON シリアライザーは、ネイティブシリアル化よりも約6〜7倍遅くなります。実験 protobufs シリアライザーは、 JSON シリアライザー、またはネイティブシリアル化よりも100倍遅い。
さらに、各シリアライザー内のスキーマ/構造情報の内部キャッシュは、明らかにパフォーマンスに影響を与えます。 JSONシリアライザーの場合、最初の書き込みは平均の約4倍遅くなります。 protobufシリアライザーの場合、9倍遅くなります。したがって、メタデータがすでにキャッシュされているオブジェクトの書き込みは、どちらのライブラリでもはるかに高速です。
読み取りでも同じ効果が観察されました。 JSONライブラリの場合、最初の読み取りは平均の約4倍遅く、protobufライブラリの場合は約2.5倍遅くなります。
最も人気のある出会い系サイト2017
protobufシリアライザーのパフォーマンスの問題は、まだ実験段階にあることを意味します。何らかの理由でフォーマットが必要な場合にのみお勧めします。ただし、形式はJSONよりもはるかに簡潔であり、したがってネットワーク経由での送信に適しているため、時間をかけて投資する価値があります。 Stack Exchangeは、内部キャッシュにこの形式を使用します。
JSONシリアライザーは明らかにパフォーマンスがはるかに優れていますが、ネイティブ実装よりも大幅に低速です。小さなオブジェクトツリーの場合、この違いは重要ではありません(50ミリ秒のリクエストに加えて数ミリ秒でもサイトのパフォーマンスが損なわれることはありません)が、これは非常に大きなオブジェクトツリーの問題になる可能性があり、私の開発の優先事項の1つです。
ライブラリはまだベータ段階です。 JSONシリアライザーは、十分にテストされており、安定しています。今後数か月のロードマップは次のとおりです。
複雑なネストされたオブジェクトデータのシリアル化と元の型への逆シリアル化をサポートするJavaScriptライブラリは他にありません。ライブラリのメリットを享受できる機能を実装している場合は、ライブラリを試してみて、フィードバックに連絡し、貢献を検討してください。
通常、オブジェクトは保存されません。それらは必要に応じてインスタンス化され、処理に使用され、不要になったときにメモリから削除されます。他の場所で一時的にデータを使用する必要がある場合は、そのデータを別の構造にシリアル化および逆シリアル化します。
オブジェクトは、構造体に対して実行できる操作とともに、構造体をカプセル化するコードの一部です。一般に、これはオブジェクト指向プログラミング言語のオブジェクトと同じです。
データオブジェクトは、データストアからのデータを一時的に含み、アプリケーションで読み取ったり処理したりできるようにするコードです。そのデータをメモリに保持する方法がない限り、オブジェクトがスコープ外になるか、その他の方法で不要になったときに、データは書き戻されるか、保持されません。
シリアル化により、実行中のプロセスまたは必要に応じて他のプロセスで使用するためにデータを保持できます。データを保存し、他の場所で使用される場合は逆シリアル化します。