メッセージ エンコードに関する考慮事項
多くのクラウド アプリケーションでは、非同期メッセージを使用してシステムのコンポーネント間で情報を交換します。 メッセージングの重要な側面は、ペイロード データのエンコードに使用される形式です。 メッセージング テクノロジ を選択後、次の手順は、メッセージのエンコード方法を定義することです。 使用可能なオプションは多数ありますが、適切な選択肢はユース ケースによって異なります。
この記事では、いくつかの考慮事項について説明します。
メッセージ交換のニーズ
プロデューサーとコンシューマーの間のメッセージ交換には、次の情報が必要です。
- メッセージのペイロードを定義する図形または構造体。
- ペイロードを表すエンコード形式。
- エンコードされたペイロードの読み取りと書き込みを行うシリアル化ライブラリ。
メッセージのプロデューサーは、ビジネス ロジックとコンシューマーに送信する情報に基づいてメッセージの形状を定義します。 図形を構成するには、情報を個別または関連する対象 (フィールド) に分割します。 これらのフィールドの値の特性を決定します。 次の質問を検討する必要があります。
- 最も効率的なデータ型は何ですか?
- ペイロードには常に特定のフィールドがありますか?
- ペイロードには、1 つのレコードまたは値の繰り返しセットがありますか?
次に、必要に応じてエンコード形式を選択します。 特定の要因には、必要に応じて高度に構造化されたデータを作成する機能、メッセージのエンコードと転送にかかる時間、ペイロードを解析する機能などがあります。 エンコード形式に応じて、適切にサポートされているシリアル化ライブラリを選択します。
メッセージのコンシューマーは、受信メッセージの読み取り方法を認識できるように、これらの決定を認識する必要があります。
メッセージを転送するために、プロデューサーはメッセージをエンコード形式にシリアル化します。 受信側では、コンシューマーはペイロードを逆シリアル化してデータを使用します。 これにより、両方のエンティティがモデルを共有し、図形が変更されない限り、メッセージングは問題なく続行されます。 コントラクトが変更されると、エンコード形式はコンシューマーを中断することなく変更を処理できる必要があります。
JSON などの一部のエンコード形式は自己記述型であるため、スキーマを参照せずに解析できます。 ただし、このような形式では、メッセージが大きくなる傾向があります。 他の形式では、データは簡単に解析できない場合がありますが、メッセージはコンパクトです。 この記事では、書式の選択に役立ついくつかの要因について説明します。
エンコード形式に関する考慮事項
エンコード形式は、構造化データのセットをバイトとして表す方法を定義します。 メッセージの種類は、形式の選択に影響を与える可能性があります。 ビジネス トランザクションに関連するメッセージには、多くの場合、高度に構造化されたデータが含まれています。 また、後で監査目的で取得することもできます。 イベントのストリームの場合は、一連のレコードをできるだけ早く読み取り、統計分析のために格納することができます。
エンコード形式を選択する際に考慮すべき点をいくつか次に示します。
人間の読みやすさ
メッセージエンコードは、テキストベースとバイナリ形式に大きく分けることができます。
テキスト ベースのエンコードでは、メッセージ ペイロードはプレーン テキストであるため、コード ライブラリを使用せずにユーザーが検査できます。 人間が判読できる形式は、アーカイブ データに適しています。 また、人間がペイロードを読み取ることができるため、テキストベースの形式をデバッグし、ログに送信してエラーをトラブルシューティングする方が簡単です。
欠点は、ペイロードが大きくなる傾向があるということです。 ペイロードのサイズは、必要に応じて人間の読みやすさのために元に戻すことができる限り、縮小プロセスによって減らすことができます。 一般的なテキストベースの形式は、JSON と YAML です。
暗号化
メッセージに機密データが含まれている場合は、それらのメッセージを全体として暗号化すべきかどうかを、保存時の Azure Service Bus データの暗号化に関するこのガイダンスを参考にして検討してください 。 または、特定のフィールドのみを暗号化する必要があり、クラウド コストを削減したい場合は、NServiceBus などのライブラリを使用することを検討してください。
エンコード サイズ
メッセージ サイズは、ネットワーク全体の I/O パフォーマンスに影響します。 バイナリ形式は、テキストベースの形式よりもコンパクトです。 バイナリ形式には、シリアル化/逆シリアル化ライブラリが必要です。 ペイロードがデコードされない限り、ペイロードを読み取ることはできません。
ワイヤ フットプリントを削減し、メッセージをより迅速に転送する場合は、バイナリ形式を使用します。 ストレージまたはネットワーク帯域幅が問題になるシナリオでは、このカテゴリの形式をお勧めします。 バイナリ形式のオプションには、Apache Avro、Google Protocol Buffers (protobuf)、MessagePack、および簡潔なバイナリ オブジェクト表現 (CBOR) があります。 これらの形式の長所と短所については、「エンコード形式 のの選択肢」で後述します。
欠点は、ペイロードが人間が判読できないことです。 ほとんどのバイナリ形式では、保守にコストがかかる複雑なシステムが使用されます。 また、デコードするには特殊なライブラリが必要です。アーカイブ データを取得する場合はサポートされない場合があります。
非バイナリ形式の場合、縮小プロセスを使用して技術的に不要なスペースと文字を削除しても、形式の仕様に合わせて維持すると、エンコード サイズに役立ちます。 エンコーダーの機能を評価して、縮小を既定にします。 たとえば、.NET の System.Text.Json.JsonSerializer
の JsonSerializerOptions.WriteIndented
は、JSON テキストを作成する際の自動圧縮を制御します。
ペイロードの理解
メッセージペイロードはバイト列として到達します。 このシーケンスを解析するには、コンシューマーがペイロード内のデータ フィールドを記述するメタデータにアクセスできる必要があります。 メタデータの格納と配布には、主に次の 2 つの方法があります。
タグ付きメタデータ。 一部のエンコード (特に JSON) では、フィールドはメッセージの本文内でデータ型と識別子でタグ付けされます。 これらの形式 は、スキーマを参照せずに値のディクショナリに解析できるため、自己記述型 です。 コンシューマーがフィールドを理解する 1 つの方法は、期待される値を照会することです。 たとえば、プロデューサーは JSON でペイロードを送信します。 コンシューマーは JSON をディクショナリに解析し、フィールドの存在を確認してペイロードを理解します。 もう 1 つの方法は、コンシューマーがプロデューサーによって共有されるデータ モデルを適用することです。 たとえば、静的に型指定された言語を使用している場合、多くの JSON シリアル化ライブラリでは、JSON 文字列を型指定されたクラスに解析できます。
スキーマ。 スキーマは、メッセージの構造とデータ フィールドを正式に定義します。 このモデルでは、プロデューサーとコンシューマーは、明確に定義されたスキーマを介してコントラクトを持ちます。 スキーマでは、データ型、必須/省略可能なフィールド、バージョン情報、ペイロードの構造を定義できます。 プロデューサーは、ライター スキーマに従ってペイロードを送信します。 コンシューマーは、リーダー スキーマを適用してペイロードを受け取ります。 メッセージは、エンコード固有のライブラリを使用してシリアル化/逆シリアル化されます。 スキーマを分散するには、次の 2 つの方法があります。
スキーマをプリアンブルまたはヘッダーとしてメッセージに格納しますが、ペイロードとは別に格納します。
スキーマを外部に格納します。
一部のエンコード形式ではスキーマが定義され、スキーマからクラスを生成するツールが使用されます。 プロデューサーとコンシューマーは、これらのクラスとライブラリを使用してペイロードをシリアル化および逆シリアル化します。 ライブラリは、ライタースキーマとリーダースキーマの間の互換性チェックも提供します。 protobuf と Apache Avro の両方がそのアプローチに従います。 主な違いは、protobuf には言語に依存しないスキーマ定義がありますが、Avro ではコンパクトな JSON が使用されていることです。 もう 1 つの違いは、両方の形式がリーダー スキーマとライター スキーマ間の互換性チェックを提供する方法です。
スキーマ レジストリにスキーマを外部に格納するもう 1 つの方法。 メッセージには、スキーマとペイロードへの参照が含まれています。 プロデューサーはメッセージ内のスキーマ識別子を送信し、コンシューマーは外部ストアからその識別子を指定してスキーマを取得します。 どちらの当事者も、形式固有のライブラリを使用してメッセージの読み取りと書き込みを行います。 スキーマを格納する以外に、レジストリは互換性チェックを提供して、スキーマの進化に伴ってプロデューサーとコンシューマーの間のコントラクトが壊れていないことを確認できます。
方法を選択する前に、転送データのサイズまたはアーカイブされたデータを後で解析する機能という、より重要なものを決定します。
ペイロードと共にスキーマを格納すると、エンコード サイズが大きくなり、断続的なメッセージに適しています。 バイトの小さなチャンクを転送することが重要であるか、レコードのシーケンスが必要な場合は、この方法を選択します。 外部スキーマ ストアを維持するコストは高くなる可能性があります。
ただし、ペイロードのオンデマンド デコードがサイズよりも重要な場合は、ペイロードを含むスキーマやタグ付けされたメタデータ アプローチを含め、後でデコードすることが保証されます。 メッセージ サイズが大幅に増加し、ストレージのコストに影響する可能性があります。
スキーマのバージョン管理
ビジネス要件が変わると、形状が変わると予想され、スキーマが進化します。 バージョン管理を使用すると、プロデューサーは新しい機能を含む可能性のあるスキーマの更新を示すことができます。 バージョン管理には 2 つの側面があります。
コンシューマーは変更を認識する必要があります。
1 つの方法は、コンシューマーがすべてのフィールドをチェックして、スキーマが変更されたかどうかを判断することです。 もう 1 つの方法は、プロデューサーがメッセージを含むスキーマ バージョン番号を発行することです。 スキーマが進化すると、プロデューサーはバージョンをインクリメントします。
変更は、コンシューマーのビジネス ロジックに影響を与えたり、中断したりしてはなりません。
フィールドが既存のスキーマに追加されるとします。 新しいバージョンを使用しているコンシューマーが古いバージョンに従ってペイロードを取得した場合、新しいフィールドの不足を見落とせないと、ロジックが壊れる可能性があります。 逆のケースを考えると、新しいスキーマでフィールドが削除されるとします。 古いスキーマを使用しているコンシューマーは、データを読み取ることができない可能性があります。
Avro などのエンコード形式では、既定値を定義できます。 前の例では、フィールドが既定値で追加されると、不足しているフィールドに既定値が設定されます。 protobuf などの他の形式では、必須フィールドと省略可能なフィールドを通じて同様の機能が提供されます。
ペイロードの構造
ペイロード内でデータを配置する方法を検討します。 レコードのシーケンスか、個別の 1 つのペイロードか。 ペイロード構造は、次のいずれかのモデルに分類できます。
配列/ディクショナリ/値: 1 次元配列または多次元配列の値を保持するエントリを定義します。 エントリには、一意のキーと値のペアがあります。 複雑な構造を表すために拡張できます。 たとえば、JSON、Apache Avro、MessagePack などがあります。
このレイアウトは、メッセージが異なるスキーマで個別にエンコードされている場合に適しています。 複数のレコードがある場合、ペイロードが過剰に冗長になり、ペイロードが肥大化する可能性があります。
表形式データ: 情報は行と列に分割されます。 各列はフィールド、または情報の件名を示し、各行にはそれらのフィールドの値が含まれています。 このレイアウトは、時系列データなどの情報の繰り返しセットに対して効率的です。
CSV は、最も単純なテキスト ベースの形式の 1 つです。 これは、共通のヘッダーを持つレコードのシーケンスとしてデータを提示します。 バイナリ エンコードの場合、Apache Avro のプリアンブルは CSV ヘッダーに似ていますが、コンパクトなエンコード サイズが生成されます。
ライブラリのサポート
独自のモデルではなく、既知のフォーマットを使用するべきです。 既知の形式は、コミュニティによって汎用でサポートされているライブラリを通じてサポートされます。 特殊な形式では、特定のライブラリが必要です。 ビジネス ロジックでは、ライブラリによって提供される API 設計の選択肢の一部を回避する必要がある場合があります。
スキーマ ベースの形式の場合は、リーダー スキーマとライター スキーマの間の互換性チェックを行うエンコード ライブラリを選択します。 Apache Avro などの特定のエンコード ライブラリでは、メッセージを逆シリアル化する前に、コンシューマーがライターとリーダー スキーマの両方を指定することが想定されています。 このチェックにより、コンシューマーがスキーマのバージョンを認識することが保証されます。
相互運用性
形式の選択は、特定のワークロードまたはテクノロジ エコシステムによって異なる場合があります。
次に例を示します。
Azure Stream Analytics には、JSON、CSV、Avro のネイティブ サポートがあります。 ワークロードで Stream Analytics を使用する場合は、これらの形式のいずれかを選択するのが理にかなっています。
JSON は、HTTP REST API の標準インターチェンジ形式です。 アプリケーションがクライアントから JSON ペイロードを受信し、非同期処理のためにメッセージ キューに配置する場合は、別の形式に再エンコードするのではなく、メッセージングに JSON を使用することが理にかなっている可能性があります。
これらは、相互運用性に関する考慮事項の 2 つの例にすぎません。 一般に、標準化された形式は、カスタム形式よりも相互運用可能です。 テキストベースのオプションでは、JSON は最も相互運用可能なオプションの 1 つです。
エンコード形式の選択肢
一般的なエンコード形式を次に示します。 形式を選択する前に、考慮事項を考慮に入れます。
JSON
JSON はオープン標準 (IETF RFC8259) です。 配列/ディクショナリ/値モデルに従うテキストベースの形式です。
JSON はメタデータのタグ付けに使用でき、スキーマなしでペイロードを解析できます。 JSON では、省略可能なフィールドを指定するオプションがサポートされています。これは、前方互換性と下位互換性に役立ちます。
最大の利点は、その普遍的に利用可能であるということです。 最も相互運用可能であり、多くのメッセージング サービスの既定のエンコード形式です。
テキストベースの形式であるため、ネットワーク経由では効率的ではなく、ストレージが問題になる場合には理想的な選択肢ではありません。 可能な場合は縮小手法を使用します。 キャッシュされた項目を HTTP 経由でクライアントに直接返す場合、JSON を格納すると、別の形式から逆シリアル化してから JSON にシリアル化するコストが節約される可能性があります。
単一レコード メッセージ、または各メッセージが異なるスキーマを持つ一連のメッセージには JSON を使用します。 時系列データなど、一連のレコードには JSON を使用しないでください。
バイナリ JSON (BSON) など、JSON には他にもバリエーションがあります。これは、MongoDB で動作するように調整されたバイナリ エンコードです。
コンマ区切り値 (CSV)
CSV は、テキストベースの表形式です。 テーブルのヘッダーはフィールドを示します。 メッセージに一連のレコードが含まれている場合に推奨される選択肢です。
欠点は、標準化の欠如です。 区切り記号、ヘッダー、および空のフィールドを表す方法は多数あります。
プロトコル バッファー (protobuf)
プロトコル バッファー (または protobuf) は、厳密に型指定された定義ファイルを使用してキーと値のペアでスキーマを定義するシリアル化形式です。 これらの定義ファイルは、メッセージのシリアル化と逆シリアル化に使用される言語固有のクラスにコンパイルされます。
メッセージには圧縮されたバイナリの小さなペイロードが含まれており、結果として転送が高速化されます。 欠点は、ペイロードが人間が判読できないことです。 また、スキーマは外部であるため、アーカイブされたデータを取得する必要がある場合には推奨されません。
Apache Avro
Apache Avro は、protobuf と同様の定義ファイルを使用するバイナリ シリアル化形式ですが、コンパイル手順はありません。 代わりに、シリアル化されたデータには常にスキーマ プリアンブルが含まれます。
プリアンブルは、ヘッダーまたはスキーマ識別子を保持できます。 エンコード サイズが小さいため、Avro はデータのストリーミングに推奨されます。 また、一連のレコードに適用されるヘッダーがあるため、表形式データに適しています。
Apache Parquet
Apache Parquet は、通常、Apache Hadoop および関連するデータ処理フレームワークに関連付けられている列形式のストレージ ファイル形式です。
この形式では、データ圧縮がサポートされ、スキーマの進化をサポートするいくつかの機能が制限されています。 この形式は、このデータの作成または使用を担当するワークロード内の他のビッグ データ テクノロジの選択によってのみ使用される可能性がある形式の例です。
MessagePack
MessagePack は、ワイヤ経由の伝送用にコンパクトに設計されたバイナリ シリアル化形式です。 メッセージ スキーマやメッセージ型チェックはありません。 この形式は、一括ストレージには推奨されません。
CBOR
簡潔なバイナリ オブジェクト表現 (CBOR) (仕様) は、小さなエンコード サイズを提供するバイナリ形式です。 MessagePack よりも CBOR の利点は、RFC7049の IETF に準拠していることです。