Нажмите "Enter" для перехода к содержанию

MySQL Server, MySQL InnoDB Cluster. Целостность данных? Не, не слышали.

Шел 2022 год… Я в своём познании Oracle EE и нахождении багов настолько преисполнился, что мне он стал скучен и не интересен. И взор пал на MySQL.

Я в качестве Oracle EE DBA

Все версии MySQL Server, MySQL InnoDB Cluster, включая последнюю 8.0.29, имеют проблемы целостности данных.

Вкратце, MySQL Server при старте не проверяет целостность данных. И если реплика кластера потеряла файл данных пользовательской базы данных или весь каталог пользовательской базы данных, то роль PRIMARY может быть назначена этой реплике.

Первая большая проблема в том, что если нарушена целостность данных сервера, то вы никогда не узнаете об этом, пока не обратитесь к этим данным. При этом не важно как мы потеряли данные: удалили файл таблицы руками или это сделал puppet по какой-нибудь маске или как-то еще. Очевидно, если при запуске MySQL Server нет проверки соответствия физического расположения файлов метаданным сервера, то нет никакой проверки повреждения файлов данных.

Вторая большая проблема в том, что механизм групповой репликации не работает должным образом. Он замечает отсутствие файлов на реплике и даже запускает механизм клонирования, но оно не работает.

Третья большая проблема вытекает из двух предыдущих. Вы можете назначить роль PRIMARY реплике с нарушенной целостностью данных.

1 мая 2022 года Oracle подтвердил 2 бага, которые связаны с данными проблемами.

Bug #34126233 Cluster Node working but Tablespace is missing for table (incremental recovery)
Bug #34126241 Clone does not work if folder manually removed from destination

Далее, я расскажу как воспроизвести проблему.

У нас есть MySQL Commercial InnoDB Cluster 8.0.29, состоящий из 3 нод:

 MySQL  node1:33060+ ssl  JS > cluster.status()
{
    "clusterName": "testCluster29",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "node1:3306",
        "ssl": "REQUIRED",
        "status": "OK",
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
        "topology": {
            "node1:3306": {
                "address": "node1:3306",
                "memberRole": "PRIMARY",
                "mode": "R/W",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            },
            "node2:3306": {
                "address": "node2:3306",
                "memberRole": "SECONDARY",
                "mode": "R/O",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            },
            "node3:3306": {
                "address": "node3:3306",
                "memberRole": "SECONDARY",
                "mode": "R/O",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            }
        },
        "topologyMode": "Single-Primary"
    },
    "groupInformationSourceMember": "node1:3306"
}

Чтобы воспроизвести проблему, нужно:

  1. Создать новую базу данных и таблицу в ней.
  2. Остановить MySQL Server на реплике.
  3. Удалить на реплике файл таблицы или каталог базы данных в директории данных сервера MySQL (datadir в my.cnf).
  4. Запустить MySQL Server.
  5. Переключить PRIMARY на реплику, в которой мы удалили файл данных или базу данных.
  6. Обратиться к удаленным данным. 
1. Создаем БД CONSISTENCY и таблицу data в ней.
 MySQL node1:33060+ ssl SQL > CREATE DATABASE CONSISTENCY;
Query OK, 1 row affected (0.0145 sec)
 MySQL node1:33060+ ssl SQL > USE CONSISTENCY;
Default schema set to `CONSISTENCY`.
Fetching table and column names from `CONSISTENCY` for auto-completion... Press ^C to stop.
MySQL node1:33060+ ssl CONSISTENCY SQL > CREATE TABLE data (name VARCHAR(20), owner VARCHAR(20), access VARCHAR(20));
Query OK, 0 rows affected (0.0362 sec)

2. Останавливаем MySQL Server на node2
[root@node2 ~]# systemctl stop mysqld

3. Удаляем файл таблицы data.
[root@node2 ~]# cd /var/lib/mysql/CONSISTENCY/
[root@node2 CONSISTENCY]# rm data.ibd
rm: remove regular file ‘data.ibd’? y

4. Запускаем MySQL Server
[root@node2 CONSISTENCY]# systemctl start mysqld

5. Проверяем статус кластера и видим, что идет восстановление на node2
 MySQL  node1:33060+ ssl  CONSISTENCY  JS > cluster.status()
{
    "clusterName": "testCluster29",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "node1:3306",
        "ssl": "REQUIRED",
        "status": "OK",
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure. 1 member is not active.",
        "topology": {
            "node1:3306": {
                "address": "node1:3306",
                "memberRole": "PRIMARY",
                "mode": "R/W",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            },
            "node2:3306": {
                "address": "node2:3306",
                "memberRole": "SECONDARY",
                "mode": "R/O",
                "readReplicas": {},
                "recoveryStatusText": "Recovery in progress",
                "role": "HA",
                "status": "RECOVERING",
                "version": "8.0.29"
            },
            "node3:3306": {
                "address": "node3:3306",
                "memberRole": "SECONDARY",
                "mode": "R/O",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            }
        },
        "topologyMode": "Single-Primary"
    },
    "groupInformationSourceMember": "node1:3306"
}

6. Проверяем еще раз и node2 становится доступной 
 MySQL  node1:33060+ ssl  CONSISTENCY  JS > cluster.status()
{
    "clusterName": "testCluster29",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "node1:3306",
        "ssl": "REQUIRED",
        "status": "OK",
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
        "topology": {
            "node1:3306": {
                "address": "node1:3306",
                "memberRole": "PRIMARY",
                "mode": "R/W",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            },
            "node2:3306": {
                "address": "node2:3306",
                "memberRole": "SECONDARY",
                "mode": "R/O",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            },
            "node3:3306": {
                "address": "node3:3306",
                "memberRole": "SECONDARY",
                "mode": "R/O",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            }
        },
        "topologyMode": "Single-Primary"
    },
    "groupInformationSourceMember": "node1:3306"
}

7. Проверим появился ли удаленный файл на node2. Файла нет, но нода ONLINE
[root@node2 ~]# ls -la /var/lib/mysql/CONSISTENCY/
total 4
drwxr-x---   2 mysql mysql    6 May  3 12:39 .
drwxr-x--x. 11 mysql mysql 4096 May  3 12:40 ..

8. Сделаем node2 PRIMARY 

MySQL node1:33060+ ssl CONSISTENCY JS > cluster.setPrimaryInstance('root@node2')
Setting instance 'node2' as the primary instance of cluster 'testCluster29'...
Instance 'node1:3306' was switched from PRIMARY to SECONDARY.
Instance 'node2:3306' was switched from SECONDARY to PRIMARY.
Instance 'node3:3306' remains SECONDARY.
WARNING: The cluster internal session is not the primary member anymore. For cluster management operations please obtain a fresh cluster handle using dba.getCluster().
The instance 'node2' was successfully elected as primary.

9.  Проверим состояние кластера. node2 стала PRIMARY
 MySQL  node1:33060+ ssl  CONSISTENCY  JS > cluster.status()
{
    "clusterName": "testCluster29",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "node2:3306",
        "ssl": "REQUIRED",
        "status": "OK",
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
        "topology": {
            "node1:3306": {
                "address": "node1:3306",
                "memberRole": "SECONDARY",
                "mode": "R/O",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            },
            "node2:3306": {
                "address": "node2:3306",
                "memberRole": "PRIMARY",
                "mode": "R/W",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            },
            "node3:3306": {
                "address": "node3:3306",
                "memberRole": "SECONDARY",
                "mode": "R/O",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            }
        },
        "topologyMode": "Single-Primary"
    },
    "groupInformationSourceMember": "node1:3306"
}

10. Подключимся к кластеру с node2 и попробуем обратиться к удаленным файлам.
[ivanov1@node2 ~]$ mysqlsh
MySQL Shell 8.0.29-commercial

Copyright (c) 2016, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
Other names may be trademarks of their respective owners.

Type '\help' or '\?' for help; '\quit' to exit.
 MySQL  JS > \c root@node2
Creating a session to 'root@node2'
Fetching schema names for autocompletion... Press ^C to stop.
Your MySQL connection id is 44 (X protocol)
Server version: 8.0.29-commercial MySQL Enterprise Server - Commercial
No default schema selected; type \use <schema> to set one.
 MySQL  node2:33060+ ssl  JS > cluster = dba.getCluster()
<Cluster:testCluster29>
 MySQL  node2:33060+ ssl  JS > \sql
Switching to SQL mode... Commands end with ;
 MySQL  node2:33060+ ssl  SQL > select * from CONSISTENCY.data;
ERROR: 1812: Tablespace is missing for table `CONSISTENCY`.`data`.
 MySQL  node2:33060+ ssl  SQL >

11. Проверим состояние кластера. Кластер в порядке, хотя на PRIMARY мы получили ошибку "ERROR: 1812: Tablespace is missing for table `CONSISTENCY`.`data`."
 MySQL  node2:33060+ ssl  JS > cluster.status()
{
    "clusterName": "testCluster29",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "node2:3306",
        "ssl": "REQUIRED",
        "status": "OK",
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
        "topology": {
            "node1:3306": {
                "address": "node1:3306",
                "memberRole": "SECONDARY",
                "mode": "R/O",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            },
            "node2:3306": {
                "address": "node2:3306",
                "memberRole": "PRIMARY",
                "mode": "R/W",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            },
            "node3:3306": {
                "address": "node3:3306",
                "memberRole": "SECONDARY",
                "mode": "R/O",
                "readReplicas": {},
                "replicationLag": null,
                "role": "HA",
                "status": "ONLINE",
                "version": "8.0.29"
            }
        },
        "topologyMode": "Single-Primary"
    },
    "groupInformationSourceMember": "node2:3306"
}

После Oracle EE наличие подобных проблем в MySQL EE InnoDB Cluster для меня — шок и нонсенс. Уверен, что Galera Cluster for MySQL и Percona XtraDB Cluster имеют те же проблемы.

Я никоим образом не хотел сказать, что MySQL плохой или не надежный. MySQL — такой, какой есть, со всеми его багами и крутыми особенностями. В каждом, тем более таком большом, продукте есть место багам. Главное их закрывать, а нам, пользователям, обходить их стороной до исправления.

Я всей душой с MySQL, я влюблен во всю команду Oracle MySQL, и особенно в команду поддержки Oracle MySQL (но, поддержку Oracle EE я презираю всем своим нутром, гребанные ублюдки). От багов никто не застрахован, главное, чтобы баги не переходили в разряд фич.