mozblog - もずぶろぐ -

自分のための備忘録や日記など。

HTTP/2 は何が変わったのか

Real World HTTP を読みました。

前回の文字コードの件と同じく、HTTP というプロトコルの歴史が概観されていて、すごく良かったです。

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

Real World HTTP ―歴史とコードに学ぶインターネットとウェブ技術

 

 Azure App Service も HTTP/2 に対応したことですし、HTTP/1.0 から HTTP/2 までの機能追加の遍歴をまとめます。

HTTP/1.0

1992 年に初期案が提案され、現在の HTTP というプロトコルのセマンティクスを形作った。

1. メソッド

1.0 以前は GET 以外のメソッドはなかったが、数多くのメソッドが提案された。
主に生き残ったメソッドは以下の 3 種類。

GET : サーバーに対してヘッダーとボディを要求
HEAD : サーバーに対してヘッダーを要求
POST : 新しいドキュメントを投稿

2. ステータスコード

3桁の数字をサーバーが応答することで、サーバーがどのような応答を返したか分かるようになった。

100 番台から 500 番台までのステータスコードが定義された。

3. リダイレクト

300 番台のステータスの一部では、Location ヘッダーを使って、リダイレクト先をサーバーからクライアントに伝えるものとされた。

受け取ったクライアントは、Location ヘッダーの指示通りに、接続しなおすことが期待される。

4. URL (Uniform Resource Locators)

HTTP/1.0 の規格化を見越して、先行して規格化されたもの。

スキーマ : // ユーザ : パスワード @ ホスト名 : ポート / パス # フラグメント ? クエリー

という形でリソースの場所を示す。

5. ボディ

1.0 以前は、リクエストにデータを含めることができなかったが、1.0 からリクエストにも含めることができるようになった。

ヘッダーとの間に空行を挟み、それ以降がボディとなる。

GET や HEAD でも、リクエストにボディを含めることは可能ではあるが、メソッドのセマンティクスと一致しないため、サーバーでは無視することが推奨される。

HTTP/1.1 

HTTP/1.1 は 1997 年に最初のバージョンが RFC 2068 にて策定された。
HTTP/1.0 から多くの改良が行われ、今日の HTTP 通信の基礎が作られた。
2015 年に HTTP/2 が登場するまで、20 年近く最新バージョンであり続けた。

1. Keep-Alive

これまでは HTTP の通信 1 回ごとに、TCP ソケットをオープン / クローズしていた。
しかし、HTTP/1.1 では TCP ソケットをオープンしたあと、クライアントもしくはサーバーが、ヘッダーに Connection: Close を含めるまで、オープンのままにする。

これにより、TCP ソケットの開閉に要するオーバーヘッドが削減できる。

2. パイプライニング

前項の Keep-Alive の利用を前提としており、ある HTTP リクエストに対するレスポンスが返る前に、次の HTTP リクエストを送信する。
サーバーは、HTTP リクエストが到達した順にレスポンスを返すことが求められる。

しかし、ある HTTP リクエストが重いと、後続のレスポンスに影響が出る (Head-of-Line Blocking) など、あまり利用されなかった。

3. TLS (Transport Layer Security) の規格化

TLS 自体は HTTP に限らず、ほかのプロトコルでも利用できるため、厳密には HTTP/1.1 とは異なる。

しかし、TLS は HTTP/1.1 と共同利用されることを強く想定して、SSL 3.0 をもとに規格化された。

4. PUT と DELETE メソッドの標準化

HTTP/1.0 ではオプション扱いであった PUT と DELETE メソッドが必須扱いとされ、現在の REST の元になる GET / POST / PUT / DELETE の 4 メソッドが出そろった。

PUT と DELETE は HTML の form からは送信できないので、JavaScript の Fetch APIXMLHttpRequest を利用する。

5. OPTIONS, TRACE, CONNECT メソッドの追加

OPTIONS は、サーバーが受け取り可能なメソッドの一覧を返す。

TRACE は、入力した HTTP リクエストの内容(ヘッダー・ボディ)をそのまま、サーバーがオウム返しに、ステータスコード 200 で返却する。
HTTP における Ping の役割を果たすメソッドとして追加されたが、TRACE メソッドが他人のパスワードを詐取する攻撃の温床になりやすいなど、セキュリティ上の懸念が有名になり、多くのサーバーで無効化された。

CONNECT は、HTTP で確立したトンネルに、他のプロトコルのパケットを流すことができるようになる。

6. プロトコルのアップグレード機能

HTTP から TLS を利用した通信や、WebSocket、HTTP/2 など、プロトコルをアップグレードできるようになった。
これはクライアントからもサーバーからも要請できる。

クライアントから行う場合、Upgrade ヘッダーにプロトコル名を入力し、Connection ヘッダーに Upgrade という文字列を入力する。
サーバーは、アップグレードが可能であれば、ステータスコード 101 (Switching Protocols) を返す。

サーバーから行う場合、同じく Upgrade と Connection ヘッダーをレスポンスで返し、ステータスコード 426 (Upgrade Required) を返す。
クライアントは 426 を受け取ると、上述の「クライアントから行う場合」と同様のフローで、プロトコルのアップグレードを行う。

7. バーチャルホスト機能

HTTP/1.0 までは、サーバー 1 台あたり 1 ドメインだった。
そのため、http://example.com/hello という URI の場合、サーバーは /hello という情報しか受け取ることができなかった。

HTTP/1.1 からは、Host というヘッダーを含め、そこに Web サイトのドメインを記述することが義務付けられた。
このヘッダーに応じて、サーバーはバーチャルホストにリクエストをルーティングすることができるようになり、サーバーで複数のドメインがホストできるようになった。

8. チャンクのサポート

サーバーが、データ全体を一括で送信するのではなく、分割して送信できるようになった。
JPEG のような画像ファイルであれば、クライアントが受け取った分だけ、インターレース表示ができるようになる。

サーバーは、チャンクで送る際は、Transfer-Encoding ヘッダーに chunked と入力する。

レスポンスは下記のようになり、データサイズを示す 16 進数のあとに、データを入れる。

HTTP/1.1 200 OK
Transfer-Encoding: chunked

186a0
(100KB のデータ)
186a0
(100KB のデータ)
・・・

186a0 は 10 進数で 100000 を表す。

9. クライアントによるボディ送信の確認

クライアントからサーバーにデータを送信する際、データサイズが受け入れ可能か、確認ができるようになった。

確認の際は、クライアントは Expect ヘッダーに 100-continue と入力し、Content-Length ヘッダーにサイズを入力して送信する。

サーバーは受け入れ可能であれば、ステータスコード 100 (Continue) を返す。
受け入れることができなければ、417 (Expectation Failed) を返す。

 

HTTP/2

HTTP/1.1 が、HTTP というプロトコルの拡張、および高速化が目的であったのに対し、HTTP/2 が策定された目的は、通信の高速化に特化している。

HTTP/2 は、メソッド、ヘッダー、ステータスコード、ボディで構成される、HTTP のスキームは維持しているものの、内部では HTTP/1.1 と比較して全く別のプロトコルになっている。

そのため、プロトコルのアップグレード機能を利用して、サーバーに HTTP/2 を利用するように伝える仕組みとなっている。

 

1. ストリーム

HTTP/1.1 までは、ひとつのリクエストが TCP ソケットを占有していたので、複数本の TCP 接続を作成し、並列にリクエストを送信していた。
そのため、2 本から 6 本程度の並列度が限界だった。

HTTP/2 では、TCP 接続の内部に、ストリームと呼ばれる仮想の TCP ソケットを作る。
ストリームは、通常の TCP 接続のようにハンドシェイクが不要で、より簡単にソケットの開閉ができるようになっている。
これにより、TCP 接続の通信容量が許す限り、ストリームをいくらでも並列化できるようになった。

2. バイナリベースのプロトコル

HTTP/1.1 までは、通信がテキストベースで行われていた。
そのため、例えばヘッダーの終端を探すには、空の行を見つけるまで、1 バイトずつ走査する必要があった。

HTTP/2 では、通信がバイナリベースとなり、ヘッダーやボディなどのデータが「フレーム」という単位で分割され、管理される。

各フレームは独立しているため、HTTP/1.1 では重いレスポンスが後続のレスポンスの返却を阻害していたが、HTTP/2 では他のレスポンスのフレームを先行して返却できるようになった。

3. フローコントロール

ストリームの通信効率を向上させるために、速い方が遅い方に大量にデータを送信するような事象を防止する。

データの送信元は、送信先のバッファサイズの上限として、初期値で 64 KB を仮定する。
送信先では、バッファサイズに余裕が出てくると、送信元に対し WINDOW_UPDATE というフレームを使い、余裕ができたサイズを通知する。

このようにして、お互いのバッファサイズを調整しながら、データを送受信する。

4. サーバープッシュ

サーバーから、優先度の高いコンテンツを、クライアントに対し先に送信してキャッシュさせることができるようになった。

また、クライアントが、対象のコンテンツに対する要求をサーバーに送信するまで、サーバーからクライアントに対し、事前にコンテンツが送られていることは検知できない。

WebSocket のような双方向の通信方式とは異なり、CSSJavaScript や画像など、サイトを構成するコンテンツに用いられる。
そのため、チャットのような用途に使うことはできない。

5. HPACK によるヘッダー圧縮

ヘッダーを HPACK と呼ばれる方式で圧縮することで、サイズを小さくしている。

HTTP のヘッダーは、出てくる語句がある程度決まっているため、効率良く圧縮することができる特性を利用している。

 

文字コード体系まとめ

 エンジニアがいつかは立ち向かう必要がある命題、文字コードについて、私もそろそろきちんと理解しなくては、、、と思い、この本を読みました。

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)

プログラマのための文字コード技術入門 (WEB+DB PRESS plus) (WEB+DB PRESS plusシリーズ)

 

本だけでは少し足りないところをネットで調べ、本記事にまとめました。
そして分かったのは、文字コードの歴史というのは計算機の発展の歴史であり、企業の思惑や国際規格との競合の結果、本当に数奇な拡張を経てきたということでした。
同時に、ひとつに統一されればいいのになぁ、、、とも思いました。
ですが、過去のプログラムのエンコード方式が異なる場合など、単純に統一せずに拡張・互換性をもたせて運用していく姿勢が、今日まで続いています。

まとめるのに 10 時間以上かかって、長い記事になってしまいましたが、このような技術の発展の歴史は、概観していて本当に面白いですね。

ということで、下記よりまとめです。 

0. はじめに

文字コードについて理解するためには、まず、以下を区別する必要がある。

・符号化文字集合:コンピュータで利用する文字と、その文字に対して一意な番号を割り当てたもの。
・符号化文字方式:符号化文字集合をどのように表すか、バイト列 (ビット列) で定義したもの。

そして、文字種別の違いとして大きく 2 種類。

・制御文字:改行や、古いものだとベルを鳴らすような、端末に特殊動作をさせるための文字。
・図形文字:我々が取り扱う、いわゆる「普通の文字」。印刷可能な文字や表示可能な文字として定義されることもある。

日本語環境にて利用される符号化文字集合は大きく分けて 2 種類あり、ASCII からの流れをくむ ISO/IEC 2022 から発展した規格と、一般に Unicode といわれる ISO/IEC 10646 の規格がある。

 

1. 符号化文字集合

1-1. ASCII (1963 年)

7 bit で英数字を表す、最初期の文字集合で、米国の標準化組織である ASA (American Standards Association) が策定した。
符号化文字集合であると同時に、符号化文字方式でもある。
文字コードの初期設計では、世界中の文字を符号化するのではなく、米国の研究者が当面必要なコードだけをコード化しようとしたため、100 文字程度、つまり 7 bit で十分だった。
8 bit 目は通信のエラーチェックのためのパリティビットとして利用されていた。

1-2. ISO/IEC 646 (1967 年)

ASCII だけでは、アクセント記号など他言語で使われる、特殊な記号の付いたアルファベットが表現できないので、各国版の文字コードを、ASCII をもとにして作ろうとした。
そのための国際規格が ISO/IEC 646
ASCII の文字コードのうち、一部を他言語にあわせて変えて使えるようにしたもの。

1-3. JIS X 0201 (1969 年)

日本版の ISO/IEC 646 として、日本工業規格で定められたものが JIS X 0201
符号化文字集合でもあり、符号化文字方式でもある。
現存する JIS の文字コード規格の中では最も古いもので、7 bit でも、8 bit でも利用できる。

ラテン文字集合と片仮名集合を持ち、8 bit コードの場合は 第 8 bit が 0 の場合はラテン文字、1 の場合は片仮名文字として扱う。
7 bit の場合は、制御文字の SHIFT-IN (0x0F) もしくは SHIFT-OUT (0x0E) を呼ぶことで、両方の集合を切り替える。

ラテン文字集合は ASCII とは以下の違いしかない。

・符号位置 0x5C: ASCII ではバックスラッシュだが、JIS X 0201 では円記号

・符号位置 0x7E: ASCII ではチルダ だが、JIS X 0201 でオーバーライン (‾)

1-4. ISO/IEC 2022 (1973 年)

ISO/IEC 646 では複数の言語を同時に取り扱うことができなかったため、ASCII を拡張するための枠組みとして確立されたのが ISO/IEC 2022 で、世界中の文字コードの基礎となった。
ISO/IEC 2022 では、ISO/IEC 646 と同様の 7 bit コードも使えるし、8 bit コードも使える。また、複数バイトの文字列も規定されており、理論上は何バイトでもよいが、ほとんどは 2 バイトの規格として使われている。

7 bit コードの場合、128 文字を表すことができるが、0x00 から 0x1F までは制御文字領域、0x20 は SP (スペース)、0x21 から 0x7E までが文字、0x7F が DEL (削除文字)。

8 bit コードの場合、256 文字を表すことができるが、上記の 7 bit コードの構造に加え、0x80 から 0x9F までは制御文字領域、0xA0 から 0xFF までも文字が取り扱える。
0x00 から 0x1F までを CL 領域、0x20 から 0x7F までを GL 領域、0x80 から 0x9F までを CR 領域、0xA0 から 0xFF までを GR 領域という。

つまり、7 bit コードは CL 領域と GL 領域しかないコードとして取り扱える。

ただし、GL 領域や GR 領域では、SP や DEL のように、2 文字分を制御文字として使う場合があるため、実質は領域当たり 94 文字を表現できるが、この 94 文字がひとつの単位となる。

複数言語を同時に取り扱うときは、GL もしくは GR 領域に、他言語のテーブルを呼び出して使う。
他言語のテーブルは、複数バイト(たいていは 2 バイト) を用いて表現するため、94 * 94 = 8836 文字を表現できる。

呼び出すときは、ESC (0x1B) を用いて、エスケープシーケンスを挿入することで、テーブルを切り替える。
例えば、0x1B 0x28 0x4B という羅列を見つけると、ドイツ語にテーブルが切り替わる。

このように、理論的にはすべての言語を取り扱えるようにしたが、当時のコンピューターの処理能力や、記憶装置の容量といった問題もあり、世界中の文字を全て取り扱う必要があるといった需要が少なく、大きく普及はしなかった。
ASCII 互換なので、ASCII + 自国の文字集合、といった用途として使われることが多い。

1-5. JIS X 0208 (1978 年)

日本版の ISO/IEC 2022 として、日本工業規格として定められたもので、7 bit もしくは 8 bit を用いた 2 バイト文字コード
初版ののちに 3 回ほど改版されており、最新のもの (1997 年) では、ひらがな、カタカナ、漢字だけでなく、英数字やキリル文字、各種記号といった 6879 文字を収録している。

 8836 文字のうち、6879 文字が収録されているので、空き領域は自由に使える。
空き領域は機器ベンダが独自に定義する文字として利用されていることがあるが、これは環境依存文字といわれる。

 1-6. JIS X 0212 (1990 年)

JIS X 0208 に足りない文字(使用頻度が低いもの)を補うため、JIS X 0208 と組み合わせて使うことを前提とした文字集合
国文学研究資料館の研究に基づき、6067 文字が定義されているが、研究色が強く、一部の地理名などが表記できなかった。

JIS X 0208 と同様、ISO/IEC 2022 に準拠した 94 * 94 = 8836 文字の集合として定義されている。
そのため、JIS X 0208 を利用しているときに、エスケープ シーケンスを用いて呼び出される。

現在は、ほとんど利用されていない。

1-7. JIS X 0213 (2000 年)

 JIS X 0208 の上位互換で、JIS X 0212 があまり普及しなかったこともあり、「完成版の JIS X 0208」を策定する目的でつくられ、11233 文字を収録している。
通称 JIS 2000、あるいは 2004 年の改版後は JIS 2004 と呼ばれる。

94 * 94 = 8836 文字では足りないため、JIS X 0213 漢字集合 1 面と、JIS X 0213 漢字集合 2 面に分かれており、この二つをエスケープ シーケンス等で切り替えて使うことが想定されている。

新録された漢字の例では、SMAP草なぎ剛の「なぎ」の漢字などがある。
後述する Unicode でもこの漢字は収録されているが、今でも古い JIS 規格への対応のため、「なぎ」を平仮名表記しているメディアもある。

1-8. ISO/IEC 8859 (1987 年)

1987 年に初版、その後改版が進み、2009 年には ISO/IEC 8859-1 から ISO/IEC 8859-16 まで、廃棄された 12 を除き、15 部が存在する。

主にヨーロッパやアメリカで広く使われている文字コード

ASCII の上位互換として存在し、8 bit コードかつ 1 バイトで文字を表現する。
ASCII と互換性を保つため、GL 領域が ASCII になっており、GR 領域は各パートごとに文字表が定義される。

1-9. ISO/IEC 10646 (1993 年)

コンピュータの処理能力の増大を背景に、文字集合の組み合わせや切り替えを必要としないような、世界中の文字列をひとつの表に収めることを目的として開発開始。
ISO/IEC 2022 に基づいた各国のコードを平行移動するような形で、4 バイトで 1 文字を表そうとした。
各バイトをそれぞれ、群 (group)、面 (plane)、区 (row)、点 (cell) と表し、各面には ISO/IEC 2022 と同様に、0x20 から 0x7F までの GL 領域と、0xA0 から 0xFF までの GR 領域に、それぞれ文字を割り当てる。
ただし、2011 年には、利用されていないことから群は廃止された。

1990 年に国際標準の手前となる DIS (Draft International Standard) として策定された。
しかし、後述の Unicode が同時期に策定されており、同じ規格が 2 つ並立することを懸念し、最初の案は 1991 年 6 月の投票で否決された。
その後、UnicodeISO/IEC 10646 を互換性を持たせることになり、群 00 の面 00 に対して、Unicodeそっくりそのまま入れる構造にすることで、初版が確立された。

このような背景もあり、現在は Unicode とほぼ同義となっている。
Unicode に文字が追加されれば、ISO/IEC 10646 にも追加され、逆もまた同様とすることで、同期してアップデートされている。

1-10. Unicode (1993 年)

1980 年代にゼロックス社が提唱し、マイクロソフトやアップルなど、IT 企業が開発したもの。
目的は ISO/IEC 10646 と同様で、「世界中の文字を表す」ことを目的とした符号化文字集合

それぞれの文字は、U+FFFF のような形で、16 進数が割り当てられている。
制御文字は U+0000 から U+001F、U+007F、U+0080 から U+009F が割り当てられている。

当初は 16 bit (65536 文字) で全ての文字を符号化しようとしていたが、のちに不十分であったことが分かる。
それに伴い、当初の 16 bit で符号化したものと互換性を保つ形で、現在は 21 bit まで拡張されている。

1-11. JIS X 0221 (1995 年)

ISO/IEC 10646 の日本語版規格。
技術的に一致するように作られている。

 

2. 符号化文字方式

2-1. 漢字用 7 bit 符号 (JIS X 0208) (1978 年)

すべての文字が 2 バイト固定で、かつ 7 bit で表される。
7 bit で、最上位のビットは使わないことから、GL 領域のみを利用する。
JIS X 0208 で定義したコードポイントに、0x20 を足すことで、ビット符号化する。
これは、JIS のコードポイントが 1 から始まるが、1 をそのまま利用すると、制御文字領域 (CL) と被る。
そこで、図形文字領域に移動するため、0x20 を追加する。

2-2. Shift_JIS (JIS X 0208) (1982 年)

 マイクロソフトが中心となって策定されたもので、MS-DOS の標準日本語コードとして採択された。

JIS X 0201 の 8 bit 符号を元にしているため、8 bit 符号で、1 バイトと 2 バイトが混在する。
また、本来は制御文字の領域である、CR 領域を図形文字の領域として使う。

2 バイトの場合、1 バイト目 (0x8D 0xA1 なら 0x8D の方) として使える値は以下のとおりの 47 個。

・0x81 から 0x9F の間の値 (31 個)
・0xE0 から 0xEF の間の値 (16 個)

2 バイト目 (0x8D 0xA1 なら 0xA1 の方) として使える値は以下のとおりの 188 個。

・0x40 から 0x7E の間の値 (63 個)
・0x80 から 0xFC の間の値 (125 個)

よって、1 バイト目が 0x20 から 0x7F まで、もしくは 0xA0 から 0xDF までの値であれば、JIS X 0201 と判断し、それ以外であれば 2 バイト目も含め JIS X 0208 として取り扱う。
また、1 バイト目が 47 種類しか表現ができないので、JIS X 0208文字集合マッピングするのには、特殊な計算式を用いる。

JIS X 0201JIS X 0208 で、同じ文字を別の符号で表す箇所が出てくるため、その場合は以下のポリシーに従う。

JIS X 0201 ラテン文字集合と JIS X 0208 の両方に定義された文字の場合、JIS X 0201 の方を採用する。
JIS X 0201 片仮名集合と JIS X 0208 の両方に定義された文字の場合、JIS X 0208 の方を採用する。

結果的に、MS-DOS および Windows OS に搭載されたこともあり、JIS X 0208 準拠の符号化方式のなかでは、最も広く普及した。
エスケープ シーケンスを利用しない分、当時のコンピュータの処理能力では、文字を素早く処理できるといった利点があった。

・CP932 との違い

当初、マイクロソフトでは Shift_JIS に対して CP932 という管理番号が与えていたが、当時のマイクロソフトは、マイクロソフトの OS をインストールしたコンピュータを販売する企業に、CP932 の拡張を許していたため、CP932 の亜種が出回ることになる。

拡張を禁止したのは 1993 年だったが、この時点で主流であった NECIBM の拡張した CP932 を統合することで、新たな CP932 を作り、IANA に対して Windows-31J という名称で登録した。
これは Java では MS932 として呼称されている。

したがって、Windows-31 J と MS932 は Shift_JIS の上位互換として、いくつかの拡張文字列を含んでいる。

拡張方式である Shift_JIS-2004 にて、JIS X 0213 にも対応している。

2-3. EUC-JP (JIS X 0208) (1985 年)

EUC とは Extended Unix Code の略称。
1980 年代前半に、日本語 UNIX システム諮問委員会が UNIX 上で扱うための文字コードについて協議し、議論の結果をもとに、1985 年に AT&T によって定められた。

0x20 から 0x7F までの GL 領域は ASCII に固定し、0xA0 から 0xFF までの GR 領域は JIS X 0208JIS X 0201 の片仮名集合、JIS X 0212 のうちいずれかとなる。

8 bit で、1 バイトと 2 バイトが混在するが、1 バイトであれば ASCII もしくは制御文字となる。
2 バイトの場合は、0xA0 から 0xFF までの値を 2 つ並べる形で 1 文字を表現する。
そのため、JIS X 0208 で定義したコードポイントに 0xA0 を足したものが、ビット列となる。

制御文字 0x8E があると、直後の 1 文字は JIS X 0201 の片仮名集合として判定され、制御文字 0x8F があると、直後の 1 文字は JIS X 0212 として判定される。

特徴として、第 8 bit が 0 であれば ASCII として取り扱えるので、ASCII にしか対応していないシステムでも 8 bit 目が素通しであれば、EUC-JP でエンコードしていても、通過できるなど、ASCII 互換である。

また、ASCII と JIS X 0208 を併用することから、アルファベットなどは一意な符号化ができず、例えば A は 0x41 と 0xA3C1 という 2 種類の表現ができてしまうことになるが、その場合は ASCII の方式である 0x41 の方を採用するよう、定められている。

Unix Code という名の通り、Unix 上で利用されることが多い。

拡張方式である EUC-JIS-2004 にて、JIS X 0213 にも対応している。

2-4. ISO-2022-JP (JIS X 0208) (1986 年)

JUNET と呼ばれる、日本の研究用ネットワークで、ネットニュースや電子メールを日本語で送受信するために策定された。俗に JIS コードとも呼ばれる。

ASCII、JIS X 0201 ラテン文字集合、JIS X 0208 (1978 年版)、JIS X 0208 (1983 年版)  を混在させて使うが、実質は ASCII と JIS X 0208 (1983 年版) を切り替える。

7 bit で GL 領域のみを利用し、1 バイトと 2 バイトが混在する。
そのため、第 8 bit が 1 のコード範囲、0x80 以上は全く使用しない。
漢字用 7 bit 符号と同様に、JIS X 0208 のコードポイントに 0x20 を足すことでビット符号化する。

エスケープシーケンスで 4 種類の文字集合を GL 領域に呼び出し、切り替えて使う。
0x1B 0x24 0x42 を見つけたら、以降のビット列を JIS X 0208 (1983) として取り扱い、0x1B 0x28 0x42 の後は ASCII として取り扱う。
また、ファイル終端、もしくは改行 (0x0D 0x0A) の前には、状態が ASCII 以外であった場合、ASCII もしくは JIS X 0201 ラテン文字集合の状態に戻すためのエスケープ シーケンスを挿入する。

エスケープシーケンスがなければ ASCII のデータとなるので、ASCII 互換である。

7 bit の通信環境に適しているため、電子メールの通信で使われる。

拡張方式である ISO-2022-JP-2004 にて、JIS X 0213 に対応。

2-5. UCS-2 (Unicode) (1993 年)

UCS とは、Universal Multiple-Octet Coded Character Set の略称。
Unicode の符号化方式の最初期バージョンで、16 bit 固定で Unicode のコードポイントをそのままバイト列で表す。

16 bit の場合、65536 文字を収録できるが、世界中の文字を収めるには不十分であり、16 bit 固定であったために拡張性がないこともあり、普及しなかった。

2-6. UTF-8 (Unicode) (1993 年)

Unicode の一つのコードポイントを、1 バイトから 4 バイトまでの可変長で符号化する方式で、主流な Unicode の符号化方式の中では、唯一の ASCII 互換である。
1993 年に初版が提案された。

符号化は以下のとおり行う。

・U+0000 から U+007F: 0xxx xxxx で表す。
・U+0080 から U+07FF: 110x xxxx 10xx xxxx で表す。
・U+0800 から U+FFFF: 1110 xxxx 10xx xxxx 10xx xxxx で表す。
・U+10000 から U+10FFFF: 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx で表す。

5 バイト以上も一応定義されているが、2000 年には後述の UTF-16 で表現できる文字列を表すように制限されたため、事実上は 4 バイトまでとなる。

この符号化により、U+007F までが ASCII とビット列が一致する。
また、ビット列の先頭を見ることで、文字の区切りが明確に分かるようになっている。

UTF-8 は、このように先頭のビット列で判断するため、エンディアンの問題はない。
ただし、BOM (0xEF 0xBB 0xBF) は、UTF-8 のデータであることを明示するために、データの先頭に付与されることがある。

また、UTF-8 の亜種として、BMP 外の符号位置 (U+10000 から U+10FFFF) を UTF-16サロゲートペアで符号化し、さらにそれを UTF-8 に符号化したものが、一部プログラムの内部処理に使われていることがある。
Java のクラスファイルでは、Modified UTF-8 という形でこのような符号化が行われている。

現時点では最も普及している文字コードであり、特に Web ページの分野では、2018 年時点で 91 % ほどが UTF-8 で記述されていると言われている。

 2-7. UTF-16 (Unicode) (1996 年)

Unicode のバージョン 2.0 をもとに作られた、16 bit 単位の符号化形式。
上述の UCS-2 に「サロゲートペア」と呼ばれる概念を追加することで、より多くの文字を表現できるようにしたもの。

UnicodeBMP の文字は 16 bit の単位ひとつで表し、BMP の範囲では UCS-2 と互換性がある。
BMP 以外の面の文字、つまり U+10000 から U+10FFFF までの文字は、単位を 2 つ用いて表す。

BMP 以外の面の文字の場合は、サロゲートペアを使う。
サロゲートペアは、Unicode で文字が定義されていなかった U+D800 から U+DFFF までのコードポイントを表す。
また、U+D800 から U+DBFF を上位サロゲート、U+DC00 から U+DFFF を下位サロゲートとし、これを組み合わせて 1 文字を表現する。
例えば、0xD8 0x67 0xDE 0x3D というような、サロゲートペアを 2 つ組み合わせることで、1 文字を表せるようにしている。

また、バイトの並べ方によって、ビッグエンディアンUTF-16-BE と、リトルエンディアンの UTF-16-LE がある。
上位 8 bit が先にくるものがビッグエンディアンで、下位 8 bit が先にくるものがリトルエンディアンとなる。
例えば、U+6587 を表すときに 6587 のビット列で表現するのがビッグエンディアン、8765 のビット列で表現するのがリトルエンディアンである。
どちらの方式か判別するために、BOM (バイト順マーク) と呼ばれるビット列をデータの先頭に付与する場合があり、U+FEFF が BOM の符号にあたる。
先頭が FFFE であればリトルエンディアン、FEFF であればビッグエンディアンとして判定して、UTF-16 のビット列を解釈する。

Java の内部で利用されているなど、ファイルやネットワークでのデータ交換よりは、プログラムの内部処理で利用されていることが多い。

ちなみに、Windows のメモ帳で、ファイルの保存時に表示される Unicode とは、UTF-16 のことを表す。

2-8. UTF-32 もしくは UCS-4 (Unicode) (2002 年)

Unicode の符号位置をそのまま 32 bit で表現したもので、2002 年に Unicode のバージョン 3.2 にて定義されている。
そのため、1 つの単位は必ず 4 バイトとなる。

UCS-4 は、当時の理論上は群 00 から FF、かつ面 00 から FF までのすべての符号を利用できるように定義されていた。
UTF-32 は、そのうち群 00、面 00 から面 10 までしか使わないため、UCS-4 の部分集合と言えるが、UCS-4 でも、そのほかの群および面には文字が割り当てられていないため、同義と言ってよい。

UTF-16 と同様に、バイト順によりビッグエンディアン、リトルエンディアンがあり、BOM も使われる。

表現する文字列のほとんどが英数字である場合、必ず 4 バイトになる性質から、ほとんどのバイトが 00 になり、記憶領域を無駄にすると考えられることもある。
プログラムの内部処理で、様々な文字列を処理する必要がある場合に利用されることが多い。

 

並行 (Concurrent) 、並列 (Parallel) 、非同期 (Asynchrony) の違い

これもよく忘れるので、メモ。
情報学的に厳格な定義があるのかは分からないけれど、ネット上をいろいろ調べて、わかった結果を書いておきたいと思います。
# 数学的に厳密な定義とかではありませんので、ご注意ください。

1. 並行 (Concurrent)

並行とは、2 つ以上のタスクが存在する時、同時に処理されているかのように見せること。
例えば、1 つのコアの CPU で複数のタスクを処理する場合、CPU が計算できるタスクは 1 度にひとつなので、ナノ秒ごとのタイムスライスで区切って、タスクに CPU 時間を割り振るが、これは「並行」にあたる。

2. 並列 (Parallel)

並列とは、2 つ以上のタスクが存在する時、それらが同時に処理されること。
例えば、2 つ以上のマルチコア CPU で複数のタスクを処理できるなら、同じ時間でタスクを処理できることになるので、これは「並列」にあたる。

 

「並列であるならば、常に並行である」という定義もある。
「同時に処理されている」(並列)ならば、「同時に処理されているように見える」(並行)なので、たしかに正しい。

一方で、並行はあくまで、「同時に処理されているかのように見せる技法」であり、完全に同時に処理されている場合は並行の定義に含まない、として並行と並列を厳格に分ける定義も存在する。

個人的には、厳密に両者を分けた方が、定義として分かりやすいんじゃないかと思う。

 

3. 非同期 (Asynchrony)

非同期は、直接的に並行・並列と関係する概念ではない。
非同期は、あるタスクを止めず(ブロックせず)に、別のタスクを実行する技法のことであり、並行・並列どちらも実現方法として有り得る。
並行の場合、厳密には数ナノ秒は処理が停止することもあるが、タスクを停止したように見せず、別のタスクを実行できているかのように見えていれば、非同期と言って良い。

 

ソートアルゴリズム まとめ

とある企業の面接対策で、ソートアルゴリズムについてどんなものか、自分の理解のためにまとめました。
残念ながら、その企業からは残念ながらオファーがもらえなかったのですが・・・
(その話は誰かの役に立ちそうなので、ほとぼりが冷めた頃にブログ化しようと思います。)

今回はソートについて、今後の備忘録でまとめておきます。
計算時間とか空間量は自信がないので、間違ってたらどなたか指摘いただけると嬉しいです。

0. 基礎知識

安定 / 不安定なソート:
比較した結果、同値である要素が、順序性を保つ場合を「安定」、保たない場合を「不安定」という。
例えば、a = [2, 1, 2] のような配列を考えると、ソート後は [1, 2, 2] になるが、これを s とおく。
安定ソートの場合、a[0] = s[1]、a[2] = s[2] といった形で、順序が保たれることが保証されるが、不安定なソートである場合、a[0] = s[2]、a[2] = s[1] と、同値なものがソートしたあとに入れ替わることがある。

内部 / 外部ソート:
ソートは、メモリの領域を必要とする。
要素 n の配列であれば、少なくとも O(n) のメモリ領域が必要だが、アルゴリズムによっては、追加のメモリ領域が必要なものもある。
(Wikipedia では auxiliary という形で追加の空間計算量が表記されている。)
この追加のメモリ領域が、たかだか O(log(n)) 程度しか必要としないようなソートを内部ソート、O(n) 以上の領域を必要とするソートを外部ソートという。
本記事では、この追加のメモリ領域を「空間計算量」として表記している。

1. バブルソート

配列の値で隣どおしの値の大小を比較し、整列していく。
1 ループごとに、右端に最大値が入っていく。

平均計算時間: O(n^2)
最悪計算時間: O(n^2)
空間計算量: O(1)
安定/内部ソートである。

例:

[5, 1, 4, 3, 6, 2] - 5 と 1 を比較して交換
[1, 5, 4, 3, 6, 2] - 5 と 4 を比較して交換
[1, 4, 5, 3, 6, 2] - 5 と 3 を比較して交換
[1, 4, 3, 5, 6, 2] - 5 と 6 を比較して何もしない
[1, 4, 3, 5, 6, 2] - 6 と 2 を比較して交換
[1, 4, 3, 5, 2, 6] - 6 が右端に来たので、[1, 4, 3, 5, 2] でもう一度ソート。これを繰り返していくと、ソートが完了する。

2. 選択ソート

配列の中から最小値を探し、左端に入れていく。

平均計算時間: O(n^2)
最悪計算時間: O(n^2)
空間計算量: O(1)
不安定/内部ソートである。

例:

[5, 1, 4, 3, 6, 2] - 5 と 1 を比較して、小さい値として 1 を記録
[5, 1, 4, 3, 6, 2] - 1 と 4 を比較して、小さい値として 1 を記録
・・・これを繰り返し、最小値が 1 と分かるので、左端に 1 を入れ、1 がいた場所には 5 を入れる。
[1, 5, 4, 3, 6, 2] - 1 が左端に来たので、[5, 4, 3, 6, 2] でもう一度最小値を取得し、ソートを行う。これを繰り返してソートを完了させる。

3. 挿入ソート

1 番目の要素と 2 番目の要素を比較して整列した後、3 番目以降の要素は、比較済みの要素のうち適切な位置に挿入していく方法。

平均計算時間: O(n^2)
最悪計算時間: O(n^2)
空間計算量: O(1)
安定/内部ソートである。

例:

[5, 1, 4, 3, 6, 2] - 1 番目の要素と 2 番目の要素を比較し、整列する。
[1, 5, 4, 3, 6, 2] - 3 番目の要素である 4 を、1 と 5 と比較し、5 より小さいので 5 の前に挿入する。
[1, 4, 5, 3, 6, 2] - 4 番目の要素である 3 を、1, 4 と比較し、4 より小さいので 4 の前に挿入する。
[1, 3, 4, 5, 6, 2] - 5 番目の要素である 6 を 1, 3, 4, 5 と比較し、どの要素よりも大きいので、位置は動かさない。
[1, 3, 4, 5, 6, 2] - 6 番目の要素である 2 を 1, 3 と比較し、3 より小さいので 3 の前に挿入する。
[1, 2, 3, 4, 5, 6] - ソート完了。

4. マージソート

まず要素を 1 つずつの配列に分割する。
隣合う配列の先頭どうしを比較し、小さい値を新規配列の末尾に順次入れていく。
これを繰り返すことでソートを完了させる。

平均計算時間: O(nlog(n))
最悪計算時間: O(nlog(n))
空間計算量: O(n)
安定/外部ソートである。

例:

[5, 1, 4, 3, 6, 2] - 要素を 1 つずつに分解する。
[5] [1] [4] [3] [6] [2] - 配列 [5] と配列 [1] を比較して、小さい方の値である 1 を新規の配列に入れ、そのあとに大きい方の値である 5 を入れる。
[1, 5] [4] [3] [6] [2] - 配列 [4] と配列 [3] を比較して、小さい方の値である 3 を新規の配列に入れ、そのあとに大きい方の値である 4 を入れる。
[1, 5] [3, 4] [6] [2] - 配列 [6] と配列 [2] を比較して、小さい方の値である 2 を新規の配列に入れ、そのあとに大きい方の値である 6 を入れる。
[1, 5] [3, 4] [2, 6] - 配列 [1, 5] と配列 [3, 4] の先頭どうし、1 と 3 を比較して、小さい値である 1 を新規の配列に入れる。
[1] [5] [3, 4] [2, 6] - 配列 [5] と配列 [3, 4] の先頭どうし、5 と 3 を比較して、小さい値である 3 を、1 を入れた配列の末尾に入れる。
[1, 3] [5] [4] [2, 6] - 配列 [5] と配列 [4] を比較して、小さい値である 4 を、1, 3 を入れた配列の末尾に入れる。
[1, 3, 4, 5] [2, 6] - 配列 [1, 3, 4, 5] と配列 [2, 6] の先頭どうしを比較して、小さい値である 1 を新規の配列に入れる。
[1] [3, 4, 5] [2, 6] - 配列 [3, 4, 5] と配列 [2, 6] の先頭どうしを比較して、小さい値である 2 を 1 を入れた配列に入れる。これを繰り返してソート完了。

5. クイックソート

ピボットを基準として、ピボットより小さいリストと大きいリストに分割する。
小さいリストと大きいリストそれぞれについて、再びピボットを選び、それぞれ小さいリスト・大きいリストに分割する。
これを要素数が 1 になるまで繰り返すことで、ソートを完了させる。

平均計算時間: O(nlog(n))
最悪計算時間: O(n^2)
空間計算量: O(log(n))
不安定/内部ソートである。

例:

[5, 1, 4, 3, 6, 2] - ピボットとして中央の値である 4 を選択する。
[5, 1, 4, 3, 6, 2] - 左端から 4 以上の値を走査し、5 を見つける。右端から 4 以下の値を走査し 2 を見つける。これらの位置を交換する。
[2, 1, 4, 3, 6, 5] - 左の続きの位置から値を走査し、4 以上の値である 4 と、右の続きの位置から値を走査し、4 以下である 3 があるので、位置を交換する。
[2, 1, 3, 4, 6, 5] - ピボットである 4 を基準に配列を分割する。
[2, 1, 3] [4] [6, 5] - [2, 1, 3] のピボットとして、中央の値である 1 を選択する。
[2, 1, 3] [4] [6, 5] - [2, 1, 3] について左から 1 以上の値を走査し、2 を見つける。右から 1 以下の値を走査し、1 を見つける。これらの位置を交換する。
[1, 2, 3] [4] [6, 5] - ピボットである 1 を基準に、[1, 2, 3] を分割する。
[1] [2, 3] [4] [6, 5] - [2, 3] のピボットとして、2 を選択するが、特に交換は不要なので 2 を基準に [2, 3] を分割する。
[1] [2] [3] [4] [6, 5] - [6, 5] のピボットとして、6 を選択する。左から 6 以上の値を走査し、6 を見つける。右から 6 以下の値を走査し、5 を見つける。これらの位置を交換する。
[1] [2] [3] [4] [5, 6] - ピボットである 6 を基準に、[5, 6] を分割する。
[1] [2] [3] [4] [5] [6] - これをマージしてソート完了。

6. シェルソート

挿入ソートを改良したもので、挿入ソートが「ほとんど整列されたデータに対しては高速である」という性質を利用している。

与えられたソート対象を、4 つ飛びや 8 つ飛びのリストに分割して、それぞれのリストをソートしたあと、より短い間隔に縮めて分割・ソートを繰りかえす。
これを繰り返し、最終的に 1 つずつ挿入ソートしてソートを完了させる。

平均計算時間: O(n(log(n)^2)
最悪計算時間: O(n^2)
空間計算量: O(1)
不安定/内部ソートである。

例:

[5, 1, 4, 3, 6, 2] - 間隔 2 (1 つ飛び) のリストに分割する。
[5, 1, 4, 3, 6, 2] - [5, 4, 6] と [1, 3, 2] で挿入ソートを行う。[5, 4, 6] について 5 と 4 をまず交換する。
[4, 1, 5, 3, 6, 2] - 6 はすでに適切な位置にあるので、[5, 4, 6] はソート完了。続いて [1, 3, 2] について 1 と 3 を整列しようとするが、すでに適切な位置にあるので何もしない。
[4, 1, 5, 3, 6, 2] - [1, 3, 2] について 2 を 3 の手前に挿入する。これで間隔 2 のソートは完了。
[4, 1, 5, 2, 6, 3] - 続いて、間隔 1 のリストとして挿入ソートを行ってソート完了。

7. 分布数え上げソート

ソート対象の中で、最小値から最大値までに対応したメモリ領域を用意する。
例えば、最小値が 0 で最大値が 9 なら、0 から 9 までの値に対応するよう 10 の領域を用意する。
次に、値を走査し、数に応じて、対応するメモリ領域に格納する数をインクリメントする。例えば、値が 3 であれば、Memory[3] に 1 を格納する。
全ての数についてインクリメントの処理が終わったら、メモリに格納された値を順番に見ていき、格納されている数だけ、メモリ領域が対応している数をアウトプットすればソート完了となる。例えば、Memory[0] = 1, Memory[1] = 2, Memory[2] = 1 なら、[0, 1, 1, 2] といった具合になる。

平均計算時間: O(n + k)
最悪計算時間: O(n + k)
空間計算量: O(k)
(ここで k は、ソート対象の最小値と最大値の幅を表す。)
安定/内部ソートである。

例:

[5, 1, 4, 3, 6, 2] - 最小値 が 1、最大値が 6 なので、6 つのメモリ領域を用意する。
[5, 1, 4, 3, 6, 2] - 値を走査し、対応するメモリ領域をインクリメントする。今回は 6 つすべてに 1 が入る。
[5, 1, 4, 3, 6, 2] - メモリに格納された値をもとに、数値をアウトプットする。
[1, 2, 3, 4, 5, 6] - ソート完了。

8. ヒープソート

2 分木:
各ノードが高々 2 個の子ノードを持つようなツリー構造。ノードが持つ値による順序性は特に考慮しない。

ヒープ:
子ノードが親ノードより常に大きいか等しい、もしくは子ノードが親ノードより常に小さいか等しい、という性質を満たす 2 分木のこと。

この 2 種類の概念を利用して、下記のようにソートする。

(1) ソート対象をそのまま 2 分木にする。
(2) 最大値が根ノードに来るようにヒープを構成する。
(3) 根ノードが最大値なので、これを新規のリストに入れる。
(4) 最も子要素のノードを、根となるノードに入れて、再びヒープを構成する。
(5) 根ノードが最大値となるので、これを (3) で利用したリストの左端に入れる。
(6) これを繰り返すことで、ソートされたリストができる。

平均計算時間: O(nlog(n))
最悪計算時間: O(nlog(n))
空間計算量: O(1)
(平均・最悪計算時間は nlog(n) だが、ヒープの構成などの処理により、クイックソートマージソートより遅くなることが多い。)
不安定/内部ソートである。

 

Azure Event Hubs をいい感じにわかりやすくしたい

 最近、仕事で Azure Event Hubs を使う機会が多くあります。
しかしながら、どうも Microsoft さんの公式ドキュメントが分かりづらい・・・(失礼
そこで、Event Hubs をどうにか、ざっくりいい感じに、まとめれないかと試みました。
私の個人的な理解なので、正しくない部分も多々あるかもしれませんが、もし気づいた方はご指摘いただけると幸いです。

 

1. Event Hubs とは?

AMQP や HTTP といったプロトコルを使って、大量のメッセージを中継することができるハブです。(公式ドキュメントでは、メッセージを「イベント」と記載しているようです。)
OSS 製品だと RabbitMQ などが近いでしょうか。
例えば、数千台の機器からセンシングしたデータを Event Hubs に送信し、受信用のクライアントが Event Hubs からデータを読み出し加工を行い、データベースに保存することができます。

 Azure 上の類似サービスでは、IoT Hub や Service Bus などがあります。

IoT Hub は Event Hubs 互換エンドポイントと呼ばれるものをもっているくらいなので、かなり類似のサービスではありますが、IoT Hub は受信側もクライアントも多くのデバイスを接続できます。

Service Bus は、中継するメッセージに対して、多くのコントロールができるサービスです。
例えば、Service Bus には、配信に失敗したメッセージを格納する Dead Letter Queue という機能や、受信したメッセージをキューから削除するような機能があります。

 

2. Event Hubs の注意点

・Event Hubs は、送信クライアントはたくさん接続してメッセージを送ることができますが、受信クライアントはあまり多くを繋ぐことができません。
あくまで受信側はメッセージの処理サーバーを繋ぐような用途が想定されています。

・送信および受信クライアントともに、IP アドレスで接続を制限することができません。
パブリックなエンドポイントが外部に公開されます。

・Event Hubs に着信したメッセージを削除する操作ができません。
着信したメッセージは、メッセージの保存期間が過ぎるまで、消すことができません。
また、検証してみたところ、保存期間が過ぎたらメッセージが必ず消されるわけではなく、保存期間はあくまで「いつまで確実に残しておくか」を表す設定のようです。

・どこまでメッセージを読み出したか、記録する機能は、Event Hubs 側にはありません。
ただ、どの地点からメッセージを読み出すか、指定することはできるので、受信クライアント側で、どこまでメッセージを読み出したか管理する必要があります。
どの地点からメッセージを読み出すか指定しない場合、Event Hubs では処理済みのメッセージを削除する機能がないので、最初からメッセージを読みなおすことになります。

 

3. Event Hubs の構成

名前空間

Azure で Event Hubs を作成する際は、まず一番最初に名前空間と呼ばれるものを定義します。
名前空間は Event Hubs の FQDN に利用されます。
たとえば、名前空間hoge とすると、hoge.servicebus.windows.net という FQDN が払い出されます。
FQDN として利用されるので、名前空間はグローバルでユニークである必要があります。

 Event Hub

名前空間を作成したあとに、Event Hub の実体が作成できます。
これは、ひとつの名前空間の中に複数の Hub が作成できるようになっており、現在はひとつの名前空間に対し最大で 10 個まで作れるようです。
例えば、hoge.servicebus.windows.net/hub1 と hoge.servicebus.windows.net/hub2 という形で、複数作成できます。
また、FQDN に使われるものではないため、グローバルでユニークである必要はなく、名前空間のなかでユニークであれば OK です。

スループット ユニット

Event Hub を構成するインスタンスで、増やすほど送受信のスループットが向上しますが、その分料金も高くなります。
最近は auto-inflate という機能が追加され、通信負荷に応じて自動でスケールできるようになったようです。

パーティション

個人的に、Event Hub で一番難しい概念が、このパーティションだと思います。

Event Hub に到達したメッセージは、パーティションに分割して、保存されます。
たとえば、パーティションの数を 4 つと設定した状態で、メッセージを 8 件送信すると、パーティションあたり 2 件ずつメッセージが保存されることになります。

Event Hubs では、メッセージの順序性はパーティション内で保証され、パーティションをまたいでは保証されません。つまり、メッセージの送信順を 0 - 7 とすると、

(Partition 0) 0, 3
(Partition 1) 1, 2
(Partition 2) 4, 6
(Partition 3) 5, 7

といった形で格納される場合があり得る、ということになります。

また、メッセージの送信時にはパーティションを直接指定して送ることもできますが、負荷が偏るため、これは推奨されないようです。
順序を保って送る必要がある場合のみ、パーティションを指定して送るようにしましょう。

パーティションの数は、受信クライアントの台数にかかわります。
さきほどの例だと、パーティションが 4 つなので、受信クライアントは最大で 4 つ接続するような方式が考えられます。
つまり、メッセージは 4 並列に処理できることになります。
もちろん、ひとつのパーティションに複数の受信クライアントを接続することもできますが、Event Hubs ではメッセージを読み出してもメッセージは削除されないため、メッセージ処理が重複して行われることになります。

パーティションの数は、増やしたとしても料金は変わらないようで、最大で 32 個まで作成できます。
しかし、後から変更できないので、あらかじめどこまで並列にするか予測する必要があるかと思います。

コンシューマー グループ

これもパーティションの次に難しい概念だと思います。

受信クライアントは、Event Hub のパーティションに直接接続するのではなく、コンシューマー グループという概念を経由して接続するような形になります。

ひとつのコンシューマー グループを経由して、あるパーティションに接続する受信クライアントは、1 つのみであることが推奨されています。

これだけでは分かりづらいので、以下の設定値の場合、接続がどうなるか考えてみます。

パーティション数:4
受信クライアント数:2
コンシューマー グループ数:1

接続はこんな感じになります。

(Partition 0) <- (Consumer Group 0) <- (Receiver 0)
(Partition 1) <- (Consumer Group 0) <- (Receiver 0)
(Partition 2) <- (Consumer Group 0) <- (Receiver 1)
(Partition 3) <- (Consumer Group 0) <- (Receiver 1)

一応、仕様としては、ひとつのコンシューマー グループを経由して、パーティションに 5 つまでの受信クライアントを接続できるようですが、1 つにすることが推奨されています。
もしひとつのパーティションに複数の受信クライアントを接続したいときは、コンシューマーグループを複数用意したほうが、管理上見やすいという意味合いがあるからかと思います。

 

 

というわけで、Event Hubs サービスの主要概念を見てきました。
いつか、EventProcessorHost など、Microsoft にて作られている、受信クライアントのライブラリなども紹介できればと思います。