[groonga-dev,03802] Mroonga で timestamp 型の index が破損するパターンがある(ストレージモード)

Zurück zum Archiv-Index

各務 洋 kagam****@outwa*****
2015年 12月 24日 (木) 19:37:27 JST


お世話になります、本日サンタではなくベイマックスと言われた各務です。

なんとか TIMESTAMP 型のインデックスが破損するパターンを掴めたようですので
ご報告させていただきます。

条件:
Mroonga のレコードを削除する事。
InnoDB と Mroonga のレコード操作が同一のトランザクション内で行われている事。
同様のトランザクション処理がいくつか同時に実行されている事。
そのいずれかの処理中になんらかの理由でエラーになる事。(Roll Back の発生)

を満たした際に発生するようです。
(ありゃ、トランザクション内に削除処理がありました)

ただ、下記の再現手順を作っていて分かったのですが、トランザクションは関
係ない比較的単純な DELETE でも処理件数が多いと発生するのかもしれません。

というのも 再現手順での run.sql を作成する際、Mroonga のテーブルから一
旦消しながら削除用SQLを作成しているのですが、指定件数分 DELETE 文が生
成されない事があります。 Null が返る時があるのです。
(最後に Null を足しているのはその名残)

innodb 側のテーブルで同じ処理を行うと期待通りの件数が出力され、また
対象レコードも 0 件になります。


環境:
CentOS   7.1
MySQL  5.7.9
Mroonga 5.10
(CentOS 6.4, MySQL 5.6.26 でも同様です)


再現手順:

CREATE DATABASE db_test;
USE db_test;
DROP TABLE IF EXISTS `tbl_mroonga`;
CREATE TABLE `tbl_mroonga` (
        `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
        `t1_date` TIMESTAMP NOT NULL DEFAULT current_timestamp,
        `t2_date` TIMESTAMP NOT NULL DEFAULT '0000-00-00',
        `a_id` BIGINT(20) NOT NULL Default 0,
        `t_text` LONGTEXT,
        PRIMARY KEY (`id`),
        UNIQUE KEY `a_id` (`a_id`),
        INDEX `t2_date` (`t2_date`),
        FULLTEXT INDEX `t_text` (`t_text` ) COMMENT 'normalizer "NormalizerMySQLUnicodeCIExceptKanaCIKanaWithVoicedSoundMark"'
) ENGINE=mroonga DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

DROP TABLE IF EXISTS `tbl_innodb`;
CREATE TABLE `tbl_innodb` (
        `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
        `t1_date` TIMESTAMP NOT NULL DEFAULT current_timestamp,
        `t2_date` TIMESTAMP NOT NULL DEFAULT '0000-00-00',
        `oa_id` BIGINT(20) NOT NULL Default 0,
        PRIMARY KEY (`id`),
        INDEX `oa_id` (`oa_id`),
        INDEX `t2_date` (`t2_date`)

) ENGINE=Innodb DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

DROP TABLE IF EXISTS `tbl_dummy`;
CREATE TABLE `tbl_dummy` (
        `id` BIGINT(20) PRIMARY KEY AUTO_INCREMENT,
        `t2_date` TIMESTAMP NOT NULL DEFAULT current_timestamp
) ENGINE=Innodb DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

-- レコードを増やす
INSERT INTO tbl_dummy (t2_date) VALUES ((SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401))))));
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;
INSERT INTO tbl_dummy (t2_date) SELECT ADDTIME(CONCAT_WS(' ','2015-06-01' + INTERVAL RAND() * 365 DAY, '00:00:00'), SEC_TO_TIME(FLOOR(0 + (RAND() * 86401)))) FROM tbl_dummy;

TRUNCATE TABLE tbl_mroonga;
INSERT INTO tbl_mroonga (a_id, t2_date)
SELECT id, t2_date FROM tbl_dummy ORDER BY id;

TRUNCATE TABLE tbl_innodb;
INSERT INTO tbl_innodb (oa_id, t2_date)
SELECT id, t2_date FROM tbl_dummy ORDER BY id;

## 概ね総レコード数の半分程度を削除対象にする。本番環境を模して連番で消さないようにする。
cat << '__EOF__' >while.sql
SET @a_id:=(SELECT a_id FROM tbl_mroonga WHERE t2_date <= current_timestamp ORDER BY RAND() LIMIT 1);
SELECT CONCAT('START TRANSACTION; UPDATE tbl_innodb SET oa_id = 0 WHERE oa_id =', @ a_id,'; DELETE FROM tbl_mroonga WHERE a_id = ', @ a_id,'; COMMIT WORK;') AS '';
DELETE FROM tbl_mroonga WHERE a_id = @a_id;
__EOF__

SELECT COUNT(id) FROM tbl_dummy WHERE t2_date <= current_timestamp;

## ↑の count の数(対象レコード分)だけ回す
rm -f run.sql;i=0; while [ ${i} -lt 4625 ]; do ((i ++)); mysql -N db_test < while.sql >> run.sql; echo ${i}; done
echo "Null">> run.sql
echo "Null">> run.sql
## ↑SQLエラーにする

-- いったんレコードを戻す 
TRUNCATE TABLE tbl_mroonga;
INSERT INTO tbl_mroonga (a_id, t2_date)
SELECT id, t2_date FROM tbl_dummy ORDER BY id;

## どん!
mysql db_test < run.sql 

を実行しながら、別セッションで rollback するSQLをガンガン実行
START TRANSACTION; UPDATE tbl_innodb SET oa_id = 0 WHERE oa_id =2756; DELETE FROM tbl_mroonga WHERE a_id = 2756; ROLLBACK WORK;
START TRANSACTION; UPDATE tbl_innodb SET oa_id = 0 WHERE oa_id =0; DELETE FROM tbl_mroonga WHERE a_id = 2756; ROLLBACK WORK;

-- run.sql の処理が完了頃に Indexの使用/未使用で SELECT 結果に差がでる
SELECT a_id
FROM tbl_mroonga AS tav_ignore 
IGNORE INDEX (t2_date)
WHERE t2_date <= current_timestamp
;
SELECT a_id 
FROM tbl_mroonga AS tav_origin
WHERE tav_origin.t2_date <= current_timestamp
;

完全ではないのですが、再現性はそこそこあります。

GTID を使用する順同期レプリケーション時に怒られてしまうので、トランザ
クション処理内からは Mroonga を外す処理に切替中です。

よろしくお願いします。


----
各務
kagam****@outwa*****




groonga-dev メーリングリストの案内
Zurück zum Archiv-Index