Re-indexação de preços causa bloqueios de banco de dados durante a finalização da compra

47

Estou com um problema em que acredito que o processo de re-indexação do preço do produto está causando uma exceção de impasse no processo de checkout.

Eu peguei essa exceção no processo de checkout:

Exceção de conversão de ordem: SQLSTATE [40001]: Falha de serialização: 1213 Impasse encontrado ao tentar obter o bloqueio; tente reiniciar a transação

Infelizmente, não tenho um rastreamento de pilha completo por causa de onde a exceção foi capturada, mas, ao verificar o status INNODB, consegui rastrear o impasse:

SELECT `si`.*, `p`.`type_id` FROM `cataloginventory_stock_item` AS `si` 
INNER JOIN `catalog_product_entity` AS `p` ON p.entity_id=si.product_id     
WHERE (stock_id=1) 
AND (product_id IN(47447, 56678)) FOR UPDATE

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 0 page no 329624 n bits 352 index 
`PRIMARY` of table `xxxx`.`catalog_product_entity` 

O bloqueio da tabela solicitante do SQL é finalmente gerado a partir de Mage_CatalogInventory_Model_Stock::registerProductsSale()quando ele tenta obter a contagem atual de inventário para diminuí-la.

No momento em que ocorreu o conflito, o processo de re-indexação do Preço do Produto estava em execução e eu suponho que ele tivesse um bloqueio de leitura no catalog_product_entity tableque causou o conflito. Se eu estiver entendendo o impasse corretamente, qualquer bloqueio de leitura causará um impasse, mas o re-índice de preços do produto mantém o bloqueio por um tempo razoável, pois o site possui ~ 50.000 produtos.

Infelizmente, nesse ponto do fluxo do código de pagamento, o cartão de crédito do cliente havia sido cobrado (por meio de um módulo de pagamento personalizado) e a criação do objeto de pedido correspondente falhou.

Minhas perguntas são:

  • A lógica do módulo de pagamento personalizado está com defeito? ou seja, existe um fluxo aceito para garantir que o Magento possa converter a cotação em uma exceção de pedido sem antes de confirmar a cobrança no método de pagamento (cartão de crédito)?

Edit: Parece que a lógica do módulo de pagamento está realmente com defeito, pois a chamada para $ paymentmethod-> authorize () deve ocorrer após o local em que esse impasse ocorre, não antes (conforme a resposta de Ivan abaixo). No entanto, a transação ainda será bloqueada pelo impasse (embora sem a cobrança incorreta no cartão de crédito).

  • Esta chamada de função $stockInfo = $this->_getResource()->getProductsStock($this, array_keys($qtys), true);em Mage_CatalogInventory_Model_Stock::registerProductsSale()torna uma leitura de bloqueio, o quão perigoso seria para torná-lo uma leitura sem bloqueio?

  • Ao pesquisar na Web por uma resposta, alguns lugares sugeriram não executar uma nova indexação completa enquanto o site estiver quente; dificilmente parece uma boa solução; é a questão da indexação causando conflitos de tabela e contenção de bloqueios um problema conhecido no Magento, existem soluções alternativas?

Edit: Parece que a questão restante aqui é a da terceira questão; re-indexação causando deadlocks na tabela. Procurando soluções alternativas para isso.

Edit: O conceito de que os impasses não são, por si só, problemas, mas a resposta a eles deve ser o foco, faz muito sentido. Investigue mais para encontrar um ponto no código para capturar a exceção de conflito e reemitir a solicitação. Fazer isso no nível do adaptador Zend Framework DB é uma abordagem, mas também estou procurando uma maneira de fazer isso no código Magento para facilitar a manutenção.

Há um patch interessante neste segmento: http://www.magentocommerce.com/boards/viewthread/31666/P0/ que parece resolver uma condição de conflito relacionada (mas não esta especificamente).

Edit: Aparentemente, o impasse foi endereçado a um grau no CE 1.8 Alpha. Ainda estou procurando uma solução alternativa até que esta versão esteja fora do Alpha

Roscius
fonte
Temos enfrentado um problema semelhante recentemente, qual extensão de pagamento você está usando?
precisa
É uma extensão codificada personalizada
Roscius
1
@kalenjordan As melhorias na indexação em 1.13 e um esquema de repetição como o de philwinkle abaixo mitigaram amplamente o problema para mim.
Roscius 21/03
1
@Roscius aproximadamente quanto eles o mitigaram? Estou vendo falhas de banco de dados de algum tipo (tempo limite de conexão, tempo de espera de bloqueio, deadlock) afetar cerca de 0,2% dos meus pedidos. Muito raro, mas eu realmente quero resolvê-lo totalmente.
precisa saber é o seguinte

Respostas:

16

Há uma probabilidade muito grande de que seu método de pagamento esteja processando o pagamento de forma incorreta.

O Processo de Salvamento de Pedidos Magento é bastante simples:

  • Prepara todos os dados que devem ser transferidos do item de cotação para o item do pedido, incluindo preços e informações do produto, depois não invocam a recuperação de preços.
  • Chamar antes de enviar eventos checkout_type_onepage_save_orderesales_model_service_quote_submit_before
    • Mage_CatalogInventory_Model_Stock::registerProductsSale() é invocado neste observador de eventos
  • Iniciar transação do banco de dados
  • Invoke $order->place()método que processa o pagamento pelo telefone $paymentMethod->authorize(), $paymentMethod->capture()ou $paymentMethod->initialize()depende de sua lógica.
  • Invoque o método $ order-> save () que salva a ordem processada nas tabelas do banco de dados sales_flat_order_*.
  • Confirmar transação do banco de dados (nesta etapa, o banco de dados libera o bloqueio na tabela de inventário)

Portanto, como você vê, não era possível, esse método de pagamento cobra dinheiro antes do bloqueio do inventário e lê os preços ou as informações do produto.

Só é possível se o método de pagamento for implementado de tal maneira, que ele execute o carregamento dos produtos com preços, após a execução da chamada de API para operação de cobrança.

Espero que isso ajude você a depurar seu problema.

Quanto à reindexação, deve ser seguro, se você não tiver esse problema com a forma de pagamento. Como a operação de leitura que depende dos bloqueios é realizada antes que o dinheiro seja cobrado.

Ivan Chepurnyi
fonte
1
Obrigado, parece que a lógica do módulo de pagamento personalizado está um pouco fora. No entanto, parece que um processo de indexação bloqueará o checkout causando uma exceção registerProductsSale()(entendendo que as correções no módulo de pagamento personalizado removerão o problema de cobrar o cartão do cliente).
Roscius 28/01
8

Como essa é uma extensão personalizada, podemos encontrar uma solução alternativa personalizada (leia-se: hack) para tentar novamente o salvamento sem editar os arquivos principais.

Eu resolvi todos os meus problemas de impasse com os dois métodos a seguir adicionados a uma classe auxiliar. Em vez de ligar, $product->save()chamo agora Mage::helper('mymodule')->saferSave($product):

/**
 * Save with a queued retry upon deadlock, set isolation level
 * @param  stdClass $obj object must have a pre-defined save() method
 * @return n/a      
 */
public function saferSave($obj)
{

    // Deadlock Workaround
    $adapter = Mage::getModel('core/resource')->getConnection('core_write');
    // Commit any existing transactions (use with caution!)
    if ($adapter->getTransactionLevel > 0) {
        $adapter->commit();
    }
    $adapter->query('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');

    //begin a retry loop that will recycle should a deadlock pop up
    $tries = 0;
        do {
            $retry = false;
            try {
                $obj->save();
            } catch (Exception $e) {
                if ($tries < 10 and $e->getMessage()=='SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction') {
                    $retry = true;
                } else {
                    //we tried at least 10 times, go ahead and throw exception
                    throw new Zend_Db_Statement_Exception($e->getMessage());
                }
                sleep($this->getDelay());
                $tries++;
            }
        } while ($retry);

    //free resources
    unset($adapter);
    return;
}

public function getDelay($tries){
    return (int) pow(2, $tries);
}

Isso realiza duas coisas distintas - enfileira uma nova tentativa quando um impasse é encontrado e define um tempo limite exponencialmente crescente para essa nova tentativa. Ele também define o nível de isolamento da transação. Há muitas informações sobre SO e DBA.SE para obter mais informações sobre os níveis de isolamento de transações do MySQL.

FWIW, não encontrei um impasse desde então.

philwinkle
fonte
1
@Mage :: getModel ('core / resource') @ deve criar uma nova conexão. Não entendo como isso pode alterar o nível de isolamento da transação atual.
giftnuss
@giftnuss justo o suficiente. Deve ser único, com certeza. Sinta-se livre para contribuir isto em meu módulo impasse sobre no github
philwinkle
@philwinkle obrigado por esse homem. Estou tentando descobrir se uma atualização do EE 1.13 resolverá meus problemas ou se devo analisar isso também. Eu sei que a 1,13 faz a indexação de forma assíncrona, o que é ótimo, mas se as mesmas consultas subjacentes estiverem envolvidas, será difícil entender como a assíncrona sozinha impediria que os impasses acontecessem.
kalenjordan
1
@kalenjordan, é uma combinação de assíncrona e a variação atualizada das alterações de db em 1.8 / 1.13 que diminui a probabilidade de conflitos.
philwinkle
Eu acho que você esqueceu de passar $triespara essa funçãosleep($this->getDelay());
Tahir Yasin
3

Nos fóruns do Magento, eles falam sobre a edição de um arquivo de biblioteca do Zend: lib / Zend / Db / Statement / Pdo.php

A função _execute original:

public function _execute(array $params = null)
    {
        // begin changes
        $tries = 0;
        do {
            $retry = false;
            try {
                if ($params !== null) {
                    return $this->_stmt->execute($params);
                } else {
                    return $this->_stmt->execute();
                }
            } catch (PDOException $e) {
                #require_once 'Zend/Db/Statement/Exception.php';
                if ($tries < 10 and $e->getMessage()=='SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction') {
                    $retry = true;
                } else {
                    throw new Zend_Db_Statement_Exception($e->getMessage());
                }
                $tries++;
            }
        } while ($retry);
        // end changes
    }

Após modificação:

public function _execute(array $params = null)
    {
        $tries = 0;
        do {
            $retry = false;
            try {
                $this->clear_result();
                $result = $this->getConnection()->query($sql);
                $this->clear_result();
            }
            catch (Exception $e) {
                if ($tries < 10 and $e->getMessage()=='SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction') {
                    $retry = true;
                } else {
                    throw $e;
                }
                $tries++;
            }
        } while ($retry);

        return $result;
    }

Como você pode ver, a única coisa que foi alterada é que as $ trips foram movidas para fora do loop.

Como sempre, é recomendável tentar isso em um ambiente de desenvolvimento / teste e não implantar instantaneamente essa correção em um ambiente de produção.

Kenny
fonte
2
Eu me preocupo em editar os arquivos de estrutura subjacentes, parece que a nova tentativa deve ocorrer no nível do código Magento.
Roscius
Tentamos a correção sugerida e de fato impediu que esse impasse em particular causasse problemas. Também recebíamos bloqueios de salvamentos no sales_flat_order_grid. Com essa correção, eles lançavam violações de contraintegração de integridade, o que obviamente não é bom.
Peter O'Callaghan
2

Eu tenho esse mesmo problema em um site Magento 1.11 e tenho um ticket aberto com o Magento desde 12/11/2012. Eles confirmaram que é um problema e devem estar criando um patch.

Minha pergunta é por que o preço precisa ser reindexado neste momento? Eu não acho que isso seja necessário:

#8 /var/www/html/app/code/core/Mage/CatalogInventory/Model/Observer.php(689): Mage_Catalog_Model_Resource_Product_Indexer_Price->reindexProductIds(Array)
Kimberely Thomas
fonte
1
Se um produto sai de estoque e os produtos fora de estoque não devem aparecer no catálogo, acredito que eles estejam ocultos por mérito de não terem registros de índice de preços que acabam excluindo-os da coleção de produtos quando o preço é associado a ele .
Davidalger
Isso não responde à pergunta. Parece que você está tentando adicionar informações adicionais à pergunta original. Talvez essa informação seja melhor como um comentário sobre a pergunta original.
Luke Mills
Eu estou com você, Kim. Eu tenho o mesmo ingresso aberto desde 11/2011.
precisa saber é o seguinte
Eu sei que isso tecnicamente não é uma resposta, mas uma sub-pergunta, mas responde à pergunta que faz referência a essa pergunta como uma duplicata! Então Kimberly Thomas e davidalger obtêm meu voto positivo por responderem a minha pergunta específica "Por que isso está reindexando os preços?" pergunta que eu estou pesquisando no momento! Obrigado!
cygnus digital
0

Tivemos um problema de impasse semelhante quando certas chamadas foram feitas durante um re-índice. Para nós, isso se manifestava principalmente quando um cliente adicionava algo ao carrinho. Embora provavelmente não tenha resolvido o problema subjacente real, a implementação da re-indexação assíncrona interrompeu completamente todas as chamadas de conflito que estávamos vendo anteriormente. Deve funcionar como um intervalo até que o problema subjacente seja corrigido e enviado para as edições EE / CE (acabamos comprando uma extensão para isso).

fr0x
fonte
0

Eu sugiro que você instale Philwinkle DeadlockRetry. Funcionou para o nosso banco de dados.

https://github.com/philwinkle/Philwinkle_DeadlockRetry

Eu também sugeriria que você observasse seus programas externos, atingindo sua API da Web. Tínhamos um que estava atualizando o QTY para produtos e estava causando muitos impasses. Reescrevemos isso e fomos diretamente para o banco de dados.

Chris Rosenau
fonte
1
Esse repositório não é mais suportado, mas felizmente recomenda sua substituição github.com/AOEpeople/Aoe_DbRetry .
Ganso
-1

Eu encontrei um problema de impasse no ano passado muitas vezes, resolvi-o simplesmente aumentando a memória do nosso servidor, porque o processo de indexação consome todos os recursos.

Você também deve usar a solução de reindexação assíncrona que usei miravist

Para um sistema mais estável, você deve separar o back-end do front-end para que eles não consumam a RAM um do outro.

Para minha experiência, não é problema do código fonte.

phanvugiap
fonte