第7章 Avroスキーマ

目次

Avroスキーマの作成
Avroスキーマ定義
プリミティブ・データ型
複合データ型
Avroスキーマの使用
スキーマ展開
スキーマ変更のルール
ライター・スキーマとリーダー・スキーマ
スキーマ展開の機能
ストアのAvroスキーマの管理
スキーマの追加
スキーマの変更
スキーマの無効化と有効化
スキーマの表示

Avroを使用すると、レコードの値のデータ・スキーマを定義できます。このスキーマは、値で使用できるフィールドとそのデータ型を表します。

Oracle NoSQL Databaseレコードの値部分にスキーマを適用するには、Avroバインディングを使用します。このバインディングを使用して、書込み前の値のシリアライズと読取り後の値のデシリアライズを行います。このバインディングを使用するには、アプリケーションでAvroデータ形式が使用されている、つまり格納値がそれぞれスキーマに関連付けられている必要があります。

Avroスキーマを使用すると、シリアライズされた値を領域効率に非常に優れたバイナリ形式で格納できます。格納される各値は、サイズの小さい内部スキーマ識別子(1から4バイト)以外のメタデータを持ちません。このような参照情報がキーと値のペアごとに格納されます。このように、シリアライズされたAvroデータ形式は、常に最小限のオーバーヘッドで、そのシリアライズに使用したスキーマに関連付けられます。この関連付けはアプリケーションに対して透過的に行われ、内部スキーマ識別子はAvroCatalogクラスの提供するバインディングによって管理されます。アプリケーションが内部識別子を直接参照したり、使用することはありません。

Avro APIは、Apacheソフトウェア財団が提供するオープン・ソース・プロジェクトとして開発されました。公式サイトはhttp://avro.apache.orgです。

また、AvroではJackson APIを使用してJSONを解析します。これはOracle NoSQL DatabaseとJSONベースのシステムを統合しているユーザーにとって関心の高い内容です。Jacksonの公式サイトはhttp://wiki.fasterxml.com/JacksonHomeです。

Avroスキーマの作成

AvroスキーマはJSON形式で作成します。JSON (JavaScript Object Notationの略)は、人が簡単に読み書きできることを目的とした、軽量でテキストベースのデータ交換形式です。JSONについては様々な場所(Webや市販の書籍)で解説されています。ただし公式の解説は、IETFのRFC 4627 (http://www.ietf.org/rfc/rfc4627.txt?number=4627)を参照してください。

Avroスキーマを記述するには、次のように、スキーマを指定するJSONレコードを作成します。

{
     "type": "record",
     "namespace": "com.example",
     "name": "FullName",
     "fields": [
       { "name": "first", "type": "string" },
       { "name": "last", "type": "string" }
     ]
} 

前述の例は、ストア内のキーと値のペアの値部分で使用されるスキーマを指定したJSONレコードを示しています。人の姓名のスキーマが指定されています。

このレコードには次の4つのフィールドがあることに注意してください。

  • type

    JSONフィールドの型を指定します。Avroスキーマでは、スキーマの最上位で指定する場合は必ずrecordとする必要があります。record型は、複数のフィールドが定義されることを示します。

  • namespace

    オブジェクトが存在する名前空間を指定します。基本的には、ユーザーや組織にとって意味のあるURIにします。同じ名前を複数のスキーマ型で共有する可能性がある場合に、それぞれを区別するために使用します。

  • name

    これはスキーマ名で、名前空間と組み合せた場合に、ストア内でスキーマを一意に識別します。前述の例で、スキーマの完全修飾名はcom.example.FullNameです。

  • fields

    これが実際のスキーマ定義です。値に含まれるフィールドの種類と各フィールドのデータ型を定義します。フィールドには、整数や文字列などの単純なデータ型や複合データ型を指定できます。これについては、次に詳しく説明します。

    スキーマのフィールド名は[A-Za-z_]で始まり、続いて[A-Za-z0-9_]のみを使用する必要があることに注意してください。

スキーマを使用するには、スキーマをフラット・テキスト・ファイルに定義してから、適切なコマンドライン呼出しを使用してストアに追加する必要があります。また、なんらかの形でユーザーのコードにも提供する必要があります。コードで使用しているスキーマは、ストアに追加されたスキーマと一致する必要があります。

この章の残りの部分で、スキーマおよびそれをストアに追加する方法について説明します。スキーマをコードで使用する方法の詳細は、「Avroバインディング」を参照してください。

Avroスキーマ定義

Avroスキーマ定義はJSONレコードです。レコードなので、JSON配列に編成された複数のフィールドで定義できます。この各フィールドでは、フィールド名とフィールド型を指定します。フィールド型には、整数のような単純なものや、別のレコードのような複雑なものを設定できます。

たとえば、次の簡単なAvroスキーマ定義は、人の年齢のみを含む値に使用できます。

{
    "type" : "record",
    "name" : "userInfo",
    "namespace" : "my.example",
    "fields" : [{"name" : "age", "type" : "int"}]
} 

データ格納の要件がこのように単純な場合は、整数を単なるバイト配列でストアに格納することもできます。(ただし、これはベスト・プラクティスではありません。)

前述の例で、単一フィールドのスキーマを定義している場合でも、スキーマ定義の最上位の型はrecord型であることに注意してください。Oracle NoSQL Databaseでは、必要なフィールドが1つのみでも、最上位の型にはrecordを使用する必要があります。

また、スキーマのフィールドにはデフォルト値を定義しておくことをお薦めします。これはオプションですが、スキーマを変更する可能性がある場合は、そうすることで様々な問題を回避できます。デフォルト値を定義するには、default属性を使用します。

{
    "type" : "record",
    "name" : "userInfo",
    "namespace" : "my.example",
    "fields" : [{"name" : "age", "type" : "int", "default" : -1}]
}

単一フィールドの定義を使用することはほとんどありません。複数のフィールドを追加するには、fieldsフィールドに配列を指定します。次に例を示します。

{
    "type" : "record",
    "name" : "userInfo",
    "namespace" : "my.example",
    "fields" : [{"name" : "username", 
                "type" : "string", 
                "default" : "NONE"},

                {"name" : "age", 
                "type" : "int", 
                "default" : -1},

                {"name" : "phone", 
                "type" : "string", 
                "default" : "NONE"},

                {"name" : "housenum", 
                "type" : "string", 
                "default" : "NONE"},

                {"name" : "street", 
                "type" : "string", 
                "default" : "NONE"},

                {"name" : "city", 
                "type" : "string", 
                "default" : "NONE"},

                {"name" : "state_province", 
                "type" : "string", 
                "default" : "NONE"},

                {"name" : "country", 
                "type" : "string", 
                "default" : "NONE"},

                {"name" : "zip", 
                "type" : "string", 
                "default" : "NONE"}]
} 

前述のスキーマ定義には様々な情報が含まれています。このように単純ですが、埋込みレコードを使用すれば、さらに構造を追加できます。

{
    "type" : "record",
    "name" : "userInfo",
    "namespace" : "my.example",
    "fields" : [{"name" : "username", 
                 "type" : "string", 
                 "default" : "NONE"},

                {"name" : "age", 
                 "type" : "int",
                 "default" : -1},

                 {"name" : "phone", 
                  "type" : "string", 
                  "default" : "NONE"},

                 {"name" : "housenum", 
                  "type" : "string", 
                  "default" : "NONE"},

                  {"name" : "address", 
                   "type" : {
                         "type" : "record",
                         "name" : "mailing_address",
                         "fields" : [
                            {"name" : "street", 
                             "type" : "string", 
                             "default" : "NONE"},

                            {"name" : "city", 
                             "type" : "string", 
                             "default" : "NONE"},

                            {"name" : "state_prov", 
                             "type" : "string", 
                             "default" : "NONE"},

                            {"name" : "country", 
                             "type" : "string", 
                             "default" : "NONE"},

                            {"name" : "zip", 
                             "type" : "string", 
                             "default" : "NONE"}
                          ]},
                          "default" : {}
                }
    ]
} 

注意

必要なレコード定義がストア全体で1つのみという状況は考えにくいです。通常は、複数の種類のレコードを使用することになります。これに対処するには、各レコード定義をそれぞれ別のファイルに設定します。その後で、各レコード定義を処理するコードを記述する必要があります。その方法については、この章の後半で説明します。

プリミティブ・データ型

前述のAvroスキーマの例では、文字列と整数のみを示しました。次に、Avroでサポートされているプリミティブ型の完全なリストを示します。

  • null

    値なし。

  • boolean

    バイナリ値。

  • int

    32ビットの符号付き整数。

  • long

    64ビットの符号付き整数。

  • float

    IEEE 754単精度(32ビット)浮動小数点数。

  • double

    IEEE 754倍精度(64ビット)浮動小数点数。

  • bytes

    8ビット符号なしバイトの順序。

  • string

    Unicode文字の順序。

これらのプリミティブ型には固有の属性がありません。プリミティブ型の名前は、型名の定義でもあります。たとえば、スキーマstringは、次と同等です。

{"type" : "string"}

複合データ型

前の項で説明したプリミティブ・データ型の他に、Avroでは6つの複合データ型(Record、Enum、Array、Map、UnionおよびFixed)をサポートしています。これらについてはこの項で説明します。

record

レコードは属性がカプセル化されたもので、すべてを組み合せて1つのものを表します。Avroレコードでサポートしている属性は次のとおりです。

  • name

    レコードの名前(必須)。レコードの内容を識別することを目的としています。たとえば、PersonInformationAutomobilesHatsBankDepositなどです。

    レコード名は[A-Za-z_]で始まり、続いて[A-Za-z0-9_]のみを使用する必要があることに注意してください。

  • namespace

    名前空間はオプションの属性で、レコードを一意に識別します。オプションですが、レコードの名前が他のレコードの名前と競合する可能性がある場合は使用する必要があります。たとえば、従業員に関するレコードを例に考えてみましょう。従業員には様々な種別(正社員、パートタイム従業員、契約社員)があるとします。その場合は、名前EmployeeInfoに、FullTimePartTimeおよびContractorという名前空間を組み合せれば、3つのレコード型すべてを作成できます。つまり、正社員を示すレコードの完全修飾名は、FullTime.EmployeeInfoになります。

    また、ストアに様々な組織の情報が含まれている場合は、レコードで使用されている組織を識別する名前空間を使用して、レコード名の競合を回避できます。この場合、完全修飾レコードの名前は、My.Company.Manufacturing.EmployeeInfoMy.Company.Sales.EmployeeInfoのようになります。

  • doc

    この属性(オプション)は、単にレコードに関するドキュメントを表します。これを解析してスキーマとともに格納すれば、Avro APIを使用してスキーマ・オブジェクトから利用できますが、シリアライズでは使用されません。

  • aliases

    この属性(オプション)はJSON配列の文字列で、レコードの代替名を表します。JSONスキーマは名前変更の操作に対応していないので注意してください。最初にname属性で定義した名前以外を使用してスキーマを参照する場合は、別名を使用してください。

  • type

    必須属性で、キーワードrecordまたは埋込みのJSONスキーマ定義のいずれかです。最上位のスキーマ定義に対する属性の場合は、recordを使用する必要があります。

  • fields

    JSON配列を表す必須属性で、スキーマ内のすべてのフィールドを一覧で示します。各フィールドにname属性とtype属性が必要です。また、doc、order、aliasesおよびdefault属性が指定されることがあります。

    • name、type、docおよびaliases属性の使用方法は、この項の前半で説明した方法とまったく同じです。

      レコード名と同様に、フィールド名は[A-Za-z_]で始まり、続いて[A-Za-z0-9_]のみを使用する必要があります。

    • order属性はオプションで、Oracle NoSQL Databaseでは無視されます。この属性を使用するアプリケーション(Oracle NoSQL Database以外)では、このフィールドで定義する当該レコードのソート順は、この属性で示します。有効な値は、ascendingdescendingまたはignoreです。この機能の詳細は、http://avro.apache.org/docs/current/spec.html#orderを参照してください。

    • default属性はオプションですが、スキーマ展開のサポートのためにお薦めします。これは、スキーマ展開の目的のみに使用されるフィールドにデフォルト値を指定します。default属性を使用するということは、新しい値のオブジェクトを作成する際にフィールドをイニシャライズしなくてもよいということではありません。すべてのフィールドは、default属性が存在するかどうかに関係なく、イニシャライズする必要があります。

      スキーマ展開については、「スキーマ展開」で説明します。

      default属性で使用できる値は、フィールドの型によって異なります。ユニオン型のデフォルト値は、ユニオンの最初のフィールドに依存します。バイト型および固定型フィールドのデフォルト値はJSON文字列です。

Enum

Enumは列挙型で、次の属性をサポートします。

  • name

    列挙の名前を指定する必須属性。この名前は[A-Za-z_]で始まり、続いて[A-Za-z0-9_]のみを使用する必要があります。

  • namespace

    列挙のname属性を修飾する属性(オプション)。

  • aliases

    列挙の代替名のJSON配列を表す属性(オプション)。

  • doc

    列挙のコメント文字列を表す属性(オプション)。

  • symbols

    列挙のシンボルを名前の配列として表す必須属性。これらのシンボルは[A-Za-z_]で始まり、続いて[A-Za-z0-9_]のみを使用する必要があります。

次に例を示します。

{ "type" : "enum",
  "name" : "Colors",
  "namespace" : "palette",
  "doc" : "Colors supported by the palette.",
  "symbols" : ["WHITE", "BLUE", "GREEN", "RED", "BLACK"]}

Array

配列フィールドを定義します。items属性(必須)のみをサポートします。items属性では、配列内の項目の型を指定します。

{"type" : "array", "items" : "string"}

Map

マップとは、データをキーと値のペアに編成する結合配列またはディクショナリです。Avroマップのキーは文字列にする必要があります。Avroマップでサポートしている属性はvaluesのみです。この属性は必須で、マップの値部分の型を定義します。

{"type" : "map", "values" : "int"}

Union

ユニオンは、フィールドが複数の型を持つ可能性を示す場合に使用します。JSON配列で表します。

たとえば、文字列またはNULLのフィールドがあるとします。その場合、ユニオンは次のように表します。

["string", "null"]

使用方法は次のとおりです。

{
     "type": "record",
     "namespace": "com.example",
     "name": "FullName",
     "fields": [
       { "name": "first", "type": ["string", "null"] },
       { "name": "last", "type": "string", "default" : "Doe" }
     ]
} 

Fixed

固定型は、バイナリ・データの格納に使用できる固定サイズ・フィールドを宣言する場合に使用します。2つの必須属性(フィールドの名前とバイト数で表したサイズ)があります。

たとえば、サイズが1MBの固定フィールドを定義するには、次のようにします。

{"type" : "fixed" , "name" : "bdata", "size" : 1048576}