Ponowne indeksowanie cen powoduje zakleszczenia DB podczas realizacji transakcji

47

Występuje problem, który moim zdaniem powoduje, że proces ponownego indeksowania ceny produktu powoduje wyjątek impasu w procesie realizacji transakcji.

Złapałem ten wyjątek podczas realizacji transakcji:

Wyjątek konwersji zamówienia: SQLSTATE [40001]: Błąd serializacji: 1213 Podczas próby uzyskania blokady znaleziono zakleszczenie; spróbuj ponownie uruchomić transakcję

Niestety nie mam śledzenia pełnego stosu z powodu miejsca, w którym został przechwycony wyjątek, ale sprawdzając status INNODB udało mi się wyśledzić impas:

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` 

Blokowanie tabeli SQL żądającej jest ostatecznie generowane, Mage_CatalogInventory_Model_Stock::registerProductsSale()gdy próbuje uzyskać bieżącą liczbę zapasów w celu jej zmniejszenia.

W momencie wystąpienia zakleszczenia proces ponownego indeksowania ceny produktu był uruchomiony i zakładam, że miał blokadę odczytu, catalog_product_entity tablektóra spowodowała zakleszczenie. Jeśli dobrze rozumiem zakleszczenie, każda blokada odczytu spowoduje zakleszczenie, ale ponowne indeksowanie ceny produktu utrzymuje blokadę przez długi czas, ponieważ witryna zawiera ~ 50 000 produktów.

Niestety, w tym momencie w kodzie przepływu transakcji karta kredytowa klienta została obciążona (za pomocą niestandardowego modułu płatności), a utworzenie odpowiedniego obiektu zamówienia nie powiodło się.

Moje pytania to:

  • Czy logika niestandardowego modułu płatności jest wadliwa? tj. Czy istnieje akceptowany przepływ zapewniający, że Magento może bezpłatnie przekonwertować ofertę na wyjątek zamówienia przed obciążeniem metodą płatności (kartą kredytową)?

Edycja: Wygląda na to, że logika modułu płatniczego jest rzeczywiście błędna, ponieważ wywołanie $ paymentmethod-> autoryzacja () powinno nastąpić po miejscu, w którym występuje ten impas, a nie wcześniej (zgodnie z odpowiedzią Iwana poniżej). Jednak transakcja będzie nadal blokowana przez impas (choć bez błędnego obciążenia karty kredytowej).

  • To wywołanie funkcji $stockInfo = $this->_getResource()->getProductsStock($this, array_keys($qtys), true);w Mage_CatalogInventory_Model_Stock::registerProductsSale()sprawia, że odczyt zamek, jak niebezpieczne byłoby zrobić to bez blokowania odczytu?

  • Szukając odpowiedzi w Internecie, kilka miejsc zasugerowało, aby nie przeprowadzać pełnego ponownego indeksowania, gdy witryna jest gorąca; nie wydaje się dobrym rozwiązaniem; czy problem indeksowania powodujący zakleszczenia tabeli i rywalizację o blokady jest znanym problemem w Magento, czy istnieją obejścia?

Edycja: Wydaje się, że pozostałe pytanie tutaj to pytanie trzecie; ponowne indeksowanie powodujące zakleszczenia tabeli. Szukasz obejścia tego problemu.

Edycja: Koncepcja, że ​​impasy nie są same w sobie problemami, a raczej reakcja na nie powinna być przedmiotem zainteresowania, ma wiele sensu. Dalsze dochodzenie w celu znalezienia punktu w kodzie w celu uchwycenia wyjątku zakleszczenia i ponownego wysłania żądania. Robienie tego na poziomie adaptera DB Zend Framework to jedno podejście, ale szukam również sposobu na wykonanie tego w kodzie Magento w celu ułatwienia konserwacji.

W tym wątku znajduje się interesująca łatka: http://www.magentocommerce.com/boards/viewthread/31666/P0/, która wydaje się rozwiązywać powiązany warunek impasu (ale nie ten konkretnie).

Edycja: Najwyraźniej impas został rozwiązany do pewnego stopnia w CE 1.8 Alpha. Wciąż szukam obejścia, dopóki ta wersja nie będzie w wersji alfa

Roscius
źródło
Ostatnio zmagamy się z podobnym problemem, jakiego rozszerzenia płatności używasz?
Peter O'Callaghan
To niestandardowe rozszerzenie kodowane
Roscius,
1
@kalenjordan Ulepszenia indeksowania w wersji 1.13 i schemat ponownych prób, taki jak philwinkle poniżej, w dużej mierze złagodziły ten problem.
Roscius
1
@Roscius z grubsza, jak bardzo to złagodzili? Widzę, że jakieś awarie DB (przekroczenie limitu czasu połączenia, przekroczenie limitu czasu oczekiwania na blokadę, zakleszczenie) wpływają na około 0,2% moich zamówień. Bardzo rzadko, ale naprawdę chcę to w pełni rozwiązać.
kalenjordan

Odpowiedzi:

16

Istnieje duże prawdopodobieństwo, że Twoja metoda płatności nieprawidłowo przetwarza płatność.

Proces zapisywania zamówień Magento jest dość prosty:

  • Przygotowuje wszystkie dane, które należy przenieść z wyceny na zamówienie, w tym ceny i informacje o produkcie, a następnie nie wywołuje pobierania ceny.
  • Wywołaj przed złożeniem zamówienia, wyślij wydarzenia checkout_type_onepage_save_orderisales_model_service_quote_submit_before
    • Mage_CatalogInventory_Model_Stock::registerProductsSale() jest wywoływany w tym obserwatorze zdarzeń
  • Rozpocznij transakcję DB
  • Wywołanie $order->place()metody, która przetwarza płatności dzwoni $paymentMethod->authorize(), $paymentMethod->capture()lub $paymentMethod->initialize()zależy od jego logiki.
  • Wywołaj metodę $ order-> save (), która zapisuje przetworzone zamówienie w tabelach DB sales_flat_order_*.
  • Zatwierdź transakcję DB (na tym etapie DB zwalnia blokadę w tabeli zapasów)

Jak widać, nie było możliwe, aby ta metoda płatności pobierała pieniądze przed zablokowaniem zapasów i odczytaniem cen produktów lub informacji o produkcie.

Jest to możliwe tylko w przypadku, gdy metoda płatności jest zaimplementowana w taki sposób, że sama ładuje produkty z cenami, po wykonaniu wezwania API do operacji ładowania.

Mam nadzieję, że pomoże to w debugowaniu problemu.

Jeśli chodzi o reindeksowanie, powinno być bezpieczne, jeśli nie masz tego problemu z metodą płatności. Ponieważ operacja odczytu zależna od blokad jest wykonywana przed naliczeniem pieniędzy.

Ivan Chepurnyi
źródło
1
Dzięki, wygląda na to, że logika niestandardowego modułu płatności jest trochę wyłączona. Wygląda jednak na to, że proces indeksowania zablokuje kasa, powodując wyjątek w registerProductsSale()(zrozumienie, że dzięki poprawkom niestandardowego modułu płatności usunie problem z obciążeniem karty klienta).
Roscius
8

Ponieważ jest to niestandardowe rozszerzenie, możemy znaleźć niestandardowe obejście (czytaj: włamać się) do ponownej próby zapisania bez edycji podstawowych plików.

Rozwiązałem wszystkie moje problemy z impasem, dodając dwie następujące metody do klasy pomocników. Zamiast dzwonić $product->save(), teraz dzwonię 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);
}

Osiąga to dwie różne rzeczy - ustawia kolejkę ponawiania próby napotkania impasu i ustawia wykładniczo zwiększający się limit czasu dla tej ponownej próby. Ustawia także poziom izolacji transakcji. Istnieje wiele informacji na temat SO i DBA.SE, aby uzyskać więcej informacji na temat poziomów izolacji transakcji MySQL.

FWIW, od tego czasu nie spotkałem impasu.

philwinkle
źródło
1
@Mage :: getModel ('core / resource') @ powinien utworzyć nowe połączenie. Nie rozumiem, jak może zmienić bieżący poziom izolacji transakcji.
giftnuss
@giftnuss wystarczy. Na pewno powinien być singletonem. Nie
krępuj się zgłosić
@ philwinkle dzięki za tego człowieka. Próbuję dowiedzieć się, czy aktualizacja EE 1.13 rozwiąże moje nieszczęścia, czy też powinienem się tym zająć. Wiem, że 1.13 indeksuje asynchronicznie, co jest świetne, ale jeśli w grę wchodzą te same kwerendy, trudno mi zrozumieć, w jaki sposób sama asynchronizacja zapobiegnie impasom.
kalenjordan
1
@Kalenjordan jest kombinacją asynchronizacji i zaktualizowanych zmian varien db w wersji 1.8 / 1.13, która zmniejsza prawdopodobieństwo zakleszczenia.
philwinkle
Myślę, że zapomniałeś przejść $triesdo tej funkcjisleep($this->getDelay());
Tahir Yasin
3

Na forach Magento rozmawiają o edycji pliku biblioteki Zend: lib / Zend / Db / Statement / Pdo.php

Oryginalna funkcja _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
    }

Po modyfikacji:

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;
    }

Jak widać, jedyną rzeczą, która została zmieniona, jest to, że $ try został przeniesiony poza pętlę.

Jak zawsze zaleca się wypróbowanie tego w środowisku programistycznym / testowym i niezwłoczne wdrożenie tej poprawki w środowisku produkcyjnym.

Kenny
źródło
2
Martwię się o edycję bazowych plików frameworka, wydaje się, że ponowna próba powinna się odbyć na poziomie kodu Magento.
Roscius
Wypróbowaliśmy sugerowaną poprawkę, która rzeczywiście zapobiegła występowaniu problemów z tym konkretnym impasem. Otrzymywaliśmy także impasy w zapisach do siatki Sales_flat_order_grid, z tą poprawką wprowadzili zamiast tego naruszenia naruszenia ograniczeń integralności, co oczywiście nie jest dobre.
Peter O'Callaghan
2

Mam ten sam problem na stronie Magento 1.11 i mam otwarty bilet z Magento na nim od 11.12.2012. Potwierdzili, że jest to problem i prawdopodobnie tworzą łatkę.

Moje pytanie brzmi: dlaczego w tym momencie cena musi być ponownie zindeksowana? Nie sądzę, że jest to potrzebne:

#8 /var/www/html/app/code/core/Mage/CatalogInventory/Model/Observer.php(689): Mage_Catalog_Model_Resource_Product_Indexer_Price->reindexProductIds(Array)
Kimberely Thomas
źródło
1
Jeśli produkt nie jest dostępny w magazynie, a produkty, których nie ma na magazynie, nie powinny pojawiać się w katalogu, uważam, że są one ukryte dzięki temu, że nie mają rekordów indeksu cen, które ostatecznie wykluczają je z kolekcji produktów, gdy ceny zostaną do niego dołączone .
davidalger
To nie odpowiada na pytanie. Wygląda na to, że próbujesz dodać dodatkowe informacje do pierwotnego pytania. Być może ta informacja byłaby lepsza jako komentarz do pierwotnego pytania.
Luke Mills,
Jestem z tobą, Kim. Mam ten sam bilet otwarty od 11/2011.
philwinkle
Wiem, że z technicznego punktu widzenia nie jest to odpowiedź, ale podpytanie, ale odpowiada na pytanie, które odnosi się do tego pytania jako duplikatu! Więc Kimberly Thomas i davidalger otrzymują moje poparcie za udzielenie odpowiedzi na moje konkretne pytanie „Dlaczego ceny indeksowania są reindeksowane?” pytanie, które ja '; obecnie googluję! Dzięki!
Cygnus Digital
0

Mieliśmy podobny problem z impasem, gdy podczas ponownego indeksowania wykonano pewne połączenia. Przejawiało się to przede wszystkim wtedy, gdy klient dodawał coś do koszyka. Prawdopodobnie nie naprawiając rzeczywistego problemu, wdrożenie asynchronicznego ponownego indeksowania całkowicie zatrzymało wszystkie wywołane przez nas zakleszczenia. Powinien działać jako przerwa, dopóki problem nie zostanie naprawiony i nie zostanie przekazany do edycji EE / CE (w końcu kupiliśmy rozszerzenie, aby to zrobić).

fr0x
źródło
0

Sugeruję zainstalowanie Philwinkle DeadlockRetry. Działa z naszą bazą danych.

https://github.com/philwinkle/Philwinkle_DeadlockRetry

Sugerowałbym również, aby spojrzeć na twoje programy zewnętrzne uderzające w twój interfejs API. Mieliśmy taki, który aktualizował QTY produktów i powodował wiele impasów. Napisaliśmy to na nowo i przeszliśmy bezpośrednio do bazy danych.

Chris Rosenau
źródło
1
To repo nie jest już obsługiwane, ale na szczęście zaleca się zastąpienie go github.com/AOEpeople/Aoe_DbRetry .
Goose
-1

W zeszłym roku spotkałem się z problemem impasu wiele razy go rozwiązałem, po prostu zwiększając pamięć dla naszego serwera, ponieważ proces indeksowania pochłania wszystkie zasoby.

Powinieneś także nam asynchronizować rozwiązanie reindex, którego użyłem miravist

Aby uzyskać bardziej stabilny system, powinieneś pomyśleć o oddzieleniu backendu od frontendu, aby nie zjadali sobie pamięci RAM.

Z mojego doświadczenia wynika, że ​​nie jest to problem z kodem źródłowym.

phanvugiap
źródło