コンテンツにスキップ

4.4 アンチパターン

アンチパターンとは、特定のタイプを使うこと自体ではなく、データの意味や要件に合わない使い方です。 各例の前提が業務に当てはまるか確認し、代替案を選びます。同じスキーマでも、現在状態には適し、 履歴の蓄積には適さない場合があります。

LOOKUP に無制限の履歴を蓄積

問題

問題は検索頻度ではなく、増え続ける計測履歴を LOOKUP に保存する設計です。LOOKUP は全行と インデックスをメモリに保持するため、長期履歴が増えるほどメモリ負荷も増えます。 小さな参照データをキーで繰り返し検索する用途には適しています。

アンチパターンの例

-- 不適切: センサー計測を LOOKUP に保存
CREATE LOOKUP TABLE sensor_data_wrong (
    sensor_id  VARCHAR(64) PRIMARY KEY,
    value      DOUBLE,
    ts         DATETIME
);

-- sensor_id が PK のため、センサーごとに現在の1行しか保存できない
-- 履歴を追加すると同じ PK と競合する
INSERT INTO sensor_data_wrong VALUES ('TEMP-01', 25.3, NOW);
INSERT INTO sensor_data_wrong VALUES ('TEMP-01', 25.5, NOW);  -- PK 重複エラー

適切なパターン

タグ別の計測履歴の収集・クエリが中心なら TAG を検討します。LOOKUP でも計測ごとに別キーを 付けられますが、全履歴がメモリに常駐するコストは残ります。最新値キャッシュは、性能上必要で 元データから復旧できる場合に VOLATILE で追加します。TAG の最新値クエリで要件を満たすなら 別キャッシュは不要です。

-- 適切: 履歴は TAG
CREATE TAG TABLE sensor_data (
    name   VARCHAR(64) PRIMARY KEY,
    time   DATETIME    BASETIME,
    value  DOUBLE
);

-- 最新値キャッシュは VOLATILE
CREATE VOLATILE TABLE sensor_latest (
    sensor_id VARCHAR(64) PRIMARY KEY,
    value     DOUBLE,
    updated_at DATETIME
);

結果

アンチパターン(LOOKUP)適切な設計(TAG)
同じセンサーキーで履歴を蓄積X(このキーは行を識別)O(タグ名の下に複数計測行)
継続入力の経路行識別子中心時系列 Append API を利用可能
時間範囲クエリ一般条件検索タグ・時間軸の検索

センサー別にテーブルを作成

問題

センサー(タグ)ごとにテーブルを作ると、センサー数に応じて DDL、権限、クエリ対象が増え、 運用コストが高くなります。

アンチパターンの例

-- 不適切: センサーごとにテーブルを作成
CREATE TAG TABLE sensor_temp_01 (
    name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE
);
CREATE TAG TABLE sensor_temp_02 (
    name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE
);
CREATE TAG TABLE sensor_temp_03 (
    name VARCHAR(32) PRIMARY KEY, time DATETIME BASETIME, value DOUBLE
);
-- ... 10,000センサーなら10,000テーブル

問題点

問題説明
管理の複雑化テーブル数だけ DDL を管理
クエリの複雑化センサー間集計に複数テーブルの結合が必要
メタデータの増加システムカタログの負荷
新センサー追加毎回 DDL が必要

適切なパターン

センサー名を PRIMARY KEY とする1つの TAG テーブルに、すべてのセンサーデータを保存します。

これは同じ列構造・権限・保持ポリシーを共有するセンサー群に適用します。単位、スキーマ、 アクセス権限、保持期間を個別管理するなら、テーブルを分けるのが適切な場合もあります。 センサー数そのものを分割基準にしないことが重要です。

-- 適切: すべての温度センサーを1テーブルに
CREATE TAG TABLE temperature_sensor (
    name   VARCHAR(128) PRIMARY KEY,
    time   DATETIME     BASETIME,
    value  DOUBLE
);

-- すべてのセンサーデータを同じテーブルに入力
INSERT INTO temperature_sensor VALUES ('TEMP-01', NOW, 23.5);
INSERT INTO temperature_sensor VALUES ('TEMP-02', NOW, 24.1);
INSERT INTO temperature_sensor VALUES ('TEMP-10000', NOW, 22.9);

利点

  • 新センサー追加に DDL が不要。新しいタグ名で INSERT するだけ。
  • センサー間の集計が容易。
  • 運用管理対象を削減。

不適切なタイプ選択

データの特性に合わないタイプ選択の代表例です。

アンチパターン1: イベントログを TAG に保存

-- 不適切: イベントログを TAG に保存
CREATE TAG TABLE error_log_wrong (
    name   VARCHAR(256) PRIMARY KEY,  -- イベント内容がタグ名になる
    time   DATETIME     BASETIME,
    level  SHORT
);
-- 問題: イベントごとに一意な名前を付けるとタグ数が急増

適切な設計: LOG テーブルを使用します。

CREATE LOG TABLE error_log (
    level   SHORT,
    msg     VARCHAR(512),
    src     VARCHAR(128)
);

アンチパターン2: センサー値を LOG に保存

LOG にセンサー値を保存すること自体は誤りではありません。問題は、タグ別の計測時刻集計が必要なのに、 実際の計測時刻を省いて受信時刻だけを残すことです。複数項目の設備イベント検索が中心なら LOG が適する場合もあります。

-- 計測時刻が必要な要件には不十分なスキーマ
CREATE LOG TABLE sensor_wrong (
    sensor_id VARCHAR(64),
    value     DOUBLE
    -- 計測時刻がなく、サーバー受信時刻だけを自動保存
);

適切な設計: TAG テーブルを使用します。

CREATE TAG TABLE sensor_measurements (
    name  VARCHAR(64) PRIMARY KEY,
    time  DATETIME    BASETIME,
    value DOUBLE
);

アンチパターン3: 大量の履歴を LOOKUP に保存

-- 不適切: リレーショナルトランザクションが必要な注文履歴を LOOKUP に保存
CREATE LOOKUP TABLE order_history_wrong (
    order_id LONG PRIMARY KEY,
    customer VARCHAR(64)
    -- 全行・インデックスのメモリと明示的トランザクション要件を確認
);

適切な設計: TRANSACTION テーブルを使用します。

CREATE TRANSACTION TABLE order_history (
    order_id  LONG,
    customer  VARCHAR(64),
    item_id   INTEGER,
    amount    DOUBLE,
    status    VARCHAR(16)
);
-- UPDATE/DELETE/SELECT をすべてサポート
UPDATE order_history SET status = 'SHIPPED' WHERE order_id = 1001;

アンチパターン4: 時系列を TRANSACTION に保存

TRANSACTION も時間列と Append API を使用できますが、TAG 専用の時間軸ストレージや ROLLUP はありません。 リレーショナルな変更より計測履歴の収集・集計が中心なら TAG を検討します。詳細は 時系列に TRANSACTION を誤用を参照してください。

VOLATILE を永続保存に使用

問題

永続的に保持する必要があるデータを VOLATILE に保存するパターンです。

アンチパターンの例

-- 不適切: 重要な設定を VOLATILE に保存
CREATE VOLATILE TABLE critical_config (
    key_name VARCHAR(64) PRIMARY KEY,
    value    VARCHAR(256)
);

INSERT INTO critical_config VALUES ('license_key', 'XXXX-XXXX-XXXX');
INSERT INTO critical_config VALUES ('max_connections', '1000');
-- 再起動ですべての設定が消失!

問題点

  • サーバー停止・再起動でデータが消失します。テーブル定義は保持されます。
  • プロセスが終了する障害でもメモリ内データを復旧できないため、唯一の元データを VOLATILE に保存するとデータ損失につながります。

適切なパターン

永続的に必要なデータは LOOKUP または TRANSACTION に保存します。

-- 適切: 設定は LOOKUP
CREATE LOOKUP TABLE app_config (
    key_name VARCHAR(64) PRIMARY KEY,
    value    VARCHAR(256)
);

INSERT INTO app_config VALUES ('max_connections', '1000');
-- 再起動後もデータを保持

VOLATILE の適切な用途

再起動後に再生成または破棄できるデータを保存します。クエリキャッシュだけでなく、寿命が明確な 作業状態も含められます。必ず保持する業務結果をメモリだけに保存しないでください。

適切不適切
センサー最新値キャッシュ元のトランザクションデータ
リアルタイム集計結果重要な設定値
セッションの一時状態監査ログ
ダッシュボードキャッシュユーザー情報

時系列に TRANSACTION を誤用

問題

センサー・IoT 計測のような継続的な時系列を TRANSACTION に保存するパターンです。リレーショナルな 更新が不要なら、TAG のタグ・時間軸と ROLLUP を利用できず、必要なクエリ・運用に合わない場合があります。 一方、計測登録と他の業務変更を1トランザクションにまとめるなら TRANSACTION を選ぶ理由があります。

アンチパターンの例

-- 不適切: センサー時系列を TRANSACTION に保存
CREATE TRANSACTION TABLE sensor_timeseries (
    sensor_id VARCHAR(64),
    ts        DATETIME,
    value     DOUBLE,
    unit      VARCHAR(16)
);

問題点

問題説明
入力の意味が不一致リレーショナルトランザクション不要の値にもリレーショナル書き込みを使用
時間軸がないTAG の BASETIME 検索構造を利用できない
集計機能の違いTAG 専用 ROLLUP を利用できない

適切なパターン

センサー計測は TAG に保存します。

-- 適切: TAG を使用
CREATE TAG TABLE sensor_history (
    name   VARCHAR(64) PRIMARY KEY,
    time   DATETIME    BASETIME,
    value  DOUBLE,
    unit   VARCHAR(16)
);

-- Append API の高速バッファで大量入力可能
-- 時間単位集計と TAG 専用最適化を利用可能
SELECT name, DATE_TRUNC('hour', time, 1) AS hour, AVG(value), MAX(value)
FROM sensor_history
WHERE time >= NOW - 86400000000000
GROUP BY name, hour;

TRANSACTION が適する場合

TRANSACTION は注文、在庫、設備履歴などリレーショナルな業務データに使います。時間列があっても UPDATE/DELETE が必要な業務履歴には TRANSACTION、変更せず蓄積する高頻度計測には TAG を選びます。

意味の異なる値を同じ統計で集計

スキーマが同じでも単位や行の意味が異なれば単純集計できません。累積電力量(kWh)の平均は 消費電力(kW)ではなく、区間平均の単純平均は全サンプルの平均と異なる場合があります。 NULL を0で埋めると計測失敗が正常な0になります。

型とともに単位、サンプル数、品質規則を記録します。区間統計の再集計には合計と有効件数などを 保持し、元データを削除する前に将来の分析に必要な解像度を確認します。

最終更新日