⚛️

Apache Ozoneに関する雑感: メタデータ

2024/12/30に公開

諸事情でApache Ozoneに関する情報をまとめたくなり、自分用の備忘録として記載しておきます。特に内部実装について記載します。本記事は随時更新予定です。もし間違っていたらこっそり教えてください。

Apache Ozoneとは

Apache Ozoneは、Hadoopエコシステム向けに開発された、スケーラブルで冗長性のある分散オブジェクトストアです。従来のHDFSが抱えていた、NameNodeのスケールアップ限界による小ファイル問題やオブジェクト数上限などの課題を解決するために生まれました。

主な特徴は以下の通りです。

  • 高いスケーラビリティ: 数十億個のオブジェクトまで拡張可能で、小ファイルと大ファイルの両方を効率的に管理できます。
  • S3互換性: S3プロトコルをサポートしており、AWS CLIなどS3互換ツールやライブラリからアクセス可能です。これにより、Hadoop以外のシステムとの連携も容易になります。
  • 高可用性: 複数のメタデータ管理コンポーネント(OMとSCM)を持ち、それぞれがRAFTコンセンサスアルゴリズムに基づいて内部状態を整合させているため、高い可用性を実現しています。
  • コンテナ環境対応: YARNやKubernetesなどのコンテナ環境での動作をサポートしており、クラウドネイティブな環境にも適応可能です。
  • 複数のAPIサポート: S3 APIに加えて、Hadoop File System APIなど、複数のAPIを通してアクセスできます。

Ozoneは、従来のHDFSに比べて柔軟性と拡張性に優れており、ビッグデータストレージの選択肢として注目されています。特に、大量のオブジェクトを扱う場合や、S3互換性を必要とする場合に有効です。

参考資料:

本題

HDFSではNameNodeにメタデータ管理を集約させていたので、atomicかつ O(1) なディレクトリのrenameなどを可能としていた。その挙動を前提としたアプリケーション(e.g., MapReduce, Hive)も多い。しかし結果としてNameNodeがボトルネックとなりスケーラビリティの限界を迎えた(Small File Problemsなど)。Ozoneではその反省を生かし、名前空間の管理とデータ管理を分離している。

ここで名前空間の管理とは、ディレクトリ情報やファイル情報に関するメタデータの管理のことである。またデータ管理とは、データの実体がどのワーカーにあるかのメタデータの管理である。HDFSでは両方をNameNodeが管理し、しかもそのメタデータをメモリに持っていた。

Ozoneでは前者をOzone Manager, 後者をStorage Container Managerが管理することにより分離した。また、HDFSではあるデータをそのまま一つのブロック(デフォルト: 256MB)としてワーカーに格納し、その情報をNameNodeが管理していたが、OzoneではContainerと呼ばれるもう少し大きい単位(デフォルト: 5GB)でStorage Container Managerが管理する。Container には複数のデータが格納されるが、その管理はワーカーが実施する。

つまり、OzoneではNameNodeが実施していた責務を複数のプロセスに分割することにより、NameNodeが持っていたスケーラビリティの問題を緩和した。

ここでは、Ozoneが具体的にどのようにオブジェクトストレージとして実装されていて、どのようにメタデータや実データを管理しているのかを見ていく。

用語定義

本記事では以下の表記を使う。また、特に断りのない限り、本記事の内容は Ozone v1.4.1に基づく。

  • OM: Ozone Managerのこと
  • SCM: Storage Container Managerのこと
  • DN: DataNodeのこと。HDFSのDataNodeとは別なので注意( なんで用語被らせるんだ・・・ )
  • S3G: S3 Gatewayのこと
  • Client: Ozoneに対して各種リクエストを出すプロセス。文脈によってSparkだったりOzone Shellのことだったりする

Ozoneを支える技術

Ozone内部では以下の技術を多用する。

  • RocksDB: Ozoneのメタデータを格納する際には基本的にRocksDBを使う。
  • Raft: OM-HA, SCM-HA, 実データのレプリケーションにRaftを利用する。実装としてはApache Ratisを利用する。
  • gRPC: Ozone内での通信 (Client <=> S3G, Client <=> DNなど)に利用される。DN <=> DN通信はRatisが利用されるが、Ratisの通信も中身はデフォルト設定ではgRPCである。
  • Hadoop IPC: Hadoopでは独自の通信プロトコルを利用する。OzoneはHadoop上のアプリケーションとの互換性を保つため、ClientとOM間の通信などにおいてHadoop IPCを利用する。

メタデータをどのように管理しているかを確認するためには、RocksDBの中身を見ることが簡単である。本記事ではRocksDBに書かれたメタデータと、それに関係するコードを確認する。また、コンポーネント間の通信も特徴的なので、どのような通信を行っているかを追うことはOzoneの理解の助けになる。通信については別の記事でまとめる。

下準備: RocksDBの見方

RocksDBに関する内容に興味がない場合は見る必要なし

RockDB はKey-Valueストア型の組み込み向けデータベースである。LSMツリー構造と呼ばれるデータ構造に基づいており、高速な書き込みが可能である。

RocksDBの中身を見るためには少し工夫が必要である。最も一般的な方法はRocksDBに付属する sst_dump を利用することだろう。

RocksDBのinstall方法 を参照しビルドすると、sst_dump というバイナリファイルが作成される。そのバイナリを使って、以下のようにすることでデータのダンプが可能である。(ここでは、 /tmp/metadata/om.db 以下にデータがあるものとする。)

$ ./sst_dump --file=/tmp/metadata/om.db/000053.sst --command=raw
options.env is 0x564209068d50
Process /tmp/metadata/om.db/000053.sst
Sst file format: block-based
raw dump written to file /tmp/metadata/om.db/000053_dump.txt
$ cat /tmp/metadata/om.db/000053_dump.txt
$ cat /tmp/metadata/om.db/000053_dump.txt
Footer Details:
--------------------------------------
  metaindex handle: CB084F offset: 1099 size: 79
  index handle: B80110 offset: 184 size: 16
  table_magic_number: 9863518390377041911
  format version: 5

Metaindex Details:
--------------------------------------
  Filter block handle: 6E45
  Properties block handle: CD01F906

Table Properties:
--------------------------------------
  # data blocks: 1
  # entries: 1
  # deletions: 0
  # merge operands: 0
  # range deletions: 0
  raw key size: 12
  raw average key size: 12.000000
  raw value size: 117
  raw average value size: 117.000000
  data block size: 110
  index block size (user-key? 1, delta-value? 1): 21
  filter block size: 69
  # entries for filter: 1
  (estimated) table size: 200
  filter policy name: bloomfilter
  prefix extractor name: nullptr
  column family ID: 13
  column family name: volumeTable
  comparator name: leveldb.BytewiseComparator
  user defined timestamps persisted: true
  largest sequence number in file: 18446744073709551615
  merge operator name: nullptr
  property collectors names: []
  SST file compression algo: Snappy
  SST file compression options: window_bits=-14; level=32767; strategy=0; max_dict_bytes=0; zstd_max_train_bytes=0; enabled=0; max_dict_buffer_bytes=0; use_zstd_dict_trainer=1;
  creation time: 1735435834
  time stamp of earliest key: 1735435834
  time stamp of newest key: 0
  file creation time: 1735435880
  slow compression estimated data size: 0
  fast compression estimated data size: 0
  DB identity: ad2a9fc2-3681-4596-a72b-9d12eb547341
  DB session identity: WFHBYB3ZI2FR1KO3L51Q
  DB host id: 56445fff8010
  original file number: 53
  unique ID: 5F228155FE1F3D83-73E7CB0D6B021DC6
  Sequence number to time mapping:

Index Details:
--------------------------------------
  Block key hex dump: Data block handle
  Block key ascii

  HEX    2F733376: 0069 offset 0 size 105
  ASCII  / s 3 v
  ------

Data Block # 1 @ 0069
--------------------------------------
  HEX    2F733376: 0A066861646F6F7012066861646F6F701A0373337620FFFFFFFFFFFFFFFFFF01320F080112066861646F6F701A01802000320F080212066861646F6F701A0180200038C4ABA580C1324080FEFFFFFFFFFFFFBF0148FFFFFFFFFFFFFFFFFF0150C4ABA580C13258FFFFFFFFFFFFFFFFFF0160006800
  ASCII  / s 3 v :
  h a d o o p   h a d o o p ␦  s 3 v   � � � � � � � � �  2     h a d o o p ␦  �   \0 2     h a d o o p ␦  �   \0 8 � � � � � 2 @ � � � � � � � � �  H � � � � � � � � �  P � � � � � 2 X � � � � � � � � �  ` \0 h \0
  ------

Data Block Summary:
--------------------------------------
  # data blocks: 1
  min data block size: 105
  max data block size: 105
  avg data block size: 105.000000

一点はまりどころとして、Ozone上で何らかの操作を実施したのち、メタデータを確認しても期待するようなSSTファイルがない場合がある。これは、RocksDBはSSTファイルを作成する前にLOGにデータを書き込み、適当なタイミングでcompactionが走るとSSTファイルが作成されるためである。そういった場合は以下のように手動でcompactionを実施することで、SSTファイルが作成されることがある。

$ ./ldb compact --db=/tmp/metadata/om.db

なお、 ldb はRocksDBをビルドすると sst_dump 等と一緒に作成されるコマンドラインツールである。

メタデータ

ここでは、メタデータについて確認していく。

OM メタデータ

ozone-site.xml で設定される ozone.metadata.dirs に指定された場所にファイルが作成される。

OMが管理するメタデータには以下の種類がある。元ネタはこちら

Common Tables:

Column Family VALUE
userTable /user->UserVolumeInfo
volumeTable /volume->VolumeInfo
bucketTable /volume/bucket-> BucketInfo
s3SecretTable s3g_access_key_id -> s3Secret
dTokenTable OzoneTokenID -> renew_time
prefixInfoTable prefix -> PrefixInfo
multipartInfoTable /volumeName/bucketName/keyName/uploadId ->...
transactionInfoTable #TRANSACTIONINFO -> OMTransactionInfo

Multi-Tenant Tables:

tenantStateTable tenantId -> OmDBTenantState
tenantAccessIdTable accessId -> OmDBAccessIdInfo
principalToAccessIdsTable userPrincipal -> OmDBUserPrincipalInfo

Simple Tables:

Column Family VALUE
keyTable /volumeName/bucketName/keyName->KeyInfo
deletedTable /volumeName/bucketName/keyName->RepeatedKeyInfo
openKey /volumeName/bucketName/keyName/id->KeyInfo

Prefix Tables:

Column Family VALUE
directoryTable /volumeId/bucketId/parentId/dirName -> DirInfo
fileTable /volumeId/bucketId/parentId/fileName -> KeyInfo
openFileTable /volumeId/bucketId/parentId/fileName/id -> KeyInfo
deletedDirTable /volumeId/bucketId/parentId/dirName/objectId -> KeyInfo

Snapshot Tables:

Column Family VALUE
snapshotInfoTable /volume/bucket/snapshotName -> SnapshotInfo
snapshotRenamedTable /volumeName/bucketName/objectID -> One of: (*)
compactionLogTable dbTrxId-compactionTime -> compactionLogEntry

(*): objectIDの種類

  1. /volumeId/bucketId/parentId/dirName
  2. /volumeId/bucketId/parentId/fileName
  3. /volumeName/bucketName/keyName

全て見るのは大変なので、今回は volumeTable, bucketTable, fileTable についてdumpデータを確認していく。

$ cat 000053_dump.txt
Footer Details:
--------------------------------------
  metaindex handle: CB084F offset: 1099 size: 79
  index handle: B80110 offset: 184 size: 16
  table_magic_number: 9863518390377041911
  format version: 5

Metaindex Details:
--------------------------------------
  Filter block handle: 6E45
  Properties block handle: CD01F906

Table Properties:
--------------------------------------
  # data blocks: 1
  # entries: 1
  # deletions: 0
  # merge operands: 0
  # range deletions: 0
  raw key size: 12
  raw average key size: 12.000000
  raw value size: 117
  raw average value size: 117.000000
  data block size: 110
  index block size (user-key? 1, delta-value? 1): 21
  filter block size: 69
  # entries for filter: 1
  (estimated) table size: 200
  filter policy name: bloomfilter
  prefix extractor name: nullptr
  column family ID: 13
  column family name: volumeTable
  comparator name: leveldb.BytewiseComparator
  user defined timestamps persisted: true
  largest sequence number in file: 18446744073709551615
  merge operator name: nullptr
  property collectors names: []
  SST file compression algo: Snappy
  SST file compression options: window_bits=-14; level=32767; strategy=0; max_dict_bytes=0; zstd_max_train_bytes=0; enabled=0; max_dict_buffer_bytes=0; use_zstd_dict_trainer=1;
  creation time: 1735435834
  time stamp of earliest key: 1735435834
  time stamp of newest key: 0
  file creation time: 1735435880
  slow compression estimated data size: 0
  fast compression estimated data size: 0
  DB identity: ad2a9fc2-3681-4596-a72b-9d12eb547341
  DB session identity: WFHBYB3ZI2FR1KO3L51Q
  DB host id: 56445fff8010
  original file number: 53
  unique ID: 5F228155FE1F3D83-73E7CB0D6B021DC6
  Sequence number to time mapping:

Index Details:
--------------------------------------
  Block key hex dump: Data block handle
  Block key ascii

  HEX    2F733376: 0069 offset 0 size 105
  ASCII  / s 3 v
  ------

Data Block # 1 @ 0069
--------------------------------------
  HEX    2F733376: 0A066861646F6F7012066861646F6F701A0373337620FFFFFFFFFFFFFFFFFF01320F080112066861646F6F701A01802000320F080212066861646F6F701A0180200038C4ABA580C1324080FEFFFFFFFFFFFFBF0148FFFFFFFFFFFFFFFFFF0150C4ABA580C13258FFFFFFFFFFFFFFFFFF0160006800
  ASCII  / s 3 v :
  h a d o o p   h a d o o p ␦  s 3 v   � � � � � � � � �  2     h a d o o p ␦  �   \0 2     h a d o o p ␦  �   \0 8 � � � � � 2 @ � � � � � � � � �  H � � � � � � � � �  P � � � � � 2 X � � � � � � � � �  ` \0 h \0
  ------

Data Block Summary:
--------------------------------------
  # data blocks: 1
  min data block size: 105
  max data block size: 105
  avg data block size: 105.000000

まずポイントとなるのが Table Propertiescolumn family name である。この部分に前述したColumnデータが入ってくる。今回の場合だと volumeTable である。この部分はSSTファイルごとに異なってくる。

次に Data Block 部分を見ていく。

Data Block # 1 @ 0069
--------------------------------------
  HEX    2F733376: 0A066861646F6F7012066861646F6F701A0373337620FFFFFFFFFFFFFFFFFF01320F080112066861646F6F701A01802000320F080212066861646F6F701A0180200038C4ABA580C1324080FEFFFFFFFFFFFFBF0148FFFFFFFFFFFFFFFFFF0150C4ABA580C13258FFFFFFFFFFFFFFFFFF0160006800
  ASCII  / s 3 v :
  h a d o o p   h a d o o p ␦  s 3 v   � � � � � � � � �  2     h a d o o p ␦  �   \0 2     h a d o o p ␦  �   \0 8 � � � � � 2 @ � � � � � � � � �  H � � � � � � � � �  P � � � � � 2 X � � � � � � � � �  ` \0 h \0

特にOzoneにデータを格納していない場合でも、 /s3v というボリュームが見える。 /s3v は事前に予約されたボリュームであり、ClientからS3G経由でデータを書き込むと /s3v 配下にバケットやキー (S3でいうオブジェクト)が書き込まれる。ASCIIの : より後が文字化けしているが、心の目で見るとこれがProtocol Buffersで書きこまれたバイナリファイルであるとわかるはずだ。JSONでデコードすると以下である。

{
  "metadata": [],
  "volumeAcls": [
    {
      "type": "USER",
      "name": "hadoop",
      "rights": [
        128
      ],
      "aclScope": "ACCESS"
    },
    {
      "type": "GROUP",
      "name": "hadoop",
      "rights": [
        128
      ],
      "aclScope": "ACCESS"
    }
  ],
  "adminName": "hadoop",
  "ownerName": "hadoop",
  "volume": "s3v",
  "quotaInBytes": 18446744073709552000,
  "creationTime": 1735435834820,
  "objectID": 13835058055282164000,
  "updateID": 18446744073709552000,
  "modificationTime": 1735435834820,
  "quotaInNamespace": -1,
  "usedNamespace": 0,
  "refCount": 0
}

この辺りの情報はOzoneのProtoファイル[1]に定義されている。

/s3v はあらかじめ用意されたボリュームだが、Ozoneでは新しくボリュームを作成することももちろん可能である。例えば以下のようなコマンドを実行する。

$ ozone sh --verbose volume create /myvolume1
{
  "metadata" : { },
  "name" : "myvolume1",
  "admin" : "hadoop",
  "owner" : "hadoop",
  "quotaInBytes" : -1,
  "quotaInNamespace" : -1,
  "usedNamespace" : 0,
  "creationTime" : "2024-12-29T10:36:35.818Z",
  "modificationTime" : "2024-12-29T10:36:35.818Z",
  "acls" : [ {
    "type" : "USER",
    "name" : "hadoop",
    "aclScope" : "ACCESS",
    "aclList" : [ "ALL" ]
  }, {
    "type" : "GROUP",
    "name" : "hadoop",
    "aclScope" : "ACCESS",
    "aclList" : [ "READ", "LIST" ]
  } ],
  "refCount" : 0
}

次にbucketTableである。なおこの前に、以下の操作で Volume, Bucket, Keyを作成している。

$ ozone sh volume create /myvolume1
$ ozone sh bucket create /myvolume1/mybucket1
$ ozone sh key put o3://om/myvolume1/mybucket1/myhosts /etc/hosts

以下がSSTファイルをダンプした結果である。

Footer Details:
--------------------------------------
  metaindex handle: AD0950 offset: 1197 size: 80
  index handle: D50121 offset: 213 size: 33
  table_magic_number: 9863518390377041911
  format version: 5

Metaindex Details:
--------------------------------------
  Filter block handle: 8B0145
  Properties block handle: FB01AD07

Table Properties:
--------------------------------------
  # data blocks: 1
  # entries: 1
  # deletions: 0
  # merge operands: 0
  # range deletions: 0
  raw key size: 28
  raw average key size: 28.000000
  raw value size: 128
  raw average value size: 128.000000
  data block size: 139
  index block size (user-key? 1, delta-value? 1): 38
  filter block size: 69
  # entries for filter: 1
  (estimated) table size: 246
  filter policy name: bloomfilter
  prefix extractor name: nullptr
  column family ID: 22
  column family name: bucketTable
  comparator name: leveldb.BytewiseComparator
  user defined timestamps persisted: true
  largest sequence number in file: 13
  merge operator name: StringAppendOperator
  property collectors names: []
  SST file compression algo: Snappy
  SST file compression options: window_bits=-14; level=32767; strategy=0; max_dict_bytes=0; zstd_max_train_bytes=0; enabled=0; max_dict_buffer_bytes=0; use_zstd_dict_trainer=1;
  creation time: 1735477537
  time stamp of earliest key: 0
  time stamp of newest key: 1735477537
  file creation time: 0
  slow compression estimated data size: 0
  fast compression estimated data size: 0
  DB identity: 76cd09ec-ab02-482d-925c-8112f3c3578b
  DB session identity: VO1T2SM8DQD1XG622VJ2
  DB host id: XXXXXXX
  original file number: 62
  unique ID: D0DF7C606CCFBAEE-B04275A4183D8248
  Sequence number to time mapping:

Index Details:
--------------------------------------
  Block key hex dump: Data block handle
  Block key ascii

  HEX    2F6D79766F6C756D65312F6D796275636B657431: 008601 offset 0 size 134
  ASCII  / m y v o l u m e 1 / m y b u c k e t 1
  ------

Data Block # 1 @ 008601
--------------------------------------
  HEX    2F6D79766F6C756D65312F6D796275636B657431: 0A096D79766F6C756D653112096D796275636B6574311A0F080112066861646F6F701A018020001A0F080212066861646F6F701A010920002000280130B2CBB093C1324880868080808080808001500358B2CBB093C13270AE0178FFFFFFFFFFFFFFFFFF018001FFFFFFFFFFFFFFFFFF018801019001029A01066861646F6F70
  ASCII  / m y v o l u m e 1 / m y b u c k e t 1 :
         m y v o l u m e 1       m y b u c k e t 1 ␦     h a d o o p ␦  �   \0 ␦     h a d o o p ␦             \0   \0 (  0 � � � � � 2 H � � � � � � � � �  P  X � � � � � 2 p �  x � � � � � � � � �  �  � � � � � � � � �  �   �   �   h a d o o p
  ------

Data Block Summary:
--------------------------------------
  # data blocks: 1
  min data block size: 134
  max data block size: 134
  avg data block size: 134.000000

まあ大体書いてあることはわかるだろう。JSONにデコードしたものが以下である。

{
  "acls": [
    {
      "type": "USER",
      "name": "hadoop",
      "rights": [
        128
      ],
      "aclScope": "ACCESS"
    },
    {
      "type": "GROUP",
      "name": "hadoop",
      "rights": [
        9
      ],
      "aclScope": "ACCESS"
    }
  ],
  "metadata": [],
  "volumeName": "myvolume1",
  "bucketName": "mybucket1",
  "isVersionEnabled": false,
  "storageType": "DISK",
  "creationTime": 1735475865010,
  "beinfo": null,
  "objectID": 9223372036854776000,
  "updateID": 3,
  "modificationTime": 1735475865010,
  "sourceVolume": "",
  "sourceBucket": "",
  "usedBytes": 174,
  "quotaInBytes": -1,
  "quotaInNamespace": -1,
  "usedNamespace": 1,
  "bucketLayout": "FILE_SYSTEM_OPTIMIZED",
  "owner": "hadoop",
  "defaultReplicationConfig": null
}

次に fileTableである。

Footer Details:
--------------------------------------
  metaindex handle: 820A50 offset: 1282 size: 80
  index handle: 970236 offset: 279 size: 54
  table_magic_number: 9863518390377041911
  format version: 5
(snip)
  column family name: fileTable
(snip)
Data Block # 1 @ 00C801
--------------------------------------
  HEX    2F2D393232333337323033363835343737353535322F2D393232333337323033363835343737353034302F2D393232333337323033363835343737353034302F6D79686F737473: 0A096D79766F6C756D653112096D796275636B6574311A076D79686F73747320AE01280130013A210800121B0A100A0C080110818080E097E587CA011002180020AE01280048002000408D9BBF93C13248AEA4BF93C13250006A0F080112066861646F6F701A018020006A0F080212066861646F6F701A0109200070818A80808080808080017807800180868080808080808001980101A201066861646F6F70
  ASCII  / - 9 2 2 3 3 7 2 0 3 6 8 5 4 7 7 5 5 5 2 / - 9 2 2 3 3 7 2 0 3 6 8 5 4 7 7 5 0 4 0 / - 9 2 2 3 3 7 2 0 3 6 8 5 4 7 7 5 0 4 0 / m y h o s t s :
         m y v o l u m e 1       m y b u c k e t 1 ␦  m y h o s t s   �  (  0  : ! \0





 � � � � � � �     \0   �  ( \0 H \0   \0 @ � � � � � 2 H � � � � � 2 P \0 j     h a d o o p ␦  �   \0 j     h a d o o p ␦             \0 p � � � � � � � � �  x  �  � � � � � � � � �  �   �   h a d o o p
  ------
(snip)

上記を見ると、ASCIIで表示されている部分について、volume名やbucket名が書かれていないことがわかる。これは、Ozoneにおいて FILE_SYSTEM_OPTIMIZED (FSO) と呼ばれる技術が使われているためである。JSONで見るとわかりやすい。

{
  "keyLocationList": [
    {
      "keyLocations": [
        {
          "blockID": {
            "containerBlockID": {
              "containerID": 1,
              "localID": 113750153625600000
            },
            "blockCommitSequenceId": 2
          },
          "offset": 0,
          "length": 174,
          "createVersion": 0,
          "token": null,
          "pipeline": null,
          "partNumber": 0
        }
      ],
      "version": 0,
      "fileEncryptionInfo": null,
      "isMultipartKey": false
    }
  ],
  "metadata": [],
  "acls": [
    {
      "type": "USER",
      "name": "hadoop",
      "rights": [
        128
      ],
      "aclScope": "ACCESS"
    },
    {
      "type": "GROUP",
      "name": "hadoop",
      "rights": [
        9
      ],
      "aclScope": "ACCESS"
    }
  ],
  "tags": [],
  "volumeName": "myvolume1",
  "bucketName": "mybucket1",
  "keyName": "myhosts",
  "dataSize": 174,
  "type": "RATIS",
  "factor": "ONE",
  "creationTime": 1735476104589,
  "modificationTime": 1735476105774,
  "latestVersion": 0,
  "fileEncryptionInfo": null,
  "objectID": 9223372036854778000,
  "updateID": 7,
  "parentID": 9223372036854776000,
  "ecReplicationConfig": null,
  "fileChecksum": null,
  "isFile": true,
  "ownerName": "hadoop",
  "expectedDataGeneration": 0
}

先ほどのbucketTableにおいて、 /myvolume1/mybucket1 の objectIDが 9223372036854776000 であったことを思い出してほしい。つまり、Ozoneのファイル(key)メタ情報としては、その親となるbucket情報などは文字列で持たず別途数値のIDで管理している。このことにより、Ozoneはオブジェクトストレージではあるものの、あるbucket、もしくはディレクトリ(相当)のrenameが O(1) でできるようにしている。なぜなら、rename操作をする際はobjectIDを変えずにそのbucket名を変えることができるので、あるbucket配下のkeyのメタ情報をすべて変更することが不要なためである。この挙動は、Hadoop上で動作するHiveなどのアプリケーションが期待するものであるため、Hadoopアプリケーションとの互換性の担保に寄与している。詳しくはOzoneのドキュメントを見るべし。

OMが管理するメタデータは他にもたくさんあるが、重要なことはこれらのメタデータをOMはRocksDBで管理していることである。常にメモリに保持する必要がないため、スケーラビリティに寄与する。

SCM メタデータ

SCMのメタデータを見ていく。OMのときと同様に、以下のコマンドを実行済みの状態であるとする。

$ ozone sh volume create /myvolume1
$ ozone sh bucket create /myvolume1/mybucket1
$ ozone sh key put o3://om/myvolume1/mybucket1/myhosts /etc/hosts
Footer Details:
--------------------------------------
(snip)

Table Properties:
--------------------------------------
(snip)
  column family ID: 1
  column family name: sequenceId
(snip)

Index Details:
--------------------------------------
  Block key hex dump: Data block handle
  Block key ascii

  HEX    6C6F63616C4964: 0060 offset 0 size 96
  ASCII  l o c a l I d
  ------

Data Block # 1 @ 0060
--------------------------------------
  HEX    43657274696669636174654964: 0000000000000002
  ASCII  C e r t i f i c a t e I d : \0 \0 \0 \0 \0 \0 \0
  ------
  HEX    636F6E7461696E65724964: 0000000000000000
  ASCII  c o n t a i n e r I d : \0 \0 \0 \0 \0 \0 \0 \0
  ------
  HEX    64656C54786E4964: 0000000000000000
  ASCII  d e l T x n I d : \0 \0 \0 \0 \0 \0 \0 \0
  ------
  HEX    6C6F63616C4964: 01941F297C000000
  ASCII  l o c a l I d :  �  ) | \0 \0 \0
  ------
(snip)

ここでも注目するべきは column family name だろう。SCMは起動時に SCMMetadataStore を起動する。実装としては SCMMetadataStoreImpl[2] が使われる。sequenceID は Containerを作成する際の BlockID を生成するために使われる。Monotonicallyに増加するようになっており、SCMが再起動してもBlockIDが一意になることを保証する。そのようなメタデータを永続化するためにRocksDBが使われていることがわかる。

なお、ContainerID + LocalIDでBlockIDとなり、これが一つのkeyを示すこととなる。この採番は新しいBlockが払い出されたときに実施される[3]

DN メタデータ

Ozone v1.4.1の場合、DataNodeのSchemaはV3となっている。SchemaV2ではContainer一つごとにそのメタデータを管理しているRocksDBができていたが、Schema V3ではディスクごとになっている[4]

ここではわかりやすいように大量にデータを書き込んだうえでメタデータを確認する。以下はblock_dataである。

Footer Details:
--------------------------------------
  metaindex handle: BFCD0253 offset: 42687 size: 83
  index handle: B7C202BA03 offset: 41271 size: 442
  table_magic_number: 9863518390377041911
  format version: 5

Metaindex Details:
--------------------------------------
  Filter block handle: ADB802850A
  Properties block handle: F6C502C407

Table Properties:
--------------------------------------
(snip)
  prefix extractor name: rocksdb.FixedPrefix.9
  column family ID: 3
  column family name: block_data
(snip)
  HEX    00000000000000017C313133373530313533363235363030303334: 00BF0A offset 0 size 1343
  ASCII          | 1 1 3 7 5 0 1 5 3 6 2 5 6 0 0 0 3 4
  ------
  HEX    00000000000000017C313133373530313533363235363030303638: C40AE40A offset 1348 size 1380
  ASCII          | 1 1 3 7 5 0 1 5 3 6 2 5 6 0 0 0 6 8
  ------
(snip)

Data Block # 1 @ 00BF0A                                                                     --------------------------------------                                                        HEX    00000000000000017C313133373530313533363235363030303031: 0A0E080110818080E097E587CA0118021A0B0A045459504512034B455922AB030A1A3131333735303135333632353630303030315F6368756E6B5F311000188080402A86030802108080011A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D2861A04AB54D28628808040
(snip)

Data Block部分をデコードすると以下である。

{
  "metadata": [
    {
      "key": "TYPE",
      "value": "KEY"
    }
  ],
  "chunks": [
    {
      "metadata": [],
      "chunkName": "113750153625600001_chunk_1",
      "offset": 0,
      "len": 1048576,
      "checksumData": {
        "checksums": [
          [
            171,
            84,
            210,
            134
          ],
(snip)
        "type": "CRC32",
        "bytesPerChecksum": 16384
      },
      "stripeChecksum": []
    }
  ],
  "blockID": {
    "containerID": 1,
    "localID": 113750153625600000,
    "blockCommitSequenceId": 2,
    "replicaIndex": 0
  },
  "flags": 0,
  "size": 1048576
}

前提として、OzoneではあるContaienrの中に複数のブロックが存在し、ブロックの中も複数のchunkでデータが分かれている。chunkのデータはDataNodeのファイルシステムから確認可能。どのchunkno
データがどのファイルに入っているのかを確認するためにRocksDBの中を使っている。上記のデータからもそれが確認できる。

以下はmetadataである。

Footer Details:
--------------------------------------
  metaindex handle: F8084F offset: 1144 size: 79
  index handle: 99011F offset: 153 size: 31
  table_magic_number: 9863518390377041911
  format version: 5

Metaindex Details:
--------------------------------------
  Filter block handle: 4F45
  Properties block handle: BD01B607

Table Properties:
--------------------------------------
(snip)
  column family ID: 4
  column family name: metadata
(snip)

Index Details:
--------------------------------------
  Block key hex dump: Data block handle
  Block key ascii

  HEX    00000000000000017C23425954455355534544: 004A offset 0 size 74
  ASCII          | # B Y T E S U S E D
  ------

Data Block # 1 @ 004A
--------------------------------------
  HEX    00000000000000017C234243534944: 0000000000000BB9
  ASCII  \0 \0 \0 \0 \0 \0 \0  | # B C S I D : \0 \0 \0 \0 \0 \0
                                                                  �
  ------
  HEX    00000000000000017C23424C4F434B434F554E54: 00000000000003E8
  ASCII  \0 \0 \0 \0 \0 \0 \0  | # B L O C K C O U N T : \0 \0 \0 \0 \0 \0  �
  ------
  HEX    00000000000000017C23425954455355534544: 000000003E800000
  ASCII  \0 \0 \0 \0 \0 \0 \0  | # B Y T E S U S E D : \0 \0 \0 \0 > � \0 \0
  ------

Data Block Summary:
--------------------------------------
  # data blocks: 1
  min data block size: 74
  max data block size: 74
  avg data block size: 74.000000

これはcontainerごとにどれくらいbytesを消費しているかなどのメタデータを表示する。

終わりに

Ozoneのメタデータがどう管理されているかを垣間見るための一助になれば。次は通信についてまとめる。

TIPS

Ozoneが提供しているDocker Composeファイルを以下のように追記するとRemote Debugできる。

services:
  datanode:
    <<: *common-config
    ports:
      - 19864
      - 9882
      - 5005:5005
    environment:
      <<: *replication
      OZONE_OPTS: "-agentlib:jdwp=transport=dt_socket,address=*:5005,server=y,suspend=n"
    command: ["ozone","datanode"]
脚注
  1. https://github.com/apache/ozone/blob/345c46850f2f145d4f735214d32390e422544894/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto#L607 ↩︎

  2. https://github.com/apache/ozone/blob/a9d5d1c630d903b28984d1c46daa78a1812674ac/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/metadata/SCMMetadataStoreImpl.java ↩︎

  3. https://github.com/apache/ozone/blob/a9d5d1c630d903b28984d1c46daa78a1812674ac/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/BlockManagerImpl.java#L186 ↩︎

  4. https://issues.apache.org/jira/browse/HDDS-3630 ↩︎

Discussion