オンライン再定義(インデックス追加)

 

更新待ち時間結果

MySQL
pt-online-schema-change -> 8秒
ALGORITHM=INPLACE -> ほぼ無し
ALGORITHM=COPY -> 5秒(インデックス作成時間)

Oracle
dbms_redefinition -> 0.5秒
onlineあり -> ほぼ無し
onlineなし -> 8秒(インデックス作成時間)

PostgreSQL
concurrentlyあり -> 3秒
concurrentlyなし -> 6秒(インデックス作成時間)

SQL Server
online=on -> 1秒
online=off -> 5秒(インデックス作成時間)

 

(8.0.29)

pt-online-schema-change

https://www.s-style.co.jp/products/percona/toolkit/pt-online-schema-change/

 

-- pt-online-schema-changeのインストール

yum install -y https://www.percona.com/downloads/percona-toolkit/3.0.2/binary/redhat/7/x86_64/percona-toolkit-3.0.2-1.el7.x86_64.rpm

pt-online-schema-change --version

 

-- テーブル作成

drop table tab1;
create table tab1(col1 bigint ,col2 bigint ,col3 datetime(6) );
alter table tab1 add constraint tab1pk primary key(col1);


-- 初期データセット
drop procedure proc1;

delimiter //
create procedure proc1(in x bigint)
begin
  declare i bigint;
  set i = 0;
  start transaction;
  while i < x do
    set i = i + 1;
    insert into tab1 values(i,i,current_timestamp(6) );
  end while;
  commit;
end
//
delimiter ;

call proc1(1000000);


-- 継続的にデータ追加と更新
drop procedure proc2;

delimiter //
create procedure proc2()
begin
  declare i bigint;
  set i = (select max(col1) from tab1);
  start transaction;
  while true do
    set i = i + 1;
    insert into tab1 values(i,i,current_timestamp(6) );
    update tab1 set col2 = floor(rand() * 1000000)+1 where col1 = floor(rand() * 1000000)+1;
    commit;
    do sleep(0.01);
    start transaction;
  end while;
  commit;
end
//
delimiter ;


call proc2();


-- ■[1]オンライン再定義


mysql -e "select count(*) from tab1;" test

pt-online-schema-change --execute  --alter "add index ind1(col2)" h=localhost,D=test,t=tab1

mysql -e "select count(*) from tab1;" test


No slaves found.  See --recursion-method if host mmm066 has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
  analyze_table, 10, 1
  copy_rows, 10, 0.25
  create_triggers, 10, 1
  drop_triggers, 10, 1
  swap_tables, 10, 1
  update_foreign_keys, 10, 1
Altering `test`.`tab1`...
Creating new table...
Created new table test._tab1_new OK.
Altering new table...
Altered `test`.`_tab1_new` OK.
2022-09-03T17:02:30 Creating triggers...
2022-09-03T17:02:30 Created triggers OK.
2022-09-03T17:02:30 Copying approximately 997442 rows...
Copying `test`.`tab1`:  97% 00:00 remain
2022-09-03T17:03:00 Copied rows OK.
2022-09-03T17:03:00 Analyzing new table...
2022-09-03T17:03:00 Swapping tables...
2022-09-03T17:03:00 Swapped original and new tables OK.
2022-09-03T17:03:00 Dropping old table...
2022-09-03T17:03:00 Dropped old table `test`.`_tab1_old` OK.
2022-09-03T17:03:00 Dropping triggers...
2022-09-03T17:03:00 Dropped triggers OK.

定義確認

show create table tab1;

 

更新まち時間確認


select col1,timediff(col3,lag(col3) over(order by col1) ) from tab1
where col1 > 1000001
order by timediff(col3,lag(col3) over(order by col1) ) desc 
limit 10
;


+---------+-----------------------------------------------+
| col1    | timediff(col3,lag(col3) over(order by col1) ) |
+---------+-----------------------------------------------+
| 1000055 | 00:00:08.432278                               |
| 1000053 | 00:00:00.826683                               |
| 1000054 | 00:00:00.801023                               |
| 1000052 | 00:00:00.774917                               |
| 1000014 | 00:00:00.721277                               |
| 1000056 | 00:00:00.717380                               |
| 1000049 | 00:00:00.716973                               |
| 1000080 | 00:00:00.708578                               |
| 1000051 | 00:00:00.704677                               |
| 1000048 | 00:00:00.703581                               |
+---------+-----------------------------------------------+

select * from tab1 where col1 > 1000001 order by col1;

テーブル切り替え時、8秒程度更新まちとなる

 

-- ■[2]オンラインインデックス追加

create index ind1 on tab1(col2) ALGORITHM=INPLACE;

→ (3.74 sec)

更新まち時間確認


select col1,timediff(col3,lag(col3) over(order by col1) ) from tab1
where col1 > 1000001
order by timediff(col3,lag(col3) over(order by col1) ) desc 
limit 1
;

+---------+-----------------------------------------------+
| col1    | timediff(col3,lag(col3) over(order by col1) ) |
+---------+-----------------------------------------------+
| 1000024 | 00:00:00.760979                               |
+---------+-----------------------------------------------+

更新まちほぼなし


-- ■[3]オフラインインデックス追加

create index ind1 on tab1(col2) ALGORITHM=COPY;

→ (4.79 sec)


更新まち時間確認


select col1,timediff(col3,lag(col3) over(order by col1) ) from tab1
where col1 > 1000001
order by timediff(col3,lag(col3) over(order by col1) ) desc 
limit 1
;

+---------+-----------------------------------------------+
| col1    | timediff(col3,lag(col3) over(order by col1) ) |
+---------+-----------------------------------------------+
| 1000125 | 00:00:05.301818                               |
+---------+-----------------------------------------------+

インデックス作成中更新まちとなる

 

 

 

(19c)
https://qiita.com/ora_gonsuke777/items/5754c18a6609ec295d8f

 

-- テーブル作成

drop table tab1 purge;
create table tab1(col1 int ,col2 int ,col3 timestamp);
alter table tab1 add constraint tab1pk primary key(col1);


-- 初期データセット
declare
begin
for i in 1..10000000 loop
  insert into tab1 values(i,i,systimestamp);
end loop;
end;
/

commit;


-- 継続的にデータ追加と更新

declare
i number;
begin
select max(col1) into i from tab1;
while true loop
  i := i+1;
  insert into tab1 values(i,i,systimestamp);
  update tab1 set col2 = floor(dbms_random.value(1, 10000000) ) where col1 = floor(dbms_random.value(1, 10000000) );
  commit;
  dbms_lock.sleep(0.01);
end loop;
end;
/


-- ■[1]オンライン再定義

-- 1. 仮表作成

drop table tab1_new purge;
create table tab1_new(col1 int ,col2 int ,col3 timestamp);
alter table tab1_new add constraint tab1pk_new primary key(col1);


select count(*) from tab1;
select count(*) from tab1_new;

set time on
set timing on

-- 2. オンライン再定義可能かチェック

exec dbms_redefinition.can_redef_table(user, 'TAB1', dbms_redefinition.cons_use_pk);


-- 3. オンライン再定義を開始

exec dbms_redefinition.start_redef_table(user, 'TAB1', 'TAB1_NEW');

→ 経過: 00:00:22.15

-- 4. 仮表にインデックス追加

create index ind1 on tab1_new(col2);

→ 経過: 00:00:06.38

-- 5. 対象表と仮表の依存オブジェクト関連付け

begin
  dbms_redefinition.register_dependent_object(
      user                         -- uname
    , 'TAB1'                       -- orig_table
    , 'TAB1_NEW'                   -- int_table
    , dbms_redefinition.cons_index -- dep_type
    , user                         -- dep_owner
    , 'TAB1PK'                     -- dep_orig_name
    , 'TAB1PK_NEW'                 -- dep_int_name
  );
end;
/

begin
  dbms_redefinition.register_dependent_object(
      user                              -- uname
    , 'TAB1'                            -- orig_table
    , 'TAB1_NEW'                        -- int_table
    , dbms_redefinition.cons_constraint -- dep_type
    , user                              -- dep_owner
    , 'TAB1PK'                          -- dep_orig_name
    , 'TAB1PK_NEW'                      -- dep_int_name
  );
end;
/

 

-- 6. オンライン再定義をキャンセル

exec dbms_redefinition.abort_redef_table(user, 'TAB1', 'TAB1_NEW');

→ 経過: 00:00:00.39


-- 7. 対象表と仮表の同期
select count(*) from tab1;
select count(*) from tab1_new;

exec dbms_redefinition.sync_interim_table(user, 'TAB1', 'TAB1_NEW');

→ 経過: 00:00:00.51


-- 8. オンライン再定義を完了
select count(*) from tab1;
select count(*) from tab1_new;


exec dbms_redefinition.finish_redef_table(user, 'TAB1', 'TAB1_NEW');


→ 経過: 00:00:03.73

このプロシージャの実行時は、元の表が一時的にロックされます。

 

 

-- 9. 更新まち時間確認


select col1, col3 - lag(col3) over(order by col1)  from tab1
where col1 > 10000001
order by col3 - lag(col3) over(order by col1) desc nulls last
fetch first 10 rows only
;

      COL1 COL3-LAG(COL3)OVER(ORDERBYCOL1)
---------- ---------------------------------------------------------------------------
  10001233 +000000000 00:00:02.801778
  10001170 +000000000 00:00:01.068741
  10001215 +000000000 00:00:00.774017
  10002173 +000000000 00:00:00.476989
  10008749 +000000000 00:00:00.467898  -> finish_redef_table実行時の更新まち★
  10001139 +000000000 00:00:00.443043
  10001259 +000000000 00:00:00.368031
  10005137 +000000000 00:00:00.311968
  10001280 +000000000 00:00:00.304046
  10001116 +000000000 00:00:00.296901
  

テーブル切り替え時の更新まちは1秒以内


-- 10. 定義確認


select * from user_tables where table_name in ('TAB1','TAB1_NEW');

select * from user_constraints where table_name in ('TAB1','TAB1_NEW');

select * from user_indexes where table_name in ('TAB1','TAB1_NEW');

 

-- ■[2]オンラインインデックス追加


create index ind1 on tab1(col2) online;

→ 経過: 00:00:08.52

更新まち時間確認
select col1, col3 - lag(col3) over(order by col1)  from tab1
where col1 > 10000001
order by col3 - lag(col3) over(order by col1) desc nulls last
fetch first 1 rows only
;

      COL1 COL3-LAG(COL3)OVER(ORDERBYCOL1)
---------- ---------------------------------------------------------------------------
  10000410 +000000000 00:00:00.034991


更新まちほぼなし

 


-- ■[3]オフラインインデックス追加

create index ind1 on tab1(col2);

→ 経過: 00:00:08.25

更新まち時間確認
select col1, col3 - lag(col3) over(order by col1)  from tab1
where col1 > 10000001
order by col3 - lag(col3) over(order by col1) desc nulls last
fetch first 1 rows only
;

      COL1 COL3-LAG(COL3)OVER(ORDERBYCOL1)
---------- ---------------------------------------------------------------------------
  10007704 +000000000 00:00:08.253500


インデックス作成中更新まちとなる

 

(14)
https://github.com/shayonj/pg-osc

pg-osc

pg-osc supports when a column is being added, dropped or renamed with no data loss.

インデックス追加はサポートされていない模様だが、念のため、実施してみる

Requirements
 PostgreSQL 9.6 and later
 Ruby 2.6 and later
 Database user should have permissions for TRIGGER and/or a SUPERUSER

-- rubyのインストール

dnf update
dnf -y install git bzip2 gcc gcc-c++ openssl-devel readline-devel zlib-devel
dnf -y install epel-release
dnf -y install nodejs
git clone https://github.com/rbenv/rbenv.git /usr/local/rbenv
git clone https://github.com/rbenv/ruby-build.git /usr/local/rbenv/plugins/ruby-build

vim /etc/profile.d/rbenv.sh

export RBENV_ROOT=/usr/local/rbenv
export PATH=${RBENV_ROOT}/bin:$PATH
eval "$(rbenv init --no-rehash -)"

source /etc/profile.d/rbenv.sh

visudo

Defaults env_keep += "RBENV_ROOT"
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/rbenv/bin:/usr/local/rbenv/shims

rbenv install -l
rbenv install 3.1.2
rbenv global 3.1.2

rbenv rehash
ruby --version


-- pg-oscのインストール


su - postgres

vim Gemfile

source 'https://rubygems.org'
gem 'pg_online_schema_change'


export PATH=$PATH:/usr/pgsql-14/bin
bundle install

bundle info pg_online_schema_change

export PATH=$PATH:/usr/local/rbenv/versions/3.1.2/bin
pg-online-schema-change help perform


-- テーブル作成

drop table tab1;
create table tab1(col1 bigint ,col2 bigint ,col3 timestamp);
alter table tab1 add constraint tab1pk primary key(col1);


-- 初期データセット

insert into tab1 select g,g,clock_timestamp() from generate_series(1,10000000) g;


-- 継続的にデータ追加と更新

do $$
declare
i bigint;
begin
  select max(col1) into STRICT i from tab1;
  while true loop
    i := i+1;
    insert into tab1 values(i,i,clock_timestamp() );
    update tab1 set col2 = floor(random() * 10000000)+1 where col1 = floor(random() * 10000000)+1;
    commit;
    PERFORM pg_sleep(0.01);
  end loop;
end;
$$;

 

-- ■[1]オンライン再定義

export PGPASSWORD="postgres"
pg-online-schema-change perform \
--alter-statement 'alter table tab1 rename column col3 to col3_new' \
--dbname "test" \
--host "localhost" \
--username "postgres" \
--drop

→ OK
ただしインデックス名が変化する

export PGPASSWORD="postgres"
pg-online-schema-change perform \
--alter-statement 'create index ind1 on tab1(col2)' \
--dbname "test" \
--host "localhost" \
--username "postgres" \
--drop

→ エラー
handle_validations': Not a valid ALTER statement: create index ind1 on tab1(col2) (PgOnlineSchemaChange::Error)


-- ■[2]オンラインインデックス追加

\timing 1

create index concurrently ind1 on tab1(col2);

→ 時間: 12550.023 ミリ秒(00:12.550)

更新まち時間確認
select col1, col3 - lag(col3) over(order by col1)  from tab1
where col1 > 10000001
order by col3 - lag(col3) over(order by col1) desc nulls last
limit 10
;

   col1   |    ?column?
----------+-----------------
 10000022 | 00:00:02.923435
 10000023 | 00:00:02.045207
 10000026 | 00:00:01.993001
 10000027 | 00:00:01.900054
 10000008 | 00:00:01.769081
 10000014 | 00:00:01.514806
 10000024 | 00:00:01.502795
 10000009 | 00:00:01.444517
 10000025 | 00:00:01.306995
 10000028 | 00:00:01.222762

3秒程度の更新まちとなる

concurrentlyを付与してもそれなりのまちが発生する


-- ■[3]オフラインインデックス追加

\timing 1

create index ind1 on tab1(col2);

→ 時間: 5753.329 ミリ秒(00:05.753)

更新まち時間確認
select col1, col3 - lag(col3) over(order by col1)  from tab1
where col1 > 10000001
order by col3 - lag(col3) over(order by col1) desc nulls last
limit 10
;

   col1   |    ?column?
----------+-----------------
 10000005 | 00:00:06.484222
 10000011 | 00:00:00.921378
 10000004 | 00:00:00.915249
 10000006 | 00:00:00.913362
 10000003 | 00:00:00.899362
 10000012 | 00:00:00.895983
 10000008 | 00:00:00.893974
 10000009 | 00:00:00.893885
 10000010 | 00:00:00.893478
 10000013 | 00:00:00.89159
 
6秒程度の更新まちとなる

 

select col1, col3 - lag(col3) over(order by col1)  from tab1
where col1 > 10000001
order by col1
;

 

(2019)


-- テーブル作成

drop table tab1;
create table tab1(col1 bigint not null,col2 bigint ,col3 datetime2);
alter table tab1 add constraint tab1pk primary key(col1);


-- 初期データセット

set nocount on
declare @i bigint;
set @i = 1;
begin transaction;
while @i <= 10000000
begin
  insert into tab1 values(@i,@i,getdate() );
  set @i = @i + 1;
end
commit;


-- 継続的にデータ追加と更新

set nocount on
declare @i bigint;
set @i = (select max(col1) from tab1);
begin transaction;
while 1 = 1
begin
  set @i = @i + 1;
  insert into tab1 values(@i,@i,getdate() );
  update tab1 set col2 = floor(rand() * 10000000)+1 where col1 = floor(rand() * 10000000)+1;
  commit;
  waitfor delay '00:00:00.01';
  begin transaction;
end
commit;

 


-- ■[1]オンライン再定義
調べた限り、オンライン再定義のツールなし

 

-- ■[2]オンラインインデックス追加

set statistics time on

create index ind2 on tab1(col2) with ( online=on );


→ 経過時間 = 12408 ミリ秒

更新まち時間確認
select top 10 
col1,datediff(millisecond, lag(col3) over(order by col1), col3 )  from tab1
where col1 > 10000001
order by datediff(millisecond, lag(col3) over(order by col1), col3 )  desc

10000374    1050
10000462    364
10000373    227
10000381    203
10000505    154
10000419    153
10000392    130
10000418    130
10000522    117
10000460    107

online=on でも1秒程度のまちは発生


-- ■[3]オフラインインデックス追加

set statistics time on

create index ind2 on tab1(col2) with ( online=off );

→ 経過時間 = 4746 ミリ秒


更新まち時間確認
select top 10 
col1,datediff(millisecond, lag(col3) over(order by col1), col3 )  from tab1
where col1 > 10000001
order by datediff(millisecond, lag(col3) over(order by col1), col3 )  desc

10000459    4736
10000117    37
10000082    30
10000375    30
10000434    30
10000464    30
10000481    30
10000512    30
10000539    30
10000711    30
 
インデックス作成中更新まちとなる