# 機能設計書 34-Variantデータ型

## 概要

本ドキュメントは、Apache Sparkにおける半構造化データ向けVariantデータ型に関する機能設計書である。Variant型はJSON等の半構造化データをバイナリ形式で効率的に格納し、カラムナストレージ上での高速なアクセスとシュレッディング（Shredding）によるクエリ最適化を実現する。

### 本機能の処理概要

**業務上の目的・背景**：現代のデータパイプラインでは、スキーマが事前に確定していない半構造化データ（JSON、ログデータ等）の処理が増加している。従来のSTRING型でのJSON格納は、解析のたびにパースコストが発生し、カラムナ圧縮の恩恵も受けにくい。Variant型は、半構造化データをバイナリでコンパクトに格納し、パース済みの状態で保持することで、繰り返しアクセス時のパフォーマンスを大幅に改善する。

**機能の利用シーン**：IoTセンサーデータ（スキーマが頻繁に変化）の格納と分析、APIレスポンス（JSON）のデータレイクへの蓄積、ログデータの構造化分析、スキーマ進化に柔軟に対応するデータパイプライン構築。

**主要な処理内容**：
1. Variantバイナリ形式の管理：value配列（データ本体）とmetadata配列（辞書キー）の2つのバイナリで構成
2. JSON解析とVariant構築：VariantBuilderによるJSON文字列からVariantバイナリへの変換
3. 型安全なアクセス：getBoolean/getLong/getString等の型付きアクセサメソッド
4. オブジェクト・配列操作：getFieldByKey（キー検索、バイナリサーチ対応）、getElementAtIndex（配列要素アクセス）
5. JSON出力：toJson()によるVariantバイナリからJSON文字列への変換
6. シュレッディング：VariantShreddingWriterによるカラムナ分解格納

**関連システム・外部連携**：Parquetストレージ（シュレッディング格納）、Jackson JSON Parser（JSON解析）

**権限による制御**：特別な権限制御はない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能に直接関連するWeb UI画面はない |

## 機能種別

データ変換 / データストレージ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| json | String | Yes（parseJson） | JSON文字列 | 有効なJSON |
| allowDuplicateKeys | boolean | Yes（parseJson） | 重複キーを許可するか | - |
| value | byte[] | Yes（Variant構築） | Variantバイナリデータ本体 | サイズ上限16MiB |
| metadata | byte[] | Yes（Variant構築） | Variantメタデータ（辞書） | バージョン検証、サイズ上限16MiB |
| key | String | Yes（getFieldByKey） | オブジェクトフィールドのキー名 | - |
| index | int | Yes（getElementAtIndex） | 配列要素のインデックス | 0以上arraySize未満 |

### 入力データソース

- JSON文字列（VariantBuilder.parseJson経由）
- プログラムAPIからのバイナリデータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Variant | Variant | パース済みVariantオブジェクト |
| value | byte[] | Variantバイナリデータ本体 |
| metadata | byte[] | Variantメタデータバイナリ |
| json | String | toJson()によるJSON文字列出力 |

### 出力先

- Spark SQLカラム値（Variant型）
- Parquetファイル（シュレッディング形式）

## 処理フロー

### 処理シーケンス

```
1. JSON → Variant変換
   └─ VariantBuilder.parseJson(json, allowDuplicateKeys)
2. JSONパーサー処理
   └─ Jackson JsonParserでトークン単位に解析
3. バイナリ構築
   └─ VariantBuilder内部でvalue/metadataバイナリを構築
4. 辞書構築
   └─ dictionaryKeysからメタデータバイナリを生成
5. Variant値アクセス
   └─ getType()で型判定、型別アクセサで値取得
6. オブジェクトフィールド検索
   └─ 要素数32未満は線形探索、32以上はバイナリサーチ
7. JSON出力
   └─ toJson(zoneId)で再帰的にJSON文字列を構築
```

### フローチャート

```mermaid
flowchart TD
    A[JSON文字列] --> B[VariantBuilder.parseJson]
    B --> C[Jackson JsonParser]
    C --> D[buildJson - 再帰的構築]
    D --> E[Variant result]
    E --> F{アクセス操作}
    F -->|getFieldByKey| G{要素数チェック}
    G -->|少32未満| H[線形探索]
    G -->|多32以上| I[バイナリサーチ]
    F -->|getElementAtIndex| J[配列要素アクセス]
    F -->|toJson| K[再帰的JSON構築]
    F -->|getType| L[型情報取得]
    L --> M{型分岐}
    M -->|OBJECT/ARRAY/NULL/BOOLEAN/LONG/STRING/DOUBLE/DECIMAL/DATE/TIMESTAMP等| N[型付きアクセサ]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | サイズ上限 | value/metadataそれぞれ16MiB（SIZE_LIMIT）まで | Variantコンストラクタ時 |
| BR-02 | バージョン検証 | metadataの先頭バイトでバージョンを検証（VERSION_MASK） | Variantコンストラクタ時 |
| BR-03 | バイナリサーチ閾値 | オブジェクトフィールド検索で要素数32以上の場合にバイナリサーチに切り替え | getFieldByKey実行時 |
| BR-04 | 重複キー制御 | allowDuplicateKeysフラグで重複キーの許可/禁止を制御 | VariantBuilder構築時 |
| BR-05 | 非有限浮動小数点 | Double/Floatの非有限値（Infinity, NaN）はJSON出力時に文字列として引用符付き出力 | toJson実行時 |

### 計算ロジック

- **メタデータ構築**: オフセットサイズ = getIntegerSize(max(dictionaryStringSize, numKeys))、ヘッダバイト = VERSION | ((offsetSize-1) << 6)
- **バイナリサーチ**: (low + high) >>> 1 で中間位置を計算（オーバーフロー安全な符号なし右シフト）
- **型判定**: VariantUtil.getType(value, pos)でType列挙値を返却

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | データベース操作なし（インメモリ処理） |

### テーブル別操作詳細

データベース操作は発生しない。Parquetファイルへの格納時はSparkのデータソースAPIを通じて処理される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| MALFORMED_VARIANT | SparkRuntimeException | 不正なバイナリ形式のVariantデータ | データの整合性を確認 |
| VARIANT_SIZE_LIMIT | VariantSizeLimitException | value/metadataが16MiB超過 | データサイズを削減 |
| IOException | IOException | JSON解析エラー | 有効なJSONを入力 |
| JsonParseException | JsonParseException | 不正なJSON構文 | JSON構文を修正 |

### リトライ仕様

Variant操作はインメモリ処理のためリトライ機構はない。

## トランザクション仕様

インメモリ操作であり、トランザクション管理の対象外。

## パフォーマンス要件

- JSON文字列からのVariant構築はシングルパスで実行される
- オブジェクトフィールド検索は要素数32以上でO(log n)のバイナリサーチ
- Variant値はゼロコピーでサブ要素にアクセス可能（posオフセットによる参照）
- サイズ上限16MiBによりメモリ安定性を確保

## セキュリティ考慮事項

- 入力JSONのサイズ制限（16MiB）によりDoS攻撃を防止
- 不正なバイナリデータに対するバリデーション（バージョン検証、サイズ検証）

## 備考

- Variantモジュールは `common/variant/` 配下にJavaで実装されている（Spark依存を回避するため）
- VariantValとの構造的等価性が設計上考慮されている（コメント参照）
- シュレッディング（ShreddingUtils/VariantShreddingWriter）により、頻繁にアクセスされるフィールドをカラムナ形式で分離格納可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

Variantのバイナリフォーマットを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | VariantUtil.java | `common/variant/src/main/java/org/apache/spark/types/variant/VariantUtil.java` | バイナリ形式定数（VERSION, SIZE_LIMIT）、Type列挙型、handleObject/handleArrayメソッド |
| 1-2 | Variant.java | `common/variant/src/main/java/org/apache/spark/types/variant/Variant.java` | value/metadata/posフィールド、型付きアクセサメソッド |

**読解のコツ**: Variant型はvalue配列とmetadata配列の2つのバイナリで構成される。valueはデータ本体、metadataはオブジェクトキーの辞書である。posオフセットにより、サブ要素アクセス時にvalue配列のコピーを回避している。

#### Step 2: Variant構築を理解する

JSON解析とバイナリ構築の処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | VariantBuilder.java | `common/variant/src/main/java/org/apache/spark/types/variant/VariantBuilder.java` | parseJson静的メソッド、result()メソッド（メタデータ構築） |

**主要処理フロー**:
1. **55-59行目**: `parseJson(String, boolean)` - JsonFactoryでパーサー生成、buildJson()呼び出し
2. **65-70行目**: `parseJson(JsonParser, boolean)` - VariantBuilderインスタンス生成、buildJson→result
3. **73-100行目**: `result()` - dictionaryKeysからメタデータバイナリを構築。offsetSize計算、ヘッダバイト生成

#### Step 3: 値アクセスを理解する

Variant値の読み取りとJSON変換を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Variant.java | `common/variant/src/main/java/org/apache/spark/types/variant/Variant.java` | getFieldByKey（バイナリサーチ）、toJson（再帰変換） |

**主要処理フロー**:
- **52-69行目**: コンストラクタ - バージョン検証、サイズ上限検証
- **146-181行目**: `getFieldByKey` - 要素数32未満は線形探索、32以上はバイナリサーチ（符号なし右シフトで中間位置計算）
- **238-242行目**: `toJson` - StringBuilderで再帰的にJSON構築
- **281-363行目**: `toJsonImpl` - 型別JSON出力（OBJECT/ARRAY/NULL/BOOLEAN/LONG/STRING/DOUBLE/DECIMAL/DATE/TIMESTAMP/FLOAT/BINARY/UUID）

#### Step 4: シュレッディングを理解する

カラムナ分解格納の仕組みを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ShreddingUtils.java | `common/variant/src/main/java/org/apache/spark/types/variant/ShreddingUtils.java` | シュレッディングユーティリティ |
| 4-2 | VariantShreddingWriter.java | `common/variant/src/main/java/org/apache/spark/types/variant/VariantShreddingWriter.java` | シュレッディング書き込み |
| 4-3 | VariantSchema.java | `common/variant/src/main/java/org/apache/spark/types/variant/VariantSchema.java` | Variantスキーマ定義 |

### プログラム呼び出し階層図

```
VariantBuilder.parseJson(json, allowDuplicateKeys)
    │
    ├─ JsonFactory.createParser(json)
    ├─ VariantBuilder.buildJson(parser)
    │      └─ 再帰的にJSON要素を構築
    └─ VariantBuilder.result()
           └─ メタデータバイナリ構築

Variant(value, metadata)
    │
    ├─ getType() → VariantUtil.getType(value, pos)
    ├─ getFieldByKey(key)
    │      ├─ 線形探索（要素数 < 32）
    │      └─ バイナリサーチ（要素数 >= 32）
    ├─ getElementAtIndex(index)
    │      └─ handleArray()
    └─ toJson(zoneId)
           └─ toJsonImpl() 再帰呼び出し
```

### データフロー図

```
[入力]                          [処理]                              [出力]

JSON文字列              VariantBuilder.parseJson          Variant
                  ──▶  → buildJson + result()       ──▶  (value[] + metadata[])

Variant                 型付きアクセサ                    プリミティブ値
(value/metadata)  ──▶  getBoolean/getLong/etc       ──▶  (boolean/long/String等)

Variant                 toJson(zoneId)                   JSON文字列
(value/metadata)  ──▶  → toJsonImpl 再帰           ──▶  (String)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Variant.java | `common/variant/src/main/java/org/apache/spark/types/variant/` | ソース | Variantデータ型の中核クラス |
| VariantBuilder.java | `common/variant/src/main/java/org/apache/spark/types/variant/` | ソース | JSON→Variant構築 |
| VariantUtil.java | `common/variant/src/main/java/org/apache/spark/types/variant/` | ソース | バイナリフォーマットユーティリティ |
| VariantSchema.java | `common/variant/src/main/java/org/apache/spark/types/variant/` | ソース | Variantスキーマ定義 |
| ShreddingUtils.java | `common/variant/src/main/java/org/apache/spark/types/variant/` | ソース | シュレッディングユーティリティ |
| VariantShreddingWriter.java | `common/variant/src/main/java/org/apache/spark/types/variant/` | ソース | シュレッディング書き込み |
| VariantSizeLimitException.java | `common/variant/src/main/java/org/apache/spark/types/variant/` | ソース | サイズ上限例外 |
