1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【AWS】Apache Iceberg 実践入門 — Athena で始めるモダンデータレイクの設計と運用

1
Last updated at Posted at 2026-03-29

【AWS】Apache Iceberg 実践入門 — Athena で始めるモダンデータレイクの設計と運用

はじめに

S3 にデータを貯めて Athena でクエリする——この構成は手軽ですが、運用が進むと壁にぶつかります。

  • ログのスキーマが変わったが、過去データとの互換性をどう保つのか
  • 誤ったデータを INSERT してしまったが、特定の行だけ DELETE できない
  • パーティション構成を変えたいが、過去データをすべて書き直す必要がある
  • 小さなファイルが大量に溜まり、クエリが遅くなる一方

これらはすべて、従来の Hive テーブル形式の限界に起因する問題です。Apache Iceberg はこれらの課題をテーブルフォーマットのレベルで解決します。

この記事では、Apache Iceberg の基本概念を押さえた上で、Athena を中心とした AWS 環境での実践的な使い方を SQL サンプル付きで解説します。パーティション設計、コンパクション運用、メダリオンアーキテクチャまで、データレイク設計に必要な知識を一通りカバーします。

対象読者: AWS でデータ分析基盤を構築する中〜上級エンジニア

前提知識:

この記事で得られること:

  • Apache Iceberg の仕組みと従来の Hive テーブルとの違い
  • Athena での Iceberg テーブル操作(CRUD、タイムトラベル、コンパクション)
  • CoW(Copy-on-Write)と MoR(Merge-on-Read)の違いと Athena での動作
  • パーティション設計・ファイル管理のベストプラクティス
  • メダリオンアーキテクチャ — Bronze(生データ)/ Silver(クレンジング済み)/ Gold(集計済み)の 3 層で管理する設計パターン

Iceberg シリーズについて: 本シリーズは全 5 回で構成されています。基本操作(本記事)→ 運用コスト最適化 → ストリーミング取り込み → エンタープライズ設計 → S3 Tables と段階的に解説し、最終回ではシリーズ全体の知見を踏まえたストレージ・取り込み・エンジン・アクセス制御の構成選定ガイドを提供します。

Apache Iceberg とは — テーブルフォーマットの基本

なぜテーブルフォーマットが必要なのか

S3 はオブジェクトストレージであり、「テーブル」という概念を持ちません。従来は Hive メタストアがディレクトリ構造をパーティションとして管理することで、S3 上のファイル群をテーブルとして扱ってきました。

しかし Hive 方式には根本的な制約があります。

課題 Hive テーブル Iceberg テーブル
スキーマ変更 カラム追加は可能だが、リネーム・型変更は困難 カラムの追加・削除・リネーム・型の拡張が可能
行レベル操作 パーティション単位の上書きのみ 行単位の UPDATE / DELETE / MERGE
パーティション変更 全データの書き直しが必要 過去データに手を加えず変更可能
トランザクション なし(部分的な書き込みが見える) ACID トランザクション
過去データ参照 不可 タイムトラベルで任意時点を参照可能
ファイル管理 ディレクトリのリスティングに依存 メタデータで全ファイルを正確に追跡

Apache Iceberg は Netflix で開発され、2018 年に Apache Software Foundation に寄贈されたオープンソースのテーブルフォーマットです。S3 上のデータファイル群を「論理的なテーブル」として管理するメタデータの仕組みを提供し、上記の課題をすべて解決します。

3 層アーキテクチャ

Iceberg テーブルは 3 つの層で構成されます。

格納場所 主な内容
Catalog Layer Glue Data Catalog テーブル名 → 現在の metadata.json パスの解決
Metadata Layer S3(metadata ディレクトリ) metadata.json → Manifest List → Manifest File のツリー構造
Data Layer S3(data ディレクトリ) 実データ(Parquet / ORC ファイル)

Catalog Layer は、テーブル名から現在の metadata.json のパスを解決します。AWS 環境では Glue Data Catalog がこの役割を担い、Athena・Glue・EMR・Redshift が同じカタログを参照できます。

Metadata Layer がテーブルの状態を完全に記述します。各スナップショット(テーブルのある時点の状態)が Manifest List → Manifest File のツリー構造でデータファイルを追跡し、カラムレベルの統計情報(min/max 値、null 数)を保持します。この統計情報により、クエリ時に不要なファイルを読み飛ばすファイルプルーニングが可能になります。

Data Layer は実データを格納する Parquet / ORC ファイルです。S3 上のオブジェクトとして保存され、Iceberg はこれらのファイルを直接変更しません。

主要機能

ACID トランザクション

Iceberg は楽観的同時実行制御で ACID トランザクションを実現します。

  1. ライターが現在の metadata.json を読み取る
  2. 新しいスナップショット(Manifest List + Manifest File + データファイル)を作成
  3. 新しい metadata.json を書き込み、カタログのポインタを atomic swap で更新
  4. 競合があればリトライ

グローバルロック不要で、リーダーは常に一貫したスナップショットを参照できます。

スキーマエボリューション

カラムの追加・削除・リネーム・型の拡張(int→bigint、float→double 等のプロモーション)を、既存データファイルの書き換えなしに実行できます。Iceberg はカラムを一意のフィールド ID で追跡するため、カラム名を変更しても過去のデータファイルとの整合性が保たれます。

-- Athena でのスキーマ変更例
ALTER TABLE my_db.events ADD COLUMNS (user_agent string);
-- カラムのリネーム(CHANGE COLUMN <旧名> <新名> <型> の形式)
ALTER TABLE my_db.events CHANGE COLUMN status status_code string;
-- 型の拡張(プロモーション): int → bigint
ALTER TABLE my_db.events CHANGE COLUMN event_count event_count bigint;

隠しパーティショニング

Iceberg のパーティションはクエリから隠蔽されます。

-- テーブル定義でパーティション変換を指定
CREATE TABLE my_db.events (
    event_id string,
    event_time timestamp,
    event_type string,
    payload string
)
PARTITIONED BY (day(event_time))  -- 隠しパーティション
LOCATION 's3://my-bucket/warehouse/events'
TBLPROPERTIES ('table_type' = 'ICEBERG');

-- クエリでは生の値を指定するだけ。パーティションを意識する必要がない
SELECT * FROM my_db.events
WHERE event_time BETWEEN TIMESTAMP '2026-01-01' AND TIMESTAMP '2026-01-31';

Hive テーブルでは WHERE year=2026 AND month=1 のようにパーティションカラムを明示する必要がありましたが、Iceberg ではエンジンが自動的にパーティションプルーニングを適用します。

さらにパーティションエボリューションにより、パーティション戦略の変更(例: month(event_time)day(event_time))を過去データの書き直しなしに実行できます。

-- パーティション戦略の変更(Spark / EMR / Glue ETL で実行)
ALTER TABLE my_db.events SET PARTITION SPEC (day(event_time));

注意: パーティションエボリューション DDL は現時点で Athena では未サポートです。Spark(EMR / Glue ETL)から実行してください。変更後のパーティション構成は Athena からも透過的に参照できます。

タイムトラベル

Iceberg はスナップショットベースの履歴管理により、過去の任意時点のテーブル状態をクエリで参照できます。誤った INSERT や DELETE からの復旧、監査ログの参照、ML の特徴量再現などに活用できます。具体的な SQL は「実践: Athena で Iceberg テーブルを操作する」で示します。

AWS での Iceberg — サービス統合の全体像

AWS は Apache Iceberg をファーストクラスでサポートしています。各サービスの役割を整理します。

レイヤー サービス 役割 Iceberg との関係
メタデータ Glue Data Catalog メタデータカタログ(SSOT) 全サービスの共通メタストア
コンピュート Athena サーバーレスクエリ CRUD + タイムトラベル + OPTIMIZE
コンピュート Glue ETL サーバーレス ETL Spark ベースの読み書き + 自動コンパクション
コンピュート EMR マネージド Spark / Flink 大規模バッチ処理 + コンパクション専用クラスタ
コンピュート Redshift DWH + Spectrum Iceberg テーブルへの読み書き
ストレージ S3 Tables Iceberg 最適化ストレージ 自動メンテナンス + 高スループット
インジェスト Data Firehose ストリーミング取り込み CDC 対応のリアルタイム配信

各サービスの連携は、Glue Data Catalog を中心としたハブ型の構成になっています。メタデータ層(Glue Data Catalog)にすべてのエンジンがアクセスし、ストレージ層(S3 / S3 Tables)のデータを読み書きします。インジェスト層(Data Firehose)はストレージ層に直接データを書き込み、Glue Data Catalog にメタデータを登録します。

ポイント: Glue Data Catalog を中心に、すべてのサービスが同じ Iceberg テーブルにアクセスできます。Athena でテーブルを作成し、Glue ETL でデータを投入し、Redshift Spectrum で BI クエリを実行する——といった組み合わせが、追加設定なしで動作します。この記事では Athena を中心に解説します。 Data Firehose によるストリーミング取り込みは第 3 弾、Lake Formation によるアクセス制御は第 4 弾、S3 Tables は第 5 弾で詳しく扱います。

S3 Tables — 注目の新サービス

2024 年 12 月の re:Invent 2024 で発表された S3 の新しいバケットタイプです。Iceberg テーブルに最適化されたストレージを提供し、自動コンパクションにより自己管理テーブルと比較して最大 3 倍のクエリスループット最大 10 倍のトランザクション/秒を実現します(AWS 公式ベンチマーク。自動コンパクションによるファイル最適化が主な要因)。コンパクションやスナップショット管理が自動実行されるため、運用負荷を大幅に削減できます。ただし、料金モデルが汎用 S3 と異なるため、ワークロードによってはコスト増になる場合もあります。

最近のアップデート:

  • Intelligent-Tiering & Replication(2025 年 12 月): アクセスパターンに基づく自動ストレージ階層化と、リージョン/アカウント間のテーブルレプリケーションが追加
  • IAM ベースの簡易パーミッション(2026 年 3 月): Glue Data Catalog が S3 Tables と Iceberg マテリアライズドビューに対する IAM ベースの認可をサポート。ストレージ・カタログ・クエリエンジンの権限を単一の IAM ポリシーで定義可能に

S3 Tables の料金モデル、汎用 S3 との TCO 比較、導入判断基準については第 5 弾「S3 Tables と SageMaker Lakehouse」で詳しく解説します。

実践: Athena で Iceberg テーブルを操作する

ここからは Athena での具体的な操作を SQL サンプルで示します。

テーブル作成

-- データベースの作成
CREATE DATABASE IF NOT EXISTS analytics;

-- Iceberg テーブルの作成
CREATE TABLE analytics.sales (
    sale_id string,
    sale_date timestamp,
    product_id string,
    product_name string,
    category string,
    unit_price decimal(10,2),
    quantity int,
    region string
)
PARTITIONED BY (month(sale_date), region)
LOCATION 's3://my-data-lake/warehouse/sales'
TBLPROPERTIES (
    'table_type' = 'ICEBERG',
    'format' = 'PARQUET',
    'write_compression' = 'ZSTD',
    'optimize_rewrite_delete_file_threshold' = '2'
);

ポイント:

  • table_type = 'ICEBERG' で Iceberg V2 テーブルが作成される(Athena は Apache Iceberg 1.4.2 を使用し、V2 テーブルのみ対応)
  • PARTITIONED BY (month(sale_date), region) では、month(sale_date) が隠しパーティション(変換関数で自動抽出)、region が identity パーティション(値をそのまま使用)。identity パーティションカラムは OPTIMIZE の WHERE 句で直接指定可能
  • write_compression = 'ZSTD' は圧縮率とパフォーマンスのバランスが良い推奨コーデック(GZIP に近い圧縮率、Snappy に近い速度)
  • optimize_rewrite_delete_file_threshold は OPTIMIZE 実行時に書き換え対象とする delete file の閾値(デフォルト: 2)。小さい値ほどコンパクションが積極的に実行される

CTAS — 新規テーブル作成とデータ投入を同時に行う場合は CTAS(CREATE TABLE AS SELECT)が効率的です:

-- CTAS では PARTITIONED BY ではなく partitioning プロパティを使用する
CREATE TABLE analytics.sales_2025
WITH (
    table_type = 'ICEBERG',
    location = 's3://my-data-lake/warehouse/sales_2025',
    partitioning = ARRAY['month(sale_date)', 'region'],
    format = 'PARQUET',
    write_compression = 'ZSTD'
)
AS SELECT * FROM raw_data.sales_csv WHERE year(sale_date) = 2025;

CRUD 操作

INSERT — データの追加:

INSERT INTO analytics.sales VALUES (
    'S001', TIMESTAMP '2026-01-15 10:30:00',
    'P100', 'ワイヤレスマウス', '周辺機器',
    3980, 2, 'ap-northeast-1'
);

UPDATE / DELETE — 行レベルの変更:

-- UPDATE: 特定行の更新
UPDATE analytics.sales
SET unit_price = 3480
WHERE product_id = 'P100' AND sale_date >= TIMESTAMP '2026-02-01';

-- DELETE: 特定行の削除
DELETE FROM analytics.sales
WHERE sale_id = 'S001';

MERGE INTO — CDC やストリーミングの差分取り込みで活用される Upsert(存在すれば更新、なければ挿入)操作:

MERGE INTO analytics.sales AS target
USING staging.new_sales AS source
ON target.sale_id = source.sale_id
WHEN MATCHED THEN
    UPDATE SET quantity = source.quantity, unit_price = source.unit_price
WHEN NOT MATCHED THEN
    INSERT (sale_id, sale_date, product_id, product_name, category,
            unit_price, quantity, region)
    VALUES (source.sale_id, source.sale_date, source.product_id,
            source.product_name, source.category, source.unit_price,
            source.quantity, source.region);

CoW vs MoR — 行レベル操作の実装方式

行レベル操作(UPDATE / DELETE / MERGE INTO)を使用する場合、Iceberg の 2 つの実装方式——Copy-on-Write(CoW)Merge-on-Read(MoR)——を理解しておく必要があります。

方式 書き込みコスト 読み取りコスト 適するワークロード
Copy-on-Write (CoW) 高い(ファイル全体を再作成) 低い(常に最新データ) 読み取り重視、バッチ更新
Merge-on-Read (MoR) 低い(Delete File に記録) やや高い(読み取り時にマージ) 書き込み重視、ストリーミング

Athena での動作

Athena では UPDATE / DELETE / MERGE INTO は常に MoR(positional delete files)で動作します。 CoW の設定は Athena では無視されるため、Athena で行レベル操作を行う場合は定期的なコンパクションが必須です(Delete File をベースファイルにマージするため)。Spark(EMR / Glue ETL)では TBLPROPERTIES で明示的に CoW / MoR を選択可能です。

-- MoR の設定例(Spark / EMR / Glue ETL で実行)
ALTER TABLE analytics.sales SET TBLPROPERTIES (
    'write.delete.mode' = 'merge-on-read',
    'write.update.mode' = 'merge-on-read'
);

注意: 上記の TBLPROPERTIES 設定は Athena では未サポートです。明示的な CoW / MoR 設定が必要な場合は Spark(EMR / Glue ETL)から実行してください。

Deletion Vectors(V3 の新機能)

本記事では Iceberg V2 を前提としていますが、将来的に重要となる V3 の機能を紹介します。Iceberg V3 では、positional delete files に代わる新しい行レベル削除の仕組みとして Deletion Vectors(DV)が導入されました。DV はバイナリ形式(Roaring Bitmap)で Puffin ファイルに格納され、V2 の positional delete files(Parquet 形式)と比較して読み取り時のマージパフォーマンスが大幅に改善されています。書き込み増幅(write amplification)の削減や GDPR 対応の削除処理の効率化にも有効です。

注意: DV は Iceberg フォーマット V3 の機能です(Apache Iceberg 1.6.0 で初めて導入、1.8.0 で安定化)。AWS では 2025 年 11 月に EMR 7.12、Glue、SageMaker notebooks、S3 Tables、Glue Data Catalog で V3 Deletion Vectors と Row Lineage のサポートが発表されました。Athena は現時点で V3 未サポートのため、DV は利用できません。

タイムトラベル

Athena では Iceberg のメタデータテーブル(テーブル名$snapshotsテーブル名$history)を使って履歴を確認し、過去の任意時点にクエリできます。他にも $manifests$partitions$files などのメタデータテーブルが利用可能で、テーブルの内部状態を詳細に確認できます。

-- スナップショット一覧を確認
SELECT snapshot_id, committed_at, operation, summary
FROM analytics.sales$snapshots
ORDER BY committed_at DESC;

-- 特定時刻のデータを参照
SELECT * FROM analytics.sales
FOR TIMESTAMP AS OF TIMESTAMP '2026-01-15 00:00:00';

-- 変更履歴の確認
SELECT * FROM analytics.sales$history;

-- 誤操作からのロールバック
ALTER TABLE analytics.sales SET TBLPROPERTIES (
    'rollback_to_snapshot' = '8832659584924201850'
);

OPTIMIZE によるコンパクション

小さなファイルが蓄積するとクエリパフォーマンスが低下します。OPTIMIZE コマンドでファイルを統合できます。

-- テーブル全体のコンパクション
OPTIMIZE analytics.sales REWRITE DATA USING BIN_PACK;

-- 特定パーティションのみ(identity パーティションカラムで絞り込み)
OPTIMIZE analytics.sales REWRITE DATA USING BIN_PACK
WHERE region = 'ap-northeast-1';

注意: OPTIMIZE の WHERE 句には identity パーティションカラム(region など)のみ指定可能です。month(sale_date) のような隠しパーティション変換は WHERE 句で使用できません。隠しパーティションのみのテーブルでは WHERE 句なし(テーブル全体)で実行してください。また、1 回の OPTIMIZE で処理できるパーティション数は最大 100 です。大規模テーブルでは WHERE 句で範囲を絞り、バッチ実行してください。

運用のポイント: Athena の OPTIMIZE は手軽ですが、大規模テーブルでは Glue テーブルオプティマイザの自動コンパクションや EMR Spark での専用ジョブが適しています。Athena OPTIMIZE の限界と代替手段(Glue テーブルオプティマイザ、EMR Spark、S3 Tables の自動コンパクション)は第 2 弾「運用コスト最適化」で詳しく解説します。

VACUUM — 古いスナップショットの管理

OPTIMIZE でファイルを統合した後も、古いスナップショットが参照していた統合前のファイルは S3 上に残り続けます。VACUUM コマンドで不要なスナップショットと孤立ファイルを削除し、ストレージコストを削減できます。

-- 古いスナップショットと孤立ファイルの削除
VACUUM analytics.sales;

VACUUM はデフォルトで 432,000 秒(5 日間) より古いスナップショットを削除します。保持期間はテーブルプロパティで制御できます。

-- スナップショットの保持設定(例: 7日間、最低5スナップショット)
ALTER TABLE analytics.sales SET TBLPROPERTIES (
    'vacuum_max_snapshot_age_seconds' = '604800',
    'vacuum_min_snapshots_to_keep' = '5'
);

VACUUM の詳細な運用戦略(実行スケジュール、コスト影響、自動化)は第 2 弾「運用コスト最適化」で解説します。

設計のベストプラクティス

パーティション戦略

隠しパーティショニングを活用し、ユーザーにパーティションキーを意識させないのが基本方針です。

-- 良い例: 隠しパーティション
PARTITIONED BY (day(event_time), bucket(16, user_id))

-- Hive 方式(非推奨): 明示的なパーティションカラム
-- PARTITIONED BY (event_date string, user_bucket int)

パーティション設計の指針:

  • クエリで頻繁にフィルタされるカラムをパーティションキーにする
  • 粒度はデータ量に応じて調整(日次数 GB なら day()、月次数 GB なら month()
  • パーティション数が多すぎるとメタデータが肥大化し、少なすぎるとプルーニング効果が低下
  • 迷ったら粗い粒度から始め、パーティションエボリューションで後から変更

ファイルサイズとコンパクション

項目 推奨値 理由
ターゲットファイルサイズ 100 MB 〜 数百 MB 小さすぎるとメタデータオーバーヘッド、大きすぎると並列性が低下
コンパクション頻度 ストリーミング: 1〜4 時間ごと / バッチ: 日次 スモールファイル問題の早期解消
コンパクション方式 Binpack(標準)/ Sort(頻出フィルタカラム最適化) Binpack でファイル統合、Sort でクエリパフォーマンスも向上

スモールファイル問題: ストリーミング取り込みや頻繁な INSERT では数 KB〜数 MB のスモールファイルが大量に生成されます。これを放置するとクエリ時のファイルオープン回数が増加し、パフォーマンスが劇的に悪化します。コンパクションは必須の運用タスクです。

アーキテクチャパターン: メダリオンアーキテクチャ on Iceberg

データレイクハウスの標準的な設計パターンであるメダリオンアーキテクチャ(Bronze / Silver / Gold の 3 層)を、Iceberg on AWS で実装する構成を示します。データソース(IoT / アプリログ / RDB CDC)から Bronze → Silver → Gold へと段階的にデータ品質を高め、各層で異なる AWS サービスが役割を担います。

各層の AWS サービス配置:

取り込み 変換 クエリ
Bronze Data Firehose / Glue Streaming Athena(デバッグ用)
Silver Glue ETL / EMR Spark Athena(分析用)
Gold Glue ETL / EMR Spark Athena + Redshift Spectrum + QuickSight

メダリオンアーキテクチャで Iceberg を使う利点:

  • Bronze → Silver の差分更新: MERGE INTO で増分データだけを効率的に処理。Hive テーブルではパーティション丸ごと上書きが必要だった
  • スキーマ変更の伝播: Bronze でスキーマが変わっても、スキーマエボリューションで Silver / Gold への影響を最小化
  • データリネージ: タイムトラベルで各層の任意時点を再現でき、データの追跡可能性が向上
  • メタデータの一元管理: Glue Data Catalog で全層のテーブルを統一管理。Lake Formation でアクセス制御も一括設定
  • Gold Layer の書き込み戦略: 読み取り重視のため CoW を推奨(Spark / EMR 経由で設定)。定型レポートや BI ダッシュボードのクエリパフォーマンスが向上する

ストリーミング + バッチのハイブリッド

リアルタイムデータ取り込みとバッチ分析を組み合わせるパターンもメダリオンアーキテクチャと好相性です。

データソース(IoT / アプリログ / RDB CDC)→ Data Firehose → Bronze(Glue 自動コンパクションが並行で最適化)→ Glue ETL で差分処理 → Silver → Glue ETL で集計 → Gold → Athena / Redshift / QuickSight でクエリ、という流れです。

DMS + Data Firehose の組み合わせにより、RDB からの INSERT / UPDATE / DELETE をマネージドな CDC パイプラインで Iceberg テーブルに自動適用できます。

まとめ

Apache Iceberg は従来のデータレイクが抱えていた課題——スキーマ変更の困難さ、行レベル操作の不在、パーティション管理の硬直性、トランザクションの欠如——を根本的に解決するテーブルフォーマットです。

AWS で Iceberg を始める際のポイント:

  • Athena + Glue Data Catalog が最も手軽な出発点table_type = 'ICEBERG' を指定するだけで Iceberg テーブルを作成でき、CRUD・タイムトラベル・コンパクションがすべて SQL で操作可能
  • 隠しパーティショニングでパーティション設計を簡素化し、パーティションエボリューションで後からの変更に対応
  • コンパクションは必須の運用タスク。小規模なら Athena の OPTIMIZE、ストリーミングワークロードなら Glue 自動コンパクションを活用
  • メダリオンアーキテクチャで Bronze / Silver / Gold の 3 層にデータ品質を整理し、MERGE INTO で効率的な差分更新を実現
  • 圧縮は ZSTDファイルサイズは 100 MB 以上を目標に

なお、Athena は現時点で Iceberg V2 のみ対応ですが、EMR や Glue では V3 の Deletion Vectors がすでに利用可能です。Athena の V3 対応が実現すれば、MoR のパフォーマンスがさらに改善されることが期待されます。

次回は、コンパクション方式別のコスト比較、VACUUM によるスナップショット管理、S3 Tables vs 汎用 S3 の TCO 比較など、Iceberg テーブルの運用コスト最適化を詳しく解説します。


Iceberg シリーズ:

  1. Apache Iceberg 実践入門 — Athena で始めるモダンデータレイクの設計と運用 ← 本記事
  2. Iceberg テーブルの運用コスト最適化 — コンパクション・メンテナンス・ストレージ戦略
  3. Iceberg × Data Firehose ストリーミング取り込み — CDC パイプラインとコンパクション戦略
  4. Iceberg データレイクハウスの高度な設計 — Lake Formation・マルチエンジン連携・本番運用
  5. S3 Tables と SageMaker Lakehouse で進化する Iceberg データレイク — 自動最適化とコスト分析
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?