本記事では、マスタデータの操作を「動詞」を軸に分類し、その系譜をデータベースに保持する実装方法を解説します。
この設計は、通常の、追加・更新・削除、に加えて、削除からの復元に対応します。
加えて、簿記のような誠実な説明能力を持ちます。
なお本記事では冒頭から HOW の解説に入ります。概念の説明についてはリンク先をご一読ください。
※簡略化のため、すべて SQL での解説となります
※末尾に動作確認用の SQL があります
1.データベースのテーブル構造
投影テーブル(Projection / Base Table)
アプリケーションが参照する最新の情報です。
画面表示や計算に使うのはこのテーブルです。
room_id、room_name の項目を持つ一般的な「マスタ」です。
(その他の項目は管理項目)
削除時は、投影テーブルからは物理削除します。
create table master_room (
room_id varchar(16) not null,
revision int not null,
updater varchar(15) not null,
update_time datetime not null,
room_name varchar(100) not null,
primary key (room_id)
);
- 説明のため最小構成の項目です。実運用ではデータ項目(属性)は増やしてください
- データ型は要件に合わせてご調整ください
履歴テーブル(History / Event Store)
業務で発生したすべての出来事を記録します
insertのみ不変(Immutable)な記録です。
そして、ここでは「event_verb = 何が起きたか(動詞)」が主役となります。
削除時は、履歴テーブルに「削除」のデータを追加(insert)します。
create table master_room_history (
room_id varchar(16) not null,
revision int not null,
updater varchar(15) not null,
update_time datetime not null,
event_verb_type tinyint not null,
parent_rev int not null,
merge_parent int not null,
room_name varchar(100) not null,
primary key (room_id, revision)
);
退避テーブル(Cold Store)
履歴テーブルは運用を続けると肥大化します。
一定期間で、この退避テーブルに移動させることで、システムの処理速度の低下を回避します。
create table master_room_archive (
room_id varchar(16) not null,
revision int not null,
updater varchar(15) not null,
update_time datetime not null,
event_verb_type tinyint not null,
parent_rev int not null,
merge_parent int not null,
room_name varchar(100) not null,
primary key (room_id, revision)
);
動詞マスタ
マスタを変化させる動作を、これらの「動詞」で表現します。
すべての履歴は動詞と共に存在します。
create table system_event_type (
event_type_id tinyint not null,
event_name varchar(20) not null,
event_name_ja varchar(10) not null, -- 日本語名(数文字)を追加
is_terminal bit not null, -- 1: 投影から消える(無の状態), 0: 投影に残る(有の状態)
is_origin bit not null, -- 1: 系譜の起点(init)となる
description varchar(100),
constraint pk_system_event_type primary key (event_type_id)
);
insert into system_event_type (event_type_id, event_name, event_name_ja, is_terminal, is_origin, description) values
( 1,'init' ,'発生', 0, 1, '実体の新規登録(または削除からの復活)'),
( 2,'revise' ,'更新', 0, 0, '既存リビジョンの属性変更'),
( 9,'terminate','終了', 1, 0, '実体の終了(投影テーブルからの削除)'),
(11,'revert' ,'復元', 0, 0, '過去リビジョンを最新として復元'),
(31,'baseline' ,'締切', 0, 0, '履歴アーカイブのための監査用チェックポイント');
2.マスタデータのライフサイクル・シナリオ
本記事では、以下のシナリオに沿って、処理と実行結果を解説します。
冒頭のシンプルなデータ構造と、本記事の末尾に掲載したロジックが、どのようにデータを書き換えていくかを追体験いただけます。
| フェーズ | タイトル | 主な使用動詞 | 見どころ |
|---|---|---|---|
| 1 | データ登録と変更 |
init → revise
|
expected_revision による排他制御。 |
| 2 | 削除とUndo |
terminate → revert
|
削除したマスタを「過去の版」から復旧する。 |
| 3 | 履歴の肥大化 | baseline |
過去の履歴をアーカイブへ送り出し、新たな起点から再出発する。 |
※ 具体的な処理手順は末尾に記載しています。
実行スクリプトはストアドプロシージャの形式です。
ストアドの実行サンプルは、お使いの言語のインターフェース相当と捉えてください。
本記事では、複雑さを排除するため同期的に投影を更新します。イベントソーシング的な振る舞いが必要な場合など、要件に合わせて読み替えてください。
フェーズ 1:データ登録と変更
新規追加および更新処理は、以下の要領で実行します。
実行スクリプト
-- @expected_revision = 0 は新規追加
exec [dbo].[up_master_room_upsert]
@room_id = 'RM335',
@room_name = 'Room 335',
@updater = 'alice',
@expected_revision = 0;
-- @expected_revision > 0 は変更
exec [dbo].[up_master_room_upsert]
@room_id = 'RM335',
@room_name = 'Larry''s Studio',
@updater = 'bob',
@expected_revision = 1;
【データ実例:フェーズ 1 終了時】
履歴テーブル(仕訳帳):master_room_history
| room_id | revision | room_name | parent_rev | merge_parent | event_verb | updater |
|---|---|---|---|---|---|---|
| RM335 | 1 | Room 335 | 0 | 0 | init |
alice |
| RM335 | 2 | Larry's Studio | 1 | 0 | revise |
bob |
処理を実行した結果、履歴テーブルには以下が記録されます。
-
@expected_revision = 0を条件に、最初の記録revision 1が生成されます -
@expected_revision = 1を指定することでparent_rev = 1を親に持つrevision 2が積みあがります
投影テーブル(元帳):master_room
アプリケーションはこちらのテーブルを参照します。
論理削除などでありがちな、検索条件などは不要です。「テーブルに存在する=有効なデータ」です
| room_id | room_name | revision | updater |
|---|---|---|---|
| RM335 | Larry's Studio | 2 | bob |
フェーズ 2:削除とUndo
マスタを削除する場合は、以下のように実行します。
実行スクリプト
exec [dbo].[up_master_room_terminate]
@room_id = 'RM335',
@updater = 'bob',
@expected_revision = 2;
【データ実例:フェーズ 2 削除後】
削除後の履歴テーブルには、revision 3 が削除の動詞(event_verb = terminate)で記録されます。
履歴テーブル(仕訳帳):master_room_history
| room_id | revision | room_name | parent_rev | merge_parent | event_verb | updater |
|---|---|---|---|---|---|---|
| RM335 | 1 | Room 335 | 0 | 0 | init |
alice |
| RM335 | 2 | Larry's Studio | 1 | 0 | revise |
bob |
| RM335 | 3 | Larry's Studio | 2 | 0 | terminate |
bob |
投影テーブル(元帳):master_room
(このタイミングで行は存在しません)
削除後のユースケース
その後、誤操作に気づいた担当者がリビジョン 2 の状態へ復旧させたいと考えます。
このような復旧(Undo)操作は、以下の要領で実行します。
実行スクリプト
exec [dbo].[up_master_room_revert]
@room_id = 'RM335',
@target_revision = 2,
@expected_revision = 3,
@updater = 'admin';
(※実際は履歴検索UIによる履歴の選択が必要です。しかしここでは簡略化のため UI 関連は省き、操作の解説に集中します)
-
@expected_revision = 3(現在の削除状態)に対して、 - 復旧元となる
@target_revision = 2を指定して実行します
【データ実例:フェーズ 2 終了時】
実行の結果、履歴テーブルには revision 4 が記録されます。
-
parent_revに 3 を記録することで、データの系譜を維持します -
merge_parentに 2 を記録することで、コピー元の情報を残します
履歴テーブル(仕訳帳):master_room_history
| room_id | revision | room_name | parent_rev | merge_parent | event_verb | updater |
|---|---|---|---|---|---|---|
| RM335 | 1 | Room 335 | 0 | 0 | init |
alice |
| RM335 | 2 | Larry's Studio | 1 | 0 | revise |
bob |
| RM335 | 3 | Larry's Studio | 2 | 0 | terminate |
bob |
| RM335 | 4 | Larry's Studio | 3 | 2 | revert |
admin |
投影テーブル(元帳):master_room
アプリケーションはこちらのテーブルを参照します。
(論理削除の実装でありがちな特殊な検索条件などは不要です)
| room_id | room_name | revision | updater |
|---|---|---|---|
| RM335 | Larry's Studio | 4 | admin |
以上で、追加・更新・削除+復元に対応できました。
次は、履歴が大量になった場合の解説です。
フェーズ 4: 履歴の肥大化対策
例えば年度末や、データ保存期間を迎えたタイミングで、履歴を整理するユースケースです。
履歴の整理を宣言(baseline)してから、退避テーブルへ移動(archive)します。
実行スクリプト
exec [dbo].[up_master_room_baseline]
@room_id = 'RM335',
@expected_revision = 4,
@updater = 'auditor';
【データ実例:フェーズ 4 baseline後】
履歴テーブル(仕訳帳):master_room_history
| room_id | revision | room_name | parent_rev | merge_parent | event_verb | updater |
|---|---|---|---|---|---|---|
| ... | ... | ... | ... | ... | ... | ... |
| RM335 | 4 | Larry's Studio | 3 | 2 | revert |
admin |
| RM335 | 5 | Larry's Studio | 4 | 0 | baseline |
auditor |
投影テーブル(元帳):master_room
内容は変わりませんが、版番号(revision)が「締め後の番号」に更新されます。
これ以前の履歴をアーカイブへ逃がすための「目印」になります。
| room_id | room_name | revision | updater |
|---|---|---|---|
| RM335 | Larry's Studi | 4 | auditor |
実行スクリプト
baseline のリビジョンを指定してアーカイブを実行します。
exec [dbo].[up_master_room_history_archive]
@room_id = 'RM335',
@keep_baseline_rev = 5;
【データ実例:フェーズ 4 終了時】
履歴テーブル(仕訳帳):master_room_history
| room_id | revision | room_name | parent_rev | merge_parent | event_verb | updater |
|---|---|---|---|---|---|---|
| RM335 | 5 | Larry's Studio | 4 | 0 | baseline |
auditor |
履歴テーブルは リビジョン5のみ残ります。
そして master_room_archive に 5未満の履歴が移動します。
(実務では一括処理できると運用が容易になりますが、説明のため room_id ごとの処理としています。)
3.全フェーズ完走:お疲れ様でした
これでマスタデータのライフサイクルが完結しました。
- 発生 (
init) - 適応 (
revise) - 充足 (
terminate) - 回帰 (
revert) - 凝縮 (
baseline)
これらの系譜を積み上げたことで、roomマスタは **『過去の任意の時点における状態を再構築し、その変更意図を第三者に証明する』**ことができるようになります。
これにより、ユーザー視点でも、データの変更の系譜が確認できるようになります。
また、保守運用フェーズにおける保守担当(開発者)の工数削減に寄与します。
おわりに ── 誠実な履歴がシステムを長寿にする
マスタデータのライフサイクルを「動詞」で定義し直すことで、マスタデータ自身が今の状態にいたる経緯を語ることができます。
とはいえ、この設計を導入することは、開発初期に「手間」を持ち込みます。
しかし、その対価として得られるのは、データ自身が系譜を語ることによる調査負荷の削減、障害発生時の復旧力です。
あなたのシステムが、単に「動く」だけでなく、数年後の自分や後任者に対して「誠実」であり続けるために。この系譜型マスタ設計が、その一助となれば幸いです。
X.実装例
サンプルはSQLServer向けストアドプロシージャです。
適宜、特定のDB向けSQL、またはコードに変換してご利用ください。
※各ストアドの @expected_revision により楽観ロックを実現しています。
1. init/revise : [up_master_room_upsert]
create procedure up_master_room_upsert (
@room_id varchar(16),
@room_name varchar(100),
@updater varchar(15),
@expected_revision int
)
as
begin
set nocount on;
-- 入力値の有無を点検する
if nullif(@room_id, '') is null raiserror('[51001] missing room_id', 16, 1);
if nullif(@room_name, '') is null raiserror('[51001] missing room_name', 16, 1);
if nullif(@updater, '') is null raiserror('[51001] missing updater', 16, 1);
if @expected_revision is null raiserror('[51001] missing expected_revision', 16, 1);
if @@error <> 0 return;
-- 現在の最新リビジョンを読み取る
-- updlock, holdlock により、この行への他者の書き込みを完了まで待機させる
declare @cur_rev int;
select @cur_rev = isnull(max(revision), 0)
from master_room_history with (updlock, holdlock)
where room_id = @room_id;
-- 現在の論理状態(終端か否か)を特定する
declare @is_term bit = isnull((
select s.is_terminal from master_room_history h
join system_event_type s on h.event_verb_type = s.event_type_id
where h.room_id = @room_id and h.revision = @cur_rev
), 1);
-- 期待する番号と実際の番号を突き合わせ、不一致なら処理を止める(楽観的ロック)
if (@expected_revision = 0 and @is_term = 0) or (@expected_revision <> 0 and @cur_rev <> @expected_revision)
begin
declare @msg nvarchar(255) = formatmessage('[50001] conflict: room_id=%s, expected=%d, actual=%d',
@room_id, @expected_revision, @cur_rev);
raiserror(@msg, 16, 1); return;
end
-- 新しいリビジョン番号とイベント種別を決定する
declare @v_init tinyint = 1, @v_rev tinyint = 2, @now datetime = getdate();
declare @nxt_rev int = @cur_rev + 1;
declare @v_type tinyint = case when @is_term = 1 then @v_init else @v_rev end;
-- 履歴テーブルへ新しい事実を挿入する
insert into master_room_history (
room_id, revision, updater, update_time, event_verb_type,
parent_rev, merge_parent, branch_id, room_name
)
values (
@room_id, @nxt_rev, @updater, @now, @v_type,
@cur_rev, 0, 0, @room_name
);
-- 投影テーブル(現在の状態)を最新化する
update master_room set
revision = @nxt_rev, updater = @updater, update_time = @now, room_name = @room_name
where room_id = @room_id;
if @@rowcount = 0
insert into master_room (room_id, revision, updater, update_time, room_name)
values (@room_id, @nxt_rev, @updater, @now, @room_name);
end
2. terminate : [dbo].[up_master_room_terminate]
create procedure up_master_room_terminate (
@room_id varchar(16),
@updater varchar(15),
@expected_revision int
)
as
begin
set nocount on;
-- 入力値を点検する(終了処理のため、期待するリビジョンは1以上が必要)
if nullif(@room_id, '') is null raiserror('[51001] missing room_id', 16, 1);
if nullif(@updater, '') is null raiserror('[51001] missing updater', 16, 1);
if isnull(@expected_revision, 0) = 0 raiserror('[51001] expected_revision must be > 0 for terminate', 16, 1);
if @@error <> 0 return;
-- 現在の最新リビジョンを読み取る
declare @cur_rev int;
select @cur_rev = isnull(max(revision), 0)
from master_room_history with (updlock, holdlock)
where room_id = @room_id;
-- 競合を検知する(既に他者が更新している、または既に削除されている場合)
if @cur_rev <> @expected_revision
begin
declare @msg nvarchar(255) = formatmessage('[50001] conflict: room_id=%s, expected=%d, actual=%d',
@room_id, @expected_revision, @cur_rev);
raiserror(@msg, 16, 1); return;
end
-- 投影テーブル(現在の状態)からレコードを抹消する
delete from master_room where room_id = @room_id;
-- 履歴テーブルへ「終了」の事実を書き込む
-- 直前のリビジョンの内容をコピーし、その断面のまま終了したことを記録する
declare @v_term tinyint = 9, @now datetime = getdate();
declare @nxt_rev int = @cur_rev + 1;
insert into master_room_history (
room_id, revision, updater, update_time, event_verb_type,
parent_rev, merge_parent, branch_id, room_name
)
select
room_id, @nxt_rev, @updater, @now, @v_term,
@cur_rev, 0, 0, room_name
from master_room_history
where room_id = @room_id and revision = @cur_rev;
end
3. revert : [up_master_room_revert]
create procedure up_master_room_revert (
@room_id varchar(16),
@target_revision int, -- 復元の出典とする過去のリビジョン
@expected_revision int, -- 現在見ているリビジョン(楽観的ロック)
@updater varchar(15)
)
as
begin
set nocount on;
-- 入力値を点検する
if nullif(@room_id, '') is null raiserror('[51001] missing room_id', 16, 1);
if isnull(@target_revision, 0) = 0 raiserror('[51001] target_revision required', 16, 1);
if @expected_revision is null raiserror('[51001] expected_revision required', 16, 1);
if @expected_revision = 0 raiserror('[51001] revert requires an explicit current revision', 16, 1);
if nullif(@updater, '') is null raiserror('[51001] missing updater', 16, 1);
if @@error <> 0 return;
-- 現在のリビジョンを読み取り、他セッションを待機させる
declare @cur_rev int;
select @cur_rev = isnull(max(revision), 0)
from master_room_history with (updlock, holdlock)
where room_id = @room_id;
-- 競合検知(画面を開いた後に誰かが更新・削除していないか)
if @cur_rev <> @expected_revision
begin
declare @msg_conflict nvarchar(255) = formatmessage('[50001] conflict: room_id=%s, expected=%d, actual=%d',
@room_id, @expected_revision, @cur_rev);
raiserror(@msg_conflict, 16, 1); return;
end
-- 出典となるリビジョンが存在するか確認する
if not exists (select 1 from master_room_history where room_id = @room_id and revision = @target_revision)
begin
raiserror('[50002] target revision not found', 16, 1); return;
end
-- 新しいリビジョン番号を確定する
declare @v_revert tinyint = 11, @now datetime = getdate();
declare @nxt_rev int = @cur_rev + 1;
-- 投影テーブル(現在の姿)を復元データで書き換える
-- 既に削除(terminate)されて行がない場合は insert で復活させる
update m set
revision = @nxt_rev, updater = @updater, update_time = @now, room_name = h.room_name
from master_room m, master_room_history h
where m.room_id = @room_id and h.room_id = @room_id and h.revision = @target_revision;
if @@rowcount = 0
insert into master_room (room_id, revision, updater, update_time, room_name)
select room_id, @nxt_rev, @updater, @now, room_name
from master_room_history
where room_id = @room_id and revision = @target_revision;
-- 履歴テーブルへ「復元」の事実を記録する
-- 出典元(target_revision)を merge_parent に刻む
insert into master_room_history (
room_id, revision, updater, update_time, event_verb_type,
parent_rev, merge_parent, branch_id, room_name
)
select
room_id, @nxt_rev, @updater, @now, @v_revert,
@cur_rev, @target_revision, 0, room_name
from master_room_history
where room_id = @room_id and revision = @target_revision;
end
4. baseline : [up_master_room_baseline]
create procedure up_master_room_baseline (
@room_id varchar(16),
@expected_revision int,
@updater varchar(15)
)
as
begin
set nocount on;
-- 入力値の存在を確認する
if nullif(@room_id, '') is null raiserror('[51001] missing room_id', 16, 1);
if @expected_revision is null raiserror('[51001] expected_revision required', 16, 1);
if nullif(@updater, '') is null raiserror('[51001] missing updater', 16, 1);
if @@error <> 0 return;
-- 現在のリビジョンを読み取り、他セッションの割り込みを制限する
declare @cur_rev int;
select @cur_rev = isnull(max(revision), 0)
from master_room_history with (updlock, holdlock)
where room_id = @room_id;
-- 競合検知(画面上の番号と最新の番号が一致するか)
if @cur_rev <> @expected_revision
begin
declare @msg nvarchar(255) = formatmessage('[50001] conflict: room_id=%s, expected=%d, actual=%d',
@room_id, @expected_revision, @cur_rev);
raiserror(@msg, 16, 1); return;
end
-- 新しいリビジョン番号(締切リビジョン)を確定する
declare @v_base tinyint = 31, @now datetime = getdate();
declare @nxt_rev int = @cur_rev + 1;
-- 履歴テーブルへ「締切」の事実を書き込む
-- 直前の属性(room_name)をそのまま引き継ぎ、イベント種別だけを更新する
insert into master_room_history (
room_id, revision, updater, update_time, event_verb_type,
parent_rev, merge_parent, branch_id, room_name
)
select
room_id, @nxt_rev, @updater, @now, @v_base,
@cur_rev, 0, 0, room_name
from master_room_history
where room_id = @room_id and revision = @cur_rev;
-- 投影テーブル(現在の姿)のリビジョン情報を最新化する
-- 部屋名に変更はないため、管理項目のみを更新
update master_room set
revision = @nxt_rev, updater = @updater, update_time = @now
where room_id = @room_id;
end
5. archive : [up_master_room_archive]
create procedure up_master_room_archive (
@room_id varchar(16),
@keep_baseline_rev int -- このリビジョンを「履歴の起点」として残し、これより古いものを移動する
)
as
begin
set nocount on;
-- 入力値の存在を確認する
if nullif(@room_id, '') is null raiserror('[51001] missing room_id', 16, 1);
if isnull(@keep_baseline_rev, 0) = 0 raiserror('[51001] keep_baseline_rev required', 16, 1);
if @@error <> 0 return;
-- 最新状況と、指定されたリビジョンの種類を確認する
-- 処理中に他者が更新できないようロックをかける
declare @max_rev int, @target_event tinyint;
select
@max_rev = isnull(max(revision), 0),
@target_event = isnull(max(case when revision = @keep_baseline_rev then event_verb_type end), 0)
from master_room_history with (updlock, holdlock)
where room_id = @room_id;
-- 指定されたリビジョンが存在するか点検する
if @target_event = 0
begin
raiserror('[50002] target revision not found', 16, 1); return;
end
-- 最新リビジョンはアーカイブできない(最低1行は履歴に残す必要がある)
if @keep_baseline_rev > @max_rev
begin
declare @msg_range nvarchar(255) = formatmessage('[50003] baseline out of range: room_id=%s, max_rev=%d',
@room_id, @max_rev);
raiserror(@msg_range, 16, 1); return;
end
-- 指定された地点が「締切(baseline: 31)」であるか確認する
-- 任意の地点で切断すると系譜が追えなくなるため、明示的なチェックポイントを必須とする
declare @v_base tinyint = 31;
if @target_event <> @v_base
begin
declare @msg_seal nvarchar(255) = formatmessage('[50101] sealed: revision %d is not a baseline.', @keep_baseline_rev);
raiserror(@msg_seal, 16, 1); return;
end
-- 1. アーカイブテーブルへ古い行をコピーする
insert into master_room_archive (
room_id, revision, updater, update_time,
event_verb_type, parent_rev, merge_parent, branch_id, room_name
)
select
room_id, revision, updater, update_time,
event_verb_type, parent_rev, merge_parent, branch_id, room_name
from master_room_history
where room_id = @room_id and revision < @keep_baseline_rev;
-- 2. 履歴テーブル(ホットストア)からコピー済みの古い行を消去する
delete from master_room_history
where room_id = @room_id and revision < @keep_baseline_rev;
end
6. 動作確認用
begin tran t1
exec [dbo].[up_master_room_upsert]
@room_id = 'RM335',
@room_name = 'Room 335',
@updater = 'alice',
@expected_revision = 0;
exec [dbo].[up_master_room_upsert]
@room_id = 'RM335',
@room_name = 'Larry''s Studio',
@updater = 'bob',
@expected_revision = 1;
exec [dbo].[up_master_room_terminate]
@room_id = 'RM335',
@updater = 'bob',
@expected_revision = 2;
exec [dbo].[up_master_room_revert]
@room_id = 'RM335',
@target_revision = 2,
@expected_revision = 3,
@updater = 'admin';
exec [dbo].[up_master_room_baseline]
@room_id = 'RM335',
@expected_revision = 4,
@updater = 'auditor';
exec [dbo].[up_master_room_archive]
@room_id = 'RM335',
@keep_baseline_rev = 5
select * from master_room
select * from master_room_history
select * from master_room_archive
rollback tran t1
--commit tran t1
X.エラー体系
本筋ではないので触れませんでしたが、サンプルにおけるエラー体系は以下の方針になります。
50000番台:系譜型モデル 標準エラー体系
-
[50001] conflict
- 「あなたが編集を開始してから保存するまでの間に、世界(本流)は一歩先に進みました」という事実を伝えます。
upsertやmergeにおいて、意図しない上書きによるデータ破壊を防ぎます
- 「あなたが編集を開始してから保存するまでの間に、世界(本流)は一歩先に進みました」という事実を伝えます。
-
[50002] source missing
-
branchやrevertにおいて、「存在しない過去」を起点にすることを許しません。すべての変化には明確な「親(parent_rev)」が必要であるという、系譜型モデルの根幹を守ります
-
-
[50003] out of range
-
archiveにおける未来のリビジョン指定や、branchにおける識別子の重複を検知します。これは、系譜が一本道(または管理された分岐)であることを担保するための制約です
-
-
[50101] sealed
-
archiveにおいて、「baseline(結び目)が打たれていない場所では歴史を切り離せない」という規律を強制します。これは、運用における「不注意なデータ喪失」を防ぐためのガードレールです
-
-
[51001] invalid format
- 「型は合っているが、事実として記録するには値しない(更新者が不明、UIDのバイト長不足など)」場合に発動します。不純なデータが系譜に混入するのを防ぎます