Volver a indexar el precio provoca puntos muertos en la base de datos durante el pago

47

Estoy experimentando un problema en el que creo que el proceso de reindexación del precio del producto está causando una excepción de punto muerto en el proceso de pago.

Capté esta excepción en el proceso de pago:

Excepción de conversión de orden: SQLSTATE [40001]: error de serialización: 1213 Punto muerto encontrado al intentar bloquear; intente reiniciar la transacción

Desafortunadamente, no tengo un seguimiento completo de la pila debido a dónde se detectó la excepción, pero al verificar el estado de INNODB pude rastrear el punto muerto:

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` 

El bloqueo de la tabla de solicitud de SQL se genera en última instancia Mage_CatalogInventory_Model_Stock::registerProductsSale()cuando se intenta obtener el recuento de inventario actual para disminuirlo.

En el momento en que se produjo el punto muerto, el proceso de reindexación del precio del producto se estaba ejecutando y supongo que tenía un bloqueo de lectura catalog_product_entity tableque causó el punto muerto. Si entiendo el punto muerto correctamente, cualquier bloqueo de lectura provocará un punto muerto, pero el re-índice del precio del producto mantiene el bloqueo durante un tiempo justo ya que el sitio tiene ~ 50,000 productos.

Desafortunadamente, en este punto del flujo del código de pago, la tarjeta de crédito del cliente había sido cargada (a través de un módulo de pago personalizado) y la creación del objeto de pedido correspondiente falló.

Mis preguntas son:

  • ¿La lógica del módulo de pago personalizado es defectuosa? es decir, ¿hay un flujo aceptado para garantizar que Magento pueda convertir la cotización a una orden de excepción gratis antes de comprometer el cargo al método de pago (tarjeta de crédito)?

Editar: Parece que la lógica del módulo de pago es realmente defectuosa ya que la llamada a $ paymentmethod-> authorize () debería ocurrir después del lugar donde se produce este punto muerto, no antes (según la respuesta de Ivan a continuación). Sin embargo, la transacción seguirá siendo bloqueada por el punto muerto (aunque sin el cargo erróneo a la tarjeta de crédito).

  • Esta llamada a la función $stockInfo = $this->_getResource()->getProductsStock($this, array_keys($qtys), true);en Mage_CatalogInventory_Model_Stock::registerProductsSale()la convierte en una lectura de bloqueo, lo peligroso que sería para que sea una lectura sin bloqueo?

  • Al buscar en la web una respuesta, un par de lugares sugirieron no ejecutar una reindexación completa mientras el sitio está activo; difícilmente parece una buena solución; ¿Es el problema de la indexación que causa puntos muertos de tabla y contención de bloqueo un problema conocido en Magento? ¿Hay soluciones alternativas?

Editar: Parece que la pregunta restante aquí es la de la tercera pregunta; reindexar causando puntos muertos en la tabla. Buscando soluciones para esto.

Editar: el concepto de que los puntos muertos no están en sí mismos, sino que la respuesta a ellos debería ser el foco, tiene mucho sentido. Investigando más a fondo para encontrar un punto en el código para detectar la excepción de punto muerto y volver a emitir la solicitud. Hacer esto en el nivel del adaptador Zend Framework DB es un enfoque, pero también estoy buscando una manera de hacerlo en el código de Magento para facilitar el mantenimiento.

Hay un parche interesante en este hilo: http://www.magentocommerce.com/boards/viewthread/31666/P0/ que parece resolver una condición de punto muerto relacionada (pero no esta específicamente).

Editar: Aparentemente, el punto muerto se ha abordado hasta cierto punto en CE 1.8 Alpha. Todavía estoy buscando una solución hasta que esta versión esté fuera de Alpha

Roscio
fuente
Hemos estado luchando contra un problema similar recientemente, ¿qué extensión de pago está utilizando?
Peter O'Callaghan
Es una extensión codificada personalizada
Roscius
1
@kalenjordan Las mejoras de indexación en 1.13 y un esquema de reintento como el de philwinkle a continuación me han mitigado en gran medida.
Roscio
1
@Roscius aproximadamente, ¿cuánto lo han mitigado? Veo que los fallos de DB de algún tipo (tiempo de espera de conexión, tiempo de espera de bloqueo, punto muerto) afectan aproximadamente al 0.2% de mis pedidos. Muy raro, pero realmente quiero resolverlo por completo.
kalenjordan

Respuestas:

16

Existe una gran probabilidad de que su método de pago esté procesando el pago incorrectamente.

El proceso de guardado de pedidos de Magento es bastante simple:

  • Prepara todos los datos que deben transferirse del artículo de presupuesto al artículo de pedido, incluidos los precios y la información del producto, luego no invoca la recuperación de precios.
  • Invocar antes del pedido enviar eventos checkout_type_onepage_save_orderysales_model_service_quote_submit_before
    • Mage_CatalogInventory_Model_Stock::registerProductsSale() se invoca en este evento observador
  • Iniciar transacción de base de datos
  • Invocar $order->place()método que procesa el pago llamando $paymentMethod->authorize(), $paymentMethod->capture()o $paymentMethod->initialize()depende de su lógica.
  • Invoque el método $ order-> save () que guarda el orden procesado en las tablas de base de datos sales_flat_order_*.
  • Confirmar transacción de DB (en este paso, DB libera el bloqueo en la tabla de inventario)

Como puede ver, no podría ser posible, ese método de pago cobra dinero antes del bloqueo del inventario y la lectura de los precios del producto o la información del producto.

Solo es posible en caso de que el método de pago se implemente de tal manera que realice la carga de los productos con los precios, después de que se realice la llamada API para la operación de cobro.

Espero que esto te ayude a depurar tu problema.

En cuanto a la reindexación, debería ser seguro, si no tiene este problema con el método de pago. Dado que la operación de lectura que depende de las cerraduras se realiza antes de que se cobre el dinero.

Ivan Chepurnyi
fuente
1
Gracias, parece que la lógica del módulo de pago personalizado está un poco apagada. Sin embargo, todavía parece que un proceso de indexación bloqueará el proceso de pago al causar una excepción en registerProductsSale()(comprender que con las correcciones al módulo de pago personalizado se eliminará el problema de que se cargue la tarjeta del cliente).
Roscio
8

Debido a que esta es una extensión personalizada, podemos encontrar una solución personalizada (leer: piratear) para volver a intentar guardar sin editar archivos principales.

He resuelto todos mis problemas de punto muerto con los siguientes dos métodos agregados a una clase auxiliar. En lugar de llamar $product->save(), ahora llamo 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);
}

Esto logra dos cosas distintas: pone en cola un reintento cuando se encuentra un punto muerto y establece un tiempo de espera exponencialmente creciente para ese reintento. También establece el nivel de aislamiento de la transacción. Hay mucha información sobre SO y DBA.SE para obtener más información sobre los niveles de aislamiento de transacciones de MySQL.

FWIW, no he encontrado un punto muerto desde entonces.

philwinkle
fuente
1
@Mage :: getModel ('core / resource') @ debería crear una nueva conexión. No entiendo cómo puede cambiar el nivel de aislamiento de la transacción actual.
giftnuss
@giftnuss lo suficientemente justo. Debería ser singleton seguro. Siéntase libre de contribuir con esto en mi módulo de punto muerto en github
philwinkle
@philwinkle gracias por este hombre. Estoy tratando de averiguar si una actualización EE 1.13 resolverá mis problemas o si también debería investigar esto. Sé que 1.13 indexa de forma asincrónica, lo cual es excelente, pero si están involucradas las mismas consultas subyacentes, me cuesta entender cómo la asincrónica por sí sola evitaría que se produzcan bloqueos.
kalenjordan
1
@kalenjordan es una combinación de async y los cambios de varien db actualizados en 1.8 / 1.13 que disminuye la probabilidad de puntos muertos.
philwinkle
Creo que olvidó pasar $triesa esta funciónsleep($this->getDelay());
Tahir Yasin
3

En los foros de Magento hablan sobre la edición de un archivo de biblioteca Zend: lib / Zend / Db / Statement / Pdo.php

La función original _execute:

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
    }

Después de la modificación:

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 puede ver, lo único que se ha cambiado es que los $ tries se han movido fuera del ciclo.

Como siempre, se sugiere probar esto en un entorno de desarrollo / prueba y no implementar instantáneamente esta solución en un entorno de producción.

Kenny
fuente
2
Me preocupa editar los archivos de marco subyacentes, parece que el reintento debería ocurrir en el nivel del código de Magento.
Roscius
Hemos probado la solución sugerida y, de hecho, evitó que este punto muerto en particular causara problemas. También recibimos puntos muertos en los guardados en sales_flat_order_grid, con esta solución en su lugar, en su lugar arrojan infracciones de integridad, lo que obviamente no es bueno.
Peter O'Callaghan
2

Tengo este mismo problema en un sitio de Magento 1.11 y tengo un ticket abierto con Magento desde el 12/11/2012. Confirmaron que es un problema y se supone que crean un parche.

Mi pregunta es ¿por qué el precio debe reindexarse ​​en este momento? No creo que esto sea necesario:

#8 /var/www/html/app/code/core/Mage/CatalogInventory/Model/Observer.php(689): Mage_Catalog_Model_Resource_Product_Indexer_Price->reindexProductIds(Array)
Kimberely Thomas
fuente
1
Si un producto se agota y no se supone que los productos no se muestren en el catálogo, creo que están ocultos por el mérito de no tener registros de índice de precios que terminen excluyéndolos de la colección de productos cuando se unen los precios. .
davidalger
Esto no responde la pregunta. Parece que estás intentando agregar información adicional a la pregunta original. Quizás esta información sería mejor como un comentario sobre la pregunta original.
Luke Mills el
Estoy contigo, Kim. Tengo el mismo boleto abierto desde el 11/2011.
philwinkle
Sé que esto técnicamente no es una respuesta, sino una subpregunta, sin embargo, responde la pregunta que hace referencia a esta pregunta como un duplicado. Entonces, Kimberly Thomas y Davidalger obtienen mi voto a favor por responder mi pregunta específica "¿Por qué está reindexando los precios?" pregunta que estoy actualmente buscando en Google! ¡Gracias!
cygnus digital
0

Tuvimos un problema de punto muerto similar cuando se realizaron ciertas llamadas durante una reindexación. Para nosotros, se manifestó principalmente cuando un cliente agregaría algo al carrito. Aunque probablemente no solucione el problema subyacente real, la implementación de la reindexación asincrónica ha detenido por completo todas las llamadas de punto muerto que estábamos viendo anteriormente. Debería funcionar como un espacio intermedio hasta que se solucione el problema subyacente y se envíe a las ediciones EE / CE (terminamos comprando una extensión para hacerlo).

fr0x
fuente
0

Le sugiero que instale Philwinkle DeadlockRetry. Funcionó para nuestra base de datos.

https://github.com/philwinkle/Philwinkle_DeadlockRetry

También sugeriría mirar cualquier programa externo que golpee su API web. Tuvimos uno que estaba actualizando la CANTIDAD para los productos y estaba causando muchos puntos muertos. Reescribimos eso y fuimos directamente a la base de datos.

Chris Rosenau
fuente
1
Este repositorio ya no es compatible, pero afortunadamente recomienda su reemplazo github.com/AOEpeople/Aoe_DbRetry .
Ganso
-1

Encontré un problema de punto muerto el año pasado muchas veces lo resolví simplemente aumentando la memoria para nuestro servidor porque el proceso de indexación consume todos los recursos.

También debe usar la solución de reindexación asíncrona que utilicé miravist

Para un sistema más estable, debe pensar en separar su backend de la interfaz para que no se coman la RAM del otro.

Para mi experiencia, no es un problema de código fuente.

phanvugiap
fuente