はじめに
すでにHDFS RawをAmazon S3へバックアップし、IcebergもAmazon S3 + Glueへ同期している場合、本稿のOzone移行は実施しない。本seriesの後続記事では、その既存AWS backupを使用する。本稿はAWSへ接続せずS3互換性をローカル検証したい場合の任意手順として残す。
前回は、Kafka Connect HDFS Sinkが保存したrawログをSpark SQLで直接解析し、Hive curatedを経由せずApache Icebergへ投入するように変更した。
今回はこの続きとして、保存先をHDFSから自宅環境のS3互換Object Storageへ段階的に移行する。ローカルS3にはApache OzoneのS3 Gatewayを使用する。
対象は次の2系統である。
- syslog
- authlog
本記事では、既存HDFSをすぐに削除しない。HDFSとOzoneを並行運用し、件数と内容を比較した後に参照先を切り替える。
本記事のOzone単一ノード構成は移行手順を確認するための検証用である。HDFSの代替として可用性まで求める場合は、OM/SCMのHA、複数DataNode、複数S3 Gateway、TLS、Kerberos、監視、バックアップを別途設計する。
今回の方針
rawとIcebergで移行方法を分ける
rawログはJSON Textであり、HDFS上の通常ファイルとして保存されている。このため、Hadoop DistCpでhdfs://からs3a://へコピーできる。
一方、Iceberg warehouseをファイル単位でそのままコピーしてはいけない。Icebergのmetadataにはdata fileやmanifest fileの場所が記録されており、HDFS上のmetadataをOzoneへ置くだけでは参照先がHDFSのまま残る可能性がある。
今回は次の方法を使う。
| 対象 | 移行方法 |
|---|---|
| raw JSON Text | Hadoop DistCpでhdfs://からs3a://へコピー |
| Iceberg | Ozone側に新規テーブルを作成し、Spark SQLのINSERT INTO ... SELECT ...で論理コピー |
既存環境を正本として残す
切り替え前は次の状態にする。
Ozone側を作成しても、検証が終わるまではHDFS側の削除や既存テーブルのDROPを行わない。
前提環境
以下は構築済みとする。
| 項目 | 値 |
|---|---|
| OS | Ubuntu 24.04 |
| Hadoop | 3.3.6 |
| HDFS nameservice | cluster1 |
| Spark | 3.5.8 |
| Apache Iceberg | 1.10.1 |
| Hive Metastore | 構築済み |
| 既存catalog | hive_prod |
| 既存warehouse | hdfs://cluster1/warehouse/iceberg |
| 操作ホスト | ope1 |
| Ozoneホスト | ozone1 |
| Ozone | 2.2.1 |
| Ozone S3 Gateway | http://ozone1:9878 |
既存テーブルは次の通りである。
hive_prod.logs.syslog_iceberg
hive_prod.logs.authlog_iceberg
rawログは次の場所に保存されている。
hdfs://cluster1/data/kafka/syslog/YYYY/MM/DD/HH
hdfs://cluster1/data/kafka/authlog/YYYY/MM/DD/HH
Ozoneには次のS3 bucketを作る。
| bucket | 用途 |
|---|---|
log-raw |
syslog/authlogのraw JSON Text |
lakehouse |
Iceberg warehouse |
Apache Ozoneを構築する
Apache Ozoneは、Ozone Manager(OM)、Storage Container Manager(SCM)、DataNode、S3 Gateway(S3G)で構成される。S3GはstatelessなS3互換REST frontendであり、objectとmetadataはOzone cluster側へ保存される。
検証用clusterを用意する
本記事では、まず公式のDocker Compose構成で移行方法を確認する。production構成は公式Administrator Guideに従ってbare metalまたはKubernetesへ構築する。
ozone1で実行する。
wget https://downloads.apache.org/ozone/2.2.1/ozone-2.2.1.tar.gz
tar xzf ozone-2.2.1.tar.gz
cd ozone-2.2.1/compose/ozone
docker compose up -d --scale datanode=3
docker compose ps
S3 Gatewayは標準では9878で待ち受ける。操作ホストから疎通を確認する。
curl -I http://ozone1:9878
Docker Compose版は開発・評価用である。HDFSから完全移行する前に、少なくともOM/SCM/DataNodeの永続volume、replication、service再起動、host再起動、disk故障時の動作を確認する。
S3 credentialを用意する
Ozone securityを無効にした検証用clusterでは、任意のaccess keyとsecret keyを使用できる。次の値は例であり、実際には推測されにくい値を使用する。
export AWS_ACCESS_KEY_ID='<OZONE_S3_ACCESS_KEY>'
export AWS_SECRET_ACCESS_KEY='<OZONE_S3_SECRET_KEY>'
export AWS_DEFAULT_REGION='us-east-1'
Kerberosを有効にしたproduction clusterでは、対象ユーザーでkinitした後に次を実行し、Ozone ManagerからS3 credentialを取得する。
ozone s3 getsecret
Ozone 1.4.0以降、生成したsecretが表示されるのは初回だけである。紛失時はozone s3 revokesecretで失効させてから再生成する。credentialは記事、Git、shell historyへ残さない。
bucketを作成する
操作ホストope1にAWS CLIを用意し、Ozone S3 Gatewayをendpointに指定する。
aws --endpoint-url http://ozone1:9878 s3api create-bucket --bucket log-raw
aws --endpoint-url http://ozone1:9878 s3api create-bucket --bucket lakehouse
aws --endpoint-url http://ozone1:9878 s3api list-buckets
S3 Gatewayで作成したbucketは、Ozoneの/s3v volume配下へ保存される。Ozone Shellでも確認できる。
ozone sh bucket list /s3v
productionではApache RangerまたはOzone ACLを使い、Spark用principalへlog-rawとlakehouseに必要な権限だけを付与する。検証用の非secure clusterをそのまま外部公開しない。
Hadoop S3Aを設定する
対応するJARを確認する
HadoopからS3互換Object Storageへ接続するにはS3A connectorが必要である。Hadoop 3.3.6では、Hadoop本体と互換性のあるhadoop-awsおよびAWS SDK bundleを使用する。
hadoop version
hadoop classpath | tr ':' '\n' | grep -E 'hadoop-aws|aws-java-sdk'
何も表示されない場合は、利用中のHadoop distributionと同じversionのhadoop-aws、およびそのPOMが要求するAWS SDKを追加する。Hadoop本体と異なるversionのhadoop-awsを混在させない。
S3A classが読み込めるclasspathを、DistCpとSparkの両方へ反映する。
credential fileを作成する
検証用として、Spark実行ユーザーだけが読めるHadoop credential providerへaccess keyとsecret keyを保存する。
sudo install -d -o spark -g spark -m 700 /var/lib/spark/.hadoop
sudo -u spark hadoop credential create fs.s3a.access.key \
-provider jceks://file/var/lib/spark/.hadoop/ozone-s3.jceks
sudo -u spark hadoop credential create fs.s3a.secret.key \
-provider jceks://file/var/lib/spark/.hadoop/ozone-s3.jceks
sudo chmod 600 /var/lib/spark/.hadoop/ozone-s3.jceks
promptにはOzone S3用のaccess keyとsecret keyを入力する。平文のcredentialをcore-site.xmlや起動shellへ直接記載しない。
cluster modeでexecutorを別hostに起動する場合は、同じJCEKSを全実行nodeの同一pathへ安全に配布する。DistCpを別ユーザーで実行する場合も、そのユーザー専用のcredential providerを作成する。
core-site.xmlを設定する
Hadoop clientとSpark executorから参照されるcore-site.xmlへ追加する。
<property>
<name>fs.s3a.endpoint</name>
<value>http://ozone1:9878</value>
</property>
<property>
<name>fs.s3a.endpoint.region</name>
<value>us-east-1</value>
</property>
<property>
<name>fs.s3a.path.style.access</name>
<value>true</value>
</property>
<property>
<name>fs.s3a.bucket.probe</name>
<value>0</value>
</property>
<property>
<name>fs.s3a.change.detection.mode</name>
<value>none</value>
</property>
<property>
<name>fs.s3a.connection.ssl.enabled</name>
<value>false</value>
</property>
<property>
<name>fs.s3a.aws.credentials.provider</name>
<value>org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider</value>
</property>
<property>
<name>hadoop.security.credential.provider.path</name>
<value>jceks://file/var/lib/spark/.hadoop/ozone-s3.jceks</value>
</property>
Ozone S3 Gatewayは標準でpath-style URLを使用する。今回はhttp://ozone1:9878/log-raw/...形式で接続するためtrueにする。Ozoneの公式S3A手順に合わせ、bucket probeとchange detectionも無効にする。
接続を確認する。
sudo -u spark hadoop fs -mkdir -p s3a://log-raw/connection-test
sudo -u spark hadoop fs -touchz s3a://log-raw/connection-test/ok
sudo -u spark hadoop fs -ls s3a://log-raw/connection-test
sudo -u spark hadoop fs -rm -r s3a://log-raw/connection-test
rawログをHDFSからOzoneへコピーする
最初から全期間をコピーせず、1日分で動作を確認する。
sudo -u spark hadoop distcp \
-update \
-strategy dynamic \
hdfs://cluster1/data/kafka/syslog/2026/09/15 \
s3a://log-raw/syslog/2026/09/15
sudo -u spark hadoop distcp \
-update \
-strategy dynamic \
hdfs://cluster1/data/kafka/authlog/2026/09/15 \
s3a://log-raw/authlog/2026/09/15
sourceとtargetのファイル数、byte数を比較する。
sudo -u spark hadoop fs -count -h hdfs://cluster1/data/kafka/syslog/2026/09/15
sudo -u spark hadoop fs -count -h s3a://log-raw/syslog/2026/09/15
sudo -u spark hadoop fs -count -h hdfs://cluster1/data/kafka/authlog/2026/09/15
sudo -u spark hadoop fs -count -h s3a://log-raw/authlog/2026/09/15
S3はHDFSと異なりdirectoryそのものを持たない。hadoop fs -countに表示されるdirectory数は一致判定の中心にせず、ファイル数、byte数、Sparkで読んだrecord数を確認する。
全期間を移行するときは、まず過去分をコピーし、切り替え直前に同じ-updateを再実行して差分を追従する。
SparkからOzone rawを読む
前回作成したload_raw_to_iceberg.shの入力pathを変更する前に、SparkがOzoneのJSON Textを読めることを確認する。
sudo -u spark /usr/local/bin/spark-sql-iceberg <<'EOF'
SET spark.sql.session.timeZone=Asia/Tokyo;
CREATE OR REPLACE TEMPORARY VIEW syslog_s3_test
USING text
OPTIONS (
path 's3a://log-raw/syslog/2026/09/15'
);
SELECT COUNT(*) AS cnt
FROM syslog_s3_test
WHERE value IS NOT NULL
AND length(trim(value)) > 0;
EOF
HDFS側も同じ条件で数える。
sudo -u spark /usr/local/bin/spark-sql-iceberg <<'EOF'
CREATE OR REPLACE TEMPORARY VIEW syslog_hdfs_test
USING text
OPTIONS (
path 'hdfs://cluster1/data/kafka/syslog/2026/09/15'
);
SELECT COUNT(*) AS cnt
FROM syslog_hdfs_test
WHERE value IS NOT NULL
AND length(trim(value)) > 0;
EOF
authlogも同様に確認する。ここで一致しない場合は、Iceberg移行へ進まず、DistCpの失敗、書き込み途中のHDFS file、権限、S3Aのmultipart uploadを確認する。
Ozone側のIceberg catalogを追加する
既存のhive_prodを残し、移行確認用にozone_s3 catalogを追加する。
Spark起動optionへ次を設定する。
--conf spark.sql.catalog.ozone_s3=org.apache.iceberg.spark.SparkCatalog
--conf spark.sql.catalog.ozone_s3.type=hive
--conf spark.sql.catalog.ozone_s3.uri=thrift://hive1:9083
--conf spark.sql.catalog.ozone_s3.warehouse=s3a://lakehouse/warehouse
hive_prodとozone_s3が同じHive Metastoreを利用するため、同じnamespaceとtable名は使わない。移行確認用namespaceはlogs_s3とする。
移行元: hive_prod.logs.syslog_iceberg
移行先: ozone_s3.logs_s3.syslog_iceberg
移行元: hive_prod.logs.authlog_iceberg
移行先: ozone_s3.logs_s3.authlog_iceberg
catalog設定後、Spark SQLを起動して確認する。
SHOW CATALOGS;
CREATE NAMESPACE IF NOT EXISTS ozone_s3.logs_s3;
ozone_s3というcatalog名だけではHive Metastore内のtable identityは分離されない。同じMetastoreを使う場合は、移行元と異なるnamespaceまたはtable名が必要である。
Ozone側にIcebergテーブルを作成する
最初に移行元のschemaとpartition specを確認する。
sudo -u spark /usr/local/bin/spark-sql-iceberg <<'EOF'
DESCRIBE TABLE EXTENDED hive_prod.logs.syslog_iceberg;
DESCRIBE TABLE EXTENDED hive_prod.logs.authlog_iceberg;
EOF
以下は既存記事と同じschema、days(ts)とhostによるpartitionを使用する例である。実環境の結果が異なる場合は、移行元に合わせる。
sudo -u spark /usr/local/bin/spark-sql-iceberg <<'EOF'
CREATE NAMESPACE IF NOT EXISTS ozone_s3.logs_s3;
CREATE TABLE IF NOT EXISTS ozone_s3.logs_s3.syslog_iceberg (
host string,
ts timestamp_ntz,
severity int,
program string,
msg string,
dt date,
hr int
)
USING iceberg
PARTITIONED BY (days(ts), host)
LOCATION 's3a://lakehouse/warehouse/logs_s3/syslog_iceberg';
CREATE TABLE IF NOT EXISTS ozone_s3.logs_s3.authlog_iceberg (
host string,
ts timestamp_ntz,
severity int,
program string,
msg string,
dt date,
hr int
)
USING iceberg
PARTITIONED BY (days(ts), host)
LOCATION 's3a://lakehouse/warehouse/logs_s3/authlog_iceberg';
EOF
作成場所を確認する。
sudo -u spark hadoop fs -ls s3a://lakehouse/warehouse/logs_s3/syslog_iceberg
sudo -u spark hadoop fs -ls s3a://lakehouse/warehouse/logs_s3/authlog_iceberg
Icebergデータを論理コピーする
まず1日分をコピーする。再実行できるよう、targetの対象日を削除してから投入する。
sudo -u spark /usr/local/bin/spark-sql-iceberg <<'EOF'
SET spark.sql.session.timeZone=Asia/Tokyo;
DELETE FROM ozone_s3.logs_s3.syslog_iceberg
WHERE dt = DATE '2026-09-15';
INSERT INTO ozone_s3.logs_s3.syslog_iceberg
SELECT host, ts, severity, program, msg, dt, hr
FROM hive_prod.logs.syslog_iceberg
WHERE dt = DATE '2026-09-15';
DELETE FROM ozone_s3.logs_s3.authlog_iceberg
WHERE dt = DATE '2026-09-15';
INSERT INTO ozone_s3.logs_s3.authlog_iceberg
SELECT host, ts, severity, program, msg, dt, hr
FROM hive_prod.logs.authlog_iceberg
WHERE dt = DATE '2026-09-15';
EOF
この処理ではSparkがHDFS側Icebergを読み、Ozone側に新しいdata file、manifest、metadataを生成する。warehouse directoryの物理コピーは行わない。
全期間を一度に処理すると失敗時の再実行範囲が大きくなるため、dt単位で古い日から順に移行する。
HDFS側とOzone側を比較する
日別件数と時刻範囲
sudo -u spark /usr/local/bin/spark-sql-iceberg <<'EOF'
SET spark.sql.session.timeZone=Asia/Tokyo;
SELECT 'hdfs' AS src, dt, COUNT(*) AS cnt,
MIN(ts) AS min_ts, MAX(ts) AS max_ts
FROM hive_prod.logs.syslog_iceberg
WHERE dt = DATE '2026-09-15'
GROUP BY dt
UNION ALL
SELECT 'ozone' AS src, dt, COUNT(*) AS cnt,
MIN(ts) AS min_ts, MAX(ts) AS max_ts
FROM ozone_s3.logs_s3.syslog_iceberg
WHERE dt = DATE '2026-09-15'
GROUP BY dt;
EOF
authlogも同じSQLで比較する。
集計差分
全行の順序には意味がないため、host、program、severity、dt、hr単位の件数を比較する。
WITH hdfs_data AS (
SELECT host, program, severity, dt, hr, COUNT(*) AS cnt
FROM hive_prod.logs.syslog_iceberg
WHERE dt = DATE '2026-09-15'
GROUP BY host, program, severity, dt, hr
), s3_data AS (
SELECT host, program, severity, dt, hr, COUNT(*) AS cnt
FROM ozone_s3.logs_s3.syslog_iceberg
WHERE dt = DATE '2026-09-15'
GROUP BY host, program, severity, dt, hr
), hdfs_only AS (
SELECT * FROM hdfs_data
EXCEPT
SELECT * FROM s3_data
), s3_only AS (
SELECT * FROM s3_data
EXCEPT
SELECT * FROM hdfs_data
)
SELECT 'hdfs_only' AS diff, * FROM hdfs_only
UNION ALL
SELECT 's3_only' AS diff, * FROM s3_only;
結果が0行であれば、この集計粒度では差分がない。複数日を比較し、特に00時台と23時台のdt、hr、tsを確認する。
日次ロードをOzoneへ切り替える
前回作成した直接ロードでは、入力pathと書き込み先を次のように変更する。
- path 'hdfs://cluster1/data/kafka/syslog/${load.day}'
+ path 's3a://log-raw/syslog/${load.day}'
- path 'hdfs://cluster1/data/kafka/authlog/${load.day}'
+ path 's3a://log-raw/authlog/${load.day}'
- DELETE FROM hive_prod.logs.syslog_iceberg
+ DELETE FROM ozone_s3.logs_s3.syslog_iceberg
- INSERT INTO hive_prod.logs.syslog_iceberg
+ INSERT INTO ozone_s3.logs_s3.syslog_iceberg
- DELETE FROM hive_prod.logs.authlog_iceberg
+ DELETE FROM ozone_s3.logs_s3.authlog_iceberg
- INSERT INTO hive_prod.logs.authlog_iceberg
+ INSERT INTO ozone_s3.logs_s3.authlog_iceberg
ただし、Kafka Connectの保存先をまだHDFSから変更していない場合、当日分のrawはOzoneに存在しない。移行期間中は次の順で日次処理を行う。
完全移行する場合はKafka Connect側もS3 Sinkへ変更する。S3 Sink connectorの導入方法と設定項目は使用するdistributionによって異なるため、本記事では既存HDFS Sinkからの履歴移行とSpark/Icebergの切り替えまでを対象とする。
切り替え手順
次の順で実施する。
- Ozone clusterとS3 bucketを構築する
- S3Aの接続を確認する
- 1日分のrawをDistCpでコピーする
- SparkでHDFS rawとOzone rawの件数を比較する
- Ozone側Icebergテーブルを別namespaceに作成する
- 1日分を論理コピーし、件数、時刻範囲、集計差分を比較する
- 過去分を
dt単位で移行する - 移行中に増えたrawをDistCpの
-updateで追従する - 日次ロードをOzone入力、Ozone Iceberg出力へ変更する
- Trino、Grafana、Zeppelinなどの参照先をOzone側へ変更する
- 一定期間、HDFS側をread-onlyのrollback元として保持する
- 保持期間と復旧手順を確認してからHDFS側を廃止する
切り替え直前には、HDFS側へのIceberg書き込みを止め、最後の対象日をOzoneへ同期して差分が0件であることを確認する。
TrinoをOzone S3 Icebergへ接続する
Trinoの設定変更は必要か
必要である。
Icebergのmetadataにdata fileのpathが記録されていても、Trinoには次の情報が必要になる。
- Iceberg catalogとして使用するHive Metastoreの接続先
-
s3a://を読み込むS3 filesystem - Ozone S3 Gatewayのendpointとregion
- パス形式のアクセス
- S3 access keyとsecret key
既存のHDFS用iceberg catalogを直接置き換えず、Ozone確認用にiceberg_ozone catalogを追加する。
iceberg = HDFS Iceberg、rollback用
iceberg_ozone = Ozone S3 Iceberg、移行先
以下はTrino 476、Coordinator兼Worker 1台とWorker 2台の構成を前提とする。catalog設定とcredentialはtrino1、trino2、trino3へ同じ内容を配置する。
TrinoはS3互換storageへ接続できるが、公式に検証される製品は限られる。Ozone S3 Gatewayは本番切り替え前に、SELECT、metadata table、partition pruning、複数Workerでの分散読み込みを実データで確認する。
既存catalogをバックアップする
各Trino nodeで実行する。
sudo cp -a \
/opt/trino/etc/catalog/iceberg.properties \
/opt/trino/etc/catalog/iceberg.properties.$(date +%Y%m%d%H%M%S).bak
既存HDFS catalogは削除しない。
Ozone credentialをsystemdへ渡す
catalog fileへsecretを平文で書かず、環境変数を使用する。
sudo tee /etc/trino/ozone-s3.env >/dev/null <<'EOF'
OZONE_S3_ACCESS_KEY=<OZONE_S3_ACCESS_KEY>
OZONE_S3_SECRET_KEY=<OZONE_S3_SECRET_KEY>
EOF
sudo chown root:trino /etc/trino/ozone-s3.env
sudo chmod 640 /etc/trino/ozone-s3.env
各nodeのTrino systemd unitへdrop-inを追加する。
sudo systemctl edit trino
[Service]
EnvironmentFile=/etc/trino/ozone-s3.env
実際のservice名が異なる場合は既存unit名へ置き換える。
Ozone用Iceberg catalogを追加する
各Trino nodeに/opt/trino/etc/catalog/iceberg_ozone.propertiesを作成する。
sudo tee /opt/trino/etc/catalog/iceberg_ozone.properties >/dev/null <<'EOF'
connector.name=iceberg
iceberg.catalog.type=hive_metastore
hive.metastore.uri=thrift://hive1:9083,thrift://hive2:9083
fs.native-s3.enabled=true
s3.endpoint=http://ozone1:9878
s3.region=us-east-1
s3.path-style-access=true
s3.aws-access-key=${ENV:OZONE_S3_ACCESS_KEY}
s3.aws-secret-key=${ENV:OZONE_S3_SECRET_KEY}
iceberg.file-format=PARQUET
iceberg.compression-codec=ZSTD
EOF
sudo chown trino:trino /opt/trino/etc/catalog/iceberg_ozone.properties
sudo chmod 640 /opt/trino/etc/catalog/iceberg_ozone.properties
Trino 476ではnative S3 filesystemをfs.native-s3.enabled=trueで有効にする。新しいTrinoではproperty名がfs.s3.enabledへ変更されているため、upgrade時は使用versionのdocumentationを確認する。
Ozone側tableはSparkでs3a://lakehouse/...として作成している。Trinoのnative S3 filesystemがOzone endpointへ接続し、Iceberg metadataに記録されたobjectを読み込む。
全Trino nodeへ配布する
trino1で設定を作成した場合の例である。
sudo rsync -av \
/opt/trino/etc/catalog/iceberg_ozone.properties \
trino2:/tmp/
sudo rsync -av \
/opt/trino/etc/catalog/iceberg_ozone.properties \
trino3:/tmp/
trino2とtrino3で所定の場所へ配置し、/etc/trino/ozone-s3.envとsystemd drop-inも同様に設定する。Coordinatorだけでなく、data fileを読む全Workerに設定とnetwork到達性が必要である。
Trinoを再起動する
sudo systemctl daemon-reload
sudo systemctl restart trino
sudo systemctl status trino --no-pager
3台構成ではWorker、Coordinatorの順に停止し、Coordinator、Workerの順に起動する既存のcluster運用手順に合わせる。
logを確認する。
sudo journalctl -u trino -n 200 --no-pager
iceberg_ozone catalogのload error、credential error、Ozone endpointへの接続失敗がないことを確認する。
Trinoから確認する
Coordinatorへ接続して実行する。
SHOW CATALOGS;
SHOW SCHEMAS FROM iceberg_ozone;
SHOW TABLES FROM iceberg_ozone.logs_s3;
SELECT
dt,
COUNT(*) AS cnt,
MIN(ts) AS min_ts,
MAX(ts) AS max_ts
FROM iceberg_ozone.logs_s3.syslog_iceberg
GROUP BY dt
ORDER BY dt DESC
LIMIT 7;
authlogも確認する。
SELECT dt, COUNT(*) AS cnt
FROM iceberg_ozone.logs_s3.authlog_iceberg
GROUP BY dt
ORDER BY dt DESC
LIMIT 7;
Iceberg metadata tableも確認する。
SELECT committed_at, snapshot_id, operation
FROM iceberg_ozone.logs_s3."syslog_iceberg$snapshots"
ORDER BY committed_at DESC
LIMIT 10;
HDFS側と同じ対象日を比較する。
SELECT 'hdfs' AS src, COUNT(*) AS cnt
FROM iceberg.logs.syslog_iceberg
WHERE dt = DATE '2026-09-15'
UNION ALL
SELECT 'ozone' AS src, COUNT(*) AS cnt
FROM iceberg_ozone.logs_s3.syslog_iceberg
WHERE dt = DATE '2026-09-15';
GrafanaとZeppelinを切り替える
Trinoの接続先は同じCoordinatorのままでよい。SQL内のcatalogとschemaを変更する。
- FROM iceberg.logs.syslog_iceberg
+ FROM iceberg_ozone.logs_s3.syslog_iceberg
- FROM iceberg.logs.authlog_iceberg
+ FROM iceberg_ozone.logs_s3.authlog_iceberg
Grafana dashboardを一度に上書きせず、複製したdashboardでHDFS版とOzone版の件数、時刻、応答時間を比較してから切り替える。
Trinoのrollback
問題があればqueryの参照先を既存のiceberg.logsへ戻す。iceberg.propertiesを残しているため、Ozone用catalogを削除しなくても即時に参照経路を戻せる。
通常時 : iceberg_ozone.logs_s3.*
rollback : iceberg.logs.*
HDFSからOzone移行時のrollback
問題が発生した場合は、日次ロードと参照先を既存のhive_prod.logsへ戻す。HDFS側を削除していなければ、metadataの書き換えやOzoneからの逆コピーをせずに切り戻せる。
通常時
ozone_s3.logs_s3.* を参照
rollback時
hive_prod.logs.* を参照
切り替え後にOzone側だけへ書き込まれた期間がある場合は、その期間をHDFS側へ論理コピーしてから完全に戻す。Iceberg warehouseのdirectoryを逆方向へ単純コピーしない。
運用上の注意
HDFSを減らしても保存先の冗長化は必要
OzoneがHadoop clusterと同じ電源、同じhost、同じdiskだけで動いている場合、保存先をS3 APIへ変えても障害分離にはならない。少なくとも次を検討する。
- OM/SCMのHAと複数DataNode構成
- 複数S3 Gatewayとload balancer
- TLS
- disk故障と容量の監視
- 別clusterまたは別mediaへのbackup
- credentialの定期rotation
- OzoneとHadoopを同時に失わない配置
small file問題は残る
保存先をHDFSからS3へ変えても、細かいdata fileが大量に作られる問題は解消しない。Icebergのcompaction、snapshot expiration、orphan file削除は継続する。
rename前提の処理を持ち込まない
Object StorageはHDFSと同じfilesystemではない。renameやdirectoryの存在を前提にした独自shellは見直す。IcebergテーブルはIcebergのcommitとmaintenance procedureを通して操作する。
credentialをログへ出さない
access key、secret keyを次へ残さない。
- Git管理対象の設定ファイル
- Qiita記事
- シェル履歴
- systemd unitのcommand line
- Sparkイベントログ
Ozone移行後の構成
今回の移行後もHive MetastoreはIceberg catalogとして使用する。廃止するのはHive curated処理とHDFS上の保存領域であり、Hive Metastoreまで同時に撤去するものではない。
まとめ
HDFSからローカルS3へ移行するときは、rawとIcebergを同じ方法で扱わないことが重要である。
Ozone側は別namespaceで作成し、HDFS側と日別件数、時刻範囲、集計結果を比較した。検証後に日次ロードと参照先を切り替えることで、問題があればHDFS側へ戻せる。
前回Hive curatedを廃止して変換処理をSparkへ集約したため、今回変更する中心は入力pathとIcebergの書き込み先になった。保存形式を単純コピーするのではなく、IcebergのmetadataをOzone上で新しく生成することが、安全に移行するための要点である。
TrinoではHive Metastore設定だけでなく、Ozone endpoint、path-style access、credentialを持つiceberg_ozone catalogを全nodeへ追加した。既存HDFS catalogを残すことで、query単位で比較とrollbackができる。
参考資料
- Apache Ozone Installation Quick Start
- Apache Ozone S3 Gateway
- Apache Ozone: S3 API
- Apache Ozone: s3a and Ozone
- Apache Hadoop: Connecting to an Amazon S3 Bucket through the S3A Connector
- Apache Hadoop: Working with Third-party S3 Stores
- Trino: Iceberg connector
- Trino: S3 file system support
関連記事
- 【導入編】HDFSログ分析基盤をAWS S3へ段階移行する全8編の構成を紹介する
- 【第1編】Hiveの中間テーブルを廃止してHDFSからApache Icebergへ直接取り込んでみる
- 【第3編】Apache OzoneのローカルS3からAWS S3へrawログとIcebergを移行してみる
- ログ収集・解析基盤にSpark/Icebergを構築してみた
- Kafka / Spark / Flink / Iceberg で時刻(ts)の扱いを統一する設計
実機検証について
本記事の手順は、執筆時点では実機環境での一連の動作検証をまだ完了していない。掲載している設定値、command、service名、package構成は設計例を含むため、利用するHadoop、Apache Ozone、Spark、Iceberg、Trinoなどのversionと実環境の構成に合わせた確認が必要である。
実施する場合は、検証環境でbackupとrollback手順を用意し、HDFS側を保持したまま件数、時刻範囲、集計結果を比較してから切り替える。実機検証の結果に応じて本記事を更新する予定である。