30 setembro 2016

Migrando Legados para MySQL 5.7 com Query Rewrite Plugin - Parte 2

Moodle 2.3 e MySQL 5.7
No post anterior, vimos o que são os Query Rewrite Plugins, tanto preparse, quanto postparse. Agora é o momento de ver como este recurso pode viabilizar a migração de uma aplicação legada real. Para exemplificar, usaremos uma versão antiga do Moodle que é incompatível com MySQL 5.7. Veremos como implementar um preparse QR plugin.

O legado: Moodle 2.3

Moodle é uma plataforma de aprendizado extremamente popular criada com o LAMP Stack. A versão 2.3 já é bastante antiga e não recebe mais atualizações desde janeiro de 2014.
Ao tentar instalar o Moodle 2.3 com MySQL 5.7, ocorre o erro:
PHP Warning:  mysqli::mysqli(): Headers and client library minor version mismatch. Headers:50550 Library:50631 in /var/www/html/moodle/lib/dml/mysqli_native_moodle_database.php on line 377

Installation
System

Error reading from database

More information about this error

It is usually not possible to recover from errors triggered during installation, you may need to create a new database or use a different database prefix if you want to retry the installation.

Debug info: Unknown system variable 'storage_engine'
SELECT @@storage_engine
[NULL]
Error code: dmlreadexception
Stack trace:
line 407 of /lib/dml/moodle_database.php: dml_read_exception thrown
line 184 of /lib/dml/mysqli_native_moodle_database.php: call to moodle_database->query_end()
line 121 of /lib/ddl/mysql_sql_generator.php: call to mysqli_native_moodle_database->get_dbengine()
line 248 of /lib/ddl/sql_generator.php: call to mysql_sql_generator->getCreateTableSQL()
line 401 of /lib/ddl/database_manager.php: call to sql_generator->getCreateStructureSQL()
line 356 of /lib/ddl/database_manager.php: call to database_manager->install_from_xmldb_structure()
line 1422 of /lib/upgradelib.php: call to database_manager->install_from_xmldb_file()
line 184 of /admin/index.php: call to install_core()
Na base de bugs do Moodle é possível encontrar um bug com a análise do motivo do problema (MDL-50633). Resumidamente o problema ocorre porque o Moodle tenta ler qual o Storage Engine padrão usando a variável de sistema do MySQL @@storage_engine. Esta variável foi renomeada no MySql 5.7 ou superior para @@default_storage_engine.
Uma saída seria atualizar o Moodle, mas isto nem sempre é possível em alguns cenários. Outra seria corrigir esta consulta específica na aplicação. No maravilhoso mundo open source quase sempre podemos contar com esta opção, porém não é o caso de outros softwares de código fechado. Vamos supor que não seja possível mexer na aplicação.
Resta a opção de interceptar os comandos enviados pela aplicação e modificá-los antes que o banco de dados execute-os. Há algumas formas de fazer isso, como por exemplo o MySQL Proxy, porém na maioria dos casos é indesejável adicionar mais um componente na infra-estrutura, principalmente algo que seja um ponto único de falha entre aplicação e banco de dados. E se fosse possível fazer a modificação de comandos online, diretamente no servidor de banco de dados? É exatamente isto que faz um Query Rewrite Plugin!

Tentativa 1: usar um preparse Query Rewrite Plugin

O caminho mais fácil seria usar o plugin Rewriter, apresentado no post anterior, para interceptar a query SELECT @@storage_engine e reescrevê-la para SELECT @@default_storage_engine.
O passo-a-passo é o seguinte:
Passo 1: Instalar o Rewriter plugin:
cd /usr/share/mysql
mysql -uroot -p < install_rewriter.sql
Passo 2: Criar a regra de reescrita:
mysql> INSERT INTO query_rewrite.rewrite_rules ( pattern, pattern_database, replacement ) VALUES ('SELECT @@storage_engine', 'moodle', 'SELECT @@default_storage_engine');
Query OK, 1 row affected (0.02 sec)
Passo 3: Carregar a regra na memória:
mysql> CALL query_rewrite.flush_rewrite_rules();
ERROR 1644 (45000): Loading of some rule(s) failed.
Houve algo errado! Vamos fazer um debug verificando o que o plugin retornou na coluna message da tabela query_rewrite.rewrite_rules:
mysql> SELECT * FROM query_rewrite.rewrite_rules \G

           pattern: SELECT @@storage_engine
  pattern_database: moodle
       replacement: SELECT @@default_storage_engine
           enabled: YES
           message: Parse error in pattern: >>Unknown system variable 'storage_e
ngine'<<
É possível concluir que as variáveis de ambiente são verificadas pelo Banco de Dados antes da etapa de parsing, pois o Rewriter é um postparse QR plugin. Desta forma, teremos que usar um preparse QR plugin.

Tentativa 2: criar um postparse Query Rewrite Plugin

Podemos criar postparse QR plugins customizados, que funcionarão de forma parecida com o rewrite_example mostrado no post anterior.
Para nossa necessidade específica, precisamos apenas de um novo plugin que intercepte a query SELECT @@storage_engine e a reescreva para SELECT @@default_storage_engine antes que ela chegue ao parser.
O rewrite_example é justamente um exemplo de implementação de preparse QR plugin. O seu código-fonte está disponível junto com o código do MySQL 5.7 no GitHub.
Com pequenas modificações, criei um novo plugin para resolver nosso problema, o Rewrite Status Variable Storage Engine, disponível no GitHub. Se usar Linux 64-bit, você pode baixar o plugin já compilado e não precisa preocupar-se com os detalhes de codificação ou processo de compilação.
Desenvolver um plugin para MySQL requer conhecimento em C++, o que pode não ser trivial para alguns leitores. Não trataremos dos detalhes sobre o código-fonte de um novo plugin neste post, talvez em um artigo futuro. Se quiser aprofundar-se no tema, recomendo o ótimo livro Expert MySQL.
Uma vez que você tenha o plugin compilado em forma de biblioteca (arquivos .so no Linux ou .dll no Windows), basta instalá-lo normalmente no MySQL. Se estiver usando Linux 64-bit pode testar usando o seguinte procedimento:
  1. Baixe o plugin compilado rewrite_status_storage_engine.so
  2. Verifique qual seu diretório de plugins no MySQL com o comando SELECT @@plugin_dir e mova o arquivo rewrite_status_storage_engine.so para lá
  3. Certifique-se que o arquivo possui as permissões corretas com chmod 755 rewrite_status_storage_engine.so
  4. Instale o novo plugin normalmente no MySQL com INSTALL PLUGIN rewrite_status_storage_engine SONAME 'rewrite_status_storage_engine.so';
  5. Verifique se o plugin foi instalado com sucesso e está ativo:
    mysql> SHOW PLUGINS \G
    Name: rewrite_status_storage_engine
    Status: ACTIVE
    Type: AUDIT
    Library: rewrite_status_storage_engine.so
    License: GPL
Agora é só confirmar que o erro não ocorre mais:
mysql>  SELECT @@storage_engine;
+--------------------------+
| @@default_storage_engine |
+--------------------------+
| InnoDB                   |
+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS \G
*************************** 1. row ***************************
  Level: Note
   Code: 1105
Message: Query 'SELECT @@storage_engine' rewritten to 'SELECT @@default_storage_
engine' by a query rewrite plugin
1 row in set (0.00 sec)

Seguindo com a instalação do Moodle 2.3

Agora você deve conseguir instalar o Moodle 2.3 com MySQL 5.7 sem erros. Siga os passos do manual do Moodle mantendo o rewrite_status_storage_engine ativo.
Instação Moodle 2.3 com sucesso

Conclusão

Query Rewrite Plugins são bastante poderosos e podem ajudar em migrações evitando alterações na aplicação. Podem ser desenvolvidos plugins cutomizados em C++ ou usar o plugin Rewriter, dependendo da necessidade específica do projeto.
Envie seus comentários e sugestões.

Referências

Migrando Legados para MySQL 5.7 com Query Rewrite Plugin - Parte 1

Uma funcionalidade que pode fazer a diferença e viabilizar projetos de migração de legado para MySQL é a Query Rewrite Plugin. Com ela é possível interceptar comandos que são enviados ao MySQL e modificá-los online, sem a necessidade de alterar a aplicação.
Neste post, vamos entender como utilizar este recurso, com um exemplo real usando Moodle 2.3 no MySQL 5.7.

O que é o Query Rewrite Plugin

A partir do MySQL 5.7.6 há suporte para os Rewrite Plugins, que podem examinar e seletivamente reescrever comandos recebidos pelo servidor antes que sejam executados.
Quando uma instrução é recebido pelo MySQL Server, por exemplo um SELECT * FROM tabela where id = 1, ele passa por um parser que vai validar sintaxe e semântica, entre outros passos. Depois disso, o Otimizador de Consultas entra em ação, gerando e selecionando o melhor plano de execução para a query. Finalmente, o melhor plano é executado e o resultado retornado para o cliente que enviou a instrução.
Execução de Query no MySQL
Plugins Query Rewrite podem ser de dois subtipos:
- preparse: interceptam instruções antes do parser.
- postparse: agem logo depois do parser realizar seu trabalho.

Query Rewrite Plugins disponíveis

No pacote do MySQL Server 5.7 há dois Query Rewrite Plugins já disponíveis:
- rewrite_example: um exemplo de como funciona e como pode ser implementado um preparse Query Rewrite Plugin
- rewriter: um postparse plugin de uso mais genérico, para reescrever queries com SELECT, como veremos a seguir.

Como usar o plugin Rewriter (postparse)

A maneira mais fácil de enteder o funcionamento do Rewriter plugin é testando-o.
Passo 1 - Instale e configure o Rewriter plugin com:
cd /usr/share/mysql
mysql -uroot -p <install_rewriter.sql
Passo 2 - Como um teste simples, crie uma regra para transformar automaticamente SELECT ? em SELECT ? + 1:
mysql> INSERT INTO query_rewrite.rewrite_rules ( pattern, replacement ) VALUES ('SELECT ?', 'SELECT ? + 1');
Query OK, 1 row affected (0.06 sec)

mysql> SELECT * FROM query_rewrite.rewrite_rules\G
*************************** 1. row ***************************
                id: 1
           pattern: SELECT ?
  pattern_database: NULL
       replacement: SELECT ? + 1
           enabled: YES
           message: NULL
    pattern_digest: NULL
normalized_pattern: NULL
1 row in set (0.00 sec)
Passo 3 - Carregue a regra recém criada na memória:
mysql> CALL query_rewrite.flush_rewrite_rules();
Query OK, 0 rows affected (0.06 sec)
Passo 4 - Execute um SELECT:
mysql> SELECT 1;
+-------+
| 1 + 1 |
+-------+
|     2 |
+-------+
1 row in set, 1 warning (0.01 sec)

mysql> SHOW WARNINGS \G
*************************** 1. row ***************************
  Level: Note
   Code: 1105
Message: Query 'SELECT 1' rewritten to 'SELECT 1 + 1' by a query rewrite plugin
1 row in set (0.00 sec)
Você pode desabilitar apenas regras específicas com:
mysql> UPDATE query_rewrite.rewrite_rules SET enabled = 'NO' WHERE id = 1;
Veja que o Rewriter tem a capacidade de reescrever SELECTs. Ele examina instruções SELECT e pode reescrevê-los, com base nas regras de reescrita definidas na tabela query_rewrite.rewrite_rules. Os DBAs usam o plugin manipulando as regras armazenadas nesta tabela. Para efetivar novas regras (alterações na tabela rewrite_rules), o DBA deve chamar a procedure flush_rewrite_rules(), que carrega as regras na memória. Se houver algum erro durante a operação, o plugin o informará através da coluna message.
O plugin adiciona algumas variáveis ​de sistema para configuração:
SHOW GLOBAL VARIABLES LIKE 'rewriter%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| rewriter_enabled | ON    |
| rewriter_verbose | 1     |
+------------------+-------+
Também são adicionadas variáveis de status com informações sobre sua operação:
SHOW GLOBAL STATUS LIKE 'rewriter%';
+-----------------------------------+-------+
| Variable_name                     | Value |
+-----------------------------------+-------+
| Rewriter_number_loaded_rules      | 1     |
| Rewriter_number_reloads           | 1     |
| Rewriter_number_rewritten_queries | 1     |
| Rewriter_reload_error             | OFF   |
+-----------------------------------+-------+
O Rewriter plugin atuará em instruções SELECT e em Prepared Statements, mas não fará reescritas para SELECTs dentro de Stored Programs ou UDFs.

Testando outro Query Rewrite Plugin (preparse)

O Rewriter é um QR plugin postparse. Isto significa que vai atuar depois do trabalho do parser. Vejamos agora como funciona um QR plugin preparse, que vai modificar a query antes de passar pelo parser:
mysql> INSTALL PLUGIN rewrite_example SONAME 'rewrite_example.so';
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT @@DEFAULT_STORAGE_ENGINE;
+--------------------------+
| @@default_storage_engine |
+--------------------------+
| InnoDB                   |
+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS \G
*************************** 1. row ***************************
  Level: Note
   Code: 1105
Message: Query 'SELECT @@DEFAULT_STORAGE_ENGINE' rewritten to 'select @@default_storage_engine' by a query rewrite plugin
*************************** 2. row ***************************
  Level: Note
   Code: 1105
Message: Query 'SHOW WARNINGS' rewritten to 'show warnings' by a query rewrite plugin
2 rows in set, 1 warning (0.00 sec)
Note que a query foi reescrita pelo plugin para letras minúsculas. Este segundo plugin é apenas um exemplo, talvez sem muita aplicação prática. Porém, talvez você necessite realmente criar um preparse QR plugin em algumas situações, como no estudo de caso que veremos na parte 2.

Conclusão

Query Rewrite Plugins podem ter vários usos e são aliados poderosos quando não é possível alterar o código da aplicação. O Rewriter é uma implementação mais genérica, que pode ajudar se for necessário interceptar e reescrever SELECTs.

Referências