Skip to content

Instantly share code, notes, and snippets.

@kazuho
Last active February 20, 2016 18:47
Show Gist options
  • Save kazuho/5027236 to your computer and use it in GitHub Desktop.
Save kazuho/5027236 to your computer and use it in GitHub Desktop.
MessagePackの文字列型追加において、Extended型を導入する提案

#MessagePackの文字列型追加において、Extended型を導入する提案

本提案は https://gist.github.com/frsyuki/5022569 https://gist.github.com/frsyuki/5022460 において提案された「バイナリ型」の構造に変更を施すものである。

##解決しようとする問題

  • MessagePackへの拡張は今後行われないとしても、独自に拡張する提案が今後頻発しそう
  • 拡張フォーマットは、既存のMessagePackに対して後方互換にならない(なりようがない)ため、相互運用性を損なう可能性が高い。これが問題

##提案する手法

今回文字列型とバイナリ型を導入するにあたり、型システムを拡張可能なものとし、拡張された型は、古いバージョンのcodecにおいては、バイナリとして扱われるような設計とすべき。

こうすることで、今後型の拡張が行われたとしても、旧バージョンのツールでラウンドトリップ問題が発生しないことを保証できるし、結局、ツールというものは自分の知ってる型のデータしか扱わないので、それで問題ない。

##フォーマット

https://gist.github.com/frsyuki/5022569 に以下の変更を施すべき。

0xc4-0xc9,0xd4,0xd5 FixExtended (0bytes - 7bytes extended type)
0xd6 Extended 8 (extended type)  // new
0xd7 Extended 16 (extended type)  // new
0xd8 Extended 32 (extended type)  // new

0xd9 string 8 (String type)  // new
0xda string 16 (String type)  // changed from raw 16
0xdb string 32 (String type)  // changed from raw 32

Extended = ExtendedTAG n-OCTETS (nは各FixExtended,Extended8,16,32で規定)

ExtendedTAG = 0x00 - binary 0xf0-0xff - private extension

全てのtypeを使い切るが、今後の拡張はExtendedTagを使って可能だし、その場合確実に後方互換性を保証できる。

###拡張例

たとえばtime_t型をTAG=0x31として追加する場合:

0xc7 0x31 0x51 0x2a 0xd5 0xb0 ; is Feb 25 03:08:32 2013 (0x512ad5b0)

たとえばShift_JIS型をTAG=0x32として追加する場合:

0xd6 0x0a 0x32 0x82 0xb1 0x82 0xf1 0x82 0xc9 0x82 0xbf 0x82 0xcd ; こんにちは

##FAQ

###Q.256個のExtendedTAGを使い切ったらどうなるか?

ExtendedTAGを1バイトとして定義しても可変長の構造として定義しても、後方互換性の観点から言うと優劣は存在しない。

なぜなら、2バイトのExtendedTAGが必要になったら、「データの先頭もExtendedTAGだよーというExtendedTAGを導入すればいい」から。その必要性がある実装は対応するだろうし、対応しない実装では、未知の型をもつバイナリとして扱われるから、後方互換性は維持される。

そこで、現時点でExtendedTAGを可変長フォーマットとして提案することで、提案がリジェクトされる可能性を危惧し、このような提案としている。

@methane
Copy link

methane commented Feb 25, 2013

ユーザー拡張型を扱えるようにしておくことで、今後型を追加する要望がでた時に「BSON使え」ではなく
「ユーザー拡張型使え」と言えるのはとても良いと思います。

@kenn
Copy link

kenn commented Feb 25, 2013

Extendedよく練られてて良さそうですね。

1点、FixExtendedが飛び地になってしまうのが仕様としてのシンプルさを損なってるような気がするので、0xc4-0xc7110001xxと下位2 bitだけ見ればすむようにするのも、他のFix系仕様との一貫性(仕様認知コストの低減)という意味ではアリのような気もします。

もちろんサイズの最適化をつきつめればmax 3 bytesよりはmax 7 bytesのほうが嬉しいのですが、スパースなデータ構造を扱うときに大量に出現するnullが1バイトずつ短くなるという効果はあるので、仕様のわかりやすさ・一貫性という意味では検討に値するような気がしますが、いかがでしょうか。

@kazuho
Copy link
Author

kazuho commented Feb 25, 2013

仕様のシンプルさという意味ではおっしゃるとおりですね。ですが、実装コストは大してかわらないと思います。

0xc4-0xc7のみを使う方法のもうひとつの問題は、reserved bytes を残すことです。

reserved bytes を残すことにどのような意味があるでしょう?

将来の拡張のため? いえ、その種の拡張は互換性を破壊します。今後全ての拡張を ExtendedType を使って行えるようにすることで互換性を壊さないようにしよう、というのが僕の提案です。reserved bytes を残すことはこの目的に反します。

僕は基本的に、互換性を壊す変更を入れるようなデータフォーマットはクソだと思っています。多くの人は、MessagePackに、相互運用性を求めているというのが僕の理解です。

文字列型を追加することで、MessagePackへのそのような信頼が揺らぐだろうと僕は考えています。彼らは思うと思います(僕なら思います)。「変更は、今回だけよ。って言いながら、どうせまたやるんでしょ。MessagePackは安定なフォーマットじゃないんでしょ」って。特に、声の大きい人が型の追加を求めているような現状において、この懸念は正当なものだと思います。

その懸念を払拭するために、文字列型の導入と同時に、今後非互換な変更が入り得ないような設計にすることが望ましい、というのが僕の意見であり、本提案の背景にある考え方です。

@frsyuki
Copy link

frsyuki commented Feb 25, 2013

@kazuho reserved bytesを残すべきではないという意見に賛成です。 @kenn
その意味で 0xc1 も NEVER USED に割り当てるのが良いのでは無いかと思っています。

NEVER USED を作る理由は:例えばJava版の実装だと、デシリアライザの実装で「次の型」を表すフィールドを1バイトで持っています。そこで「まだ読んでない」を表すために 0xc6 を使っている。0xc6は読んだ時点でエラーになるので、「次の型」として0xc6が使われることは無い。
特に 0xc6 にこだわりはなく、0xc6 を 0xc1 に変えるのは実装の詳細の変更だけで済みます。

@kazuho
Copy link
Author

kazuho commented Feb 26, 2013

@frsyuki @kenn
個人的には 0xc1 に TinyExtended8 (つまり8バイト長のExtended) を割り振ることができると、圧縮率上がるし、拡張ポイントを完全に防げるかなぁ、と思わないでもないです。

あるいは 0xc1 を never に残す場合(僕は現時点でこちらのアプローチに賛成です)、TinyExtended を本当に 0-7 バイトに割り当てるのでいいのか、という話もあります。たとえば、0 1 2 4 6 8 10 12 のほうがいいのかもしれない。だが、歯抜けになるのも良くないよねという(特に小さいデータほど圧縮効率が悪くなるのは、データ型の分布を事前に予測できないというのであれば避けるべきですから)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment