Jump to content
hakeryk2

Nagłe skoki procesora? Większe zużycie na serwerze? Sklep czasami przymula? Sprawdź koszyki!

Recommended Posts

Posted (edited)

Na grupie zamkniętej Prestashop Polska ktoś poruszył temat zwiększonego zużycia CPU na hostingu ostatnimi na czasy i w sumie lekko temat olałem, dopóki nie powiązałem tego z krótkimi skokami zużycia na serwerze. Ostatnio też miałem dosyć duże skoki w którym po kilkanaście sekund lub nawet nieco dłużej wszystkie 4 rdzenie były na 100% i gdy szybko podglądałem co to za proces w PHP (komendą strace) to gdzieś tam śmigało odpalane zapytanie z PHP do MySQL o SUM('quantity') FROM `ps_cart_product` where bla bla i id_cart". No cóż, myślałem, że sobie z botami generującymi koszyki poradziłem (na forum są o tym tematy jak je blokować), ale jeden wybitnie ma gdzieś moje zabezpieczenia i wygenerował koszyk w którym było ponad 1000 produktów i gdy dodawał każdy kolejny produkt odpalały się wszystkie reguły koszyków itp, które dosyć mocno obliczeniowe były i obstawiam, że i u Was się to robiło.

Tak więc na start diagnoza i naprawa:
1) Sprawdzić czy nie ma porzuconych koszyków, mój był na kilkanaście milionów złotych xD
2) Wprowadzić limit na produkty dodane do koszyka, można to na szybko zrobić o tak https://youtu.be/wAlXtr8u5Qo choć można sobie nieco to doprecyzować lub skopiować całą funkcję processChangeProductInCart stąd i podmienić w pliku controllers/front/CartController:php Można ustawić zmienne pod siebie zmieniając zmienne  $max_cart_quantity oraz $max_cart_products.

    protected function processChangeProductInCart()
    {
        $mode = (Tools::getIsset('update') && $this->id_product) ? 'update' : 'add';

        if ($this->qty == 0) {
            $this->errors[] = Tools::displayError('Null quantity.', !Tools::getValue('ajax'));
        } elseif (!$this->id_product) {
            $this->errors[] = Tools::displayError('Product not found', !Tools::getValue('ajax'));
        }

        $product = new Product($this->id_product, true, $this->context->language->id);
        if (!$product->id || !$product->active || !$product->checkAccess($this->context->cart->id_customer)) {
            $this->errors[] = Tools::displayError('This product is no longer available.', !Tools::getValue('ajax'));
            return;
        }

        $qty_to_check = $this->qty;
        $cart_products = $this->context->cart->getProducts();

        $total_cart_quantity = 0;
        $total_cart_products = count($cart_products);

        if (is_array($cart_products)) {
            foreach ($cart_products as $cart_product) {
                $total_cart_quantity += $cart_product['cart_quantity'];
                if ((!isset($this->id_product_attribute) || $cart_product['id_product_attribute'] == $this->id_product_attribute) &&
                    (isset($this->id_product) && $cart_product['id_product'] == $this->id_product)) {
                    $qty_to_check = $cart_product['cart_quantity'];

                    if (Tools::getValue('op', 'up') == 'down') {
                        $qty_to_check -= $this->qty;
                    } else {
                        $qty_to_check += $this->qty;
                    }

                    break;
                }
            }
        }

        $max_cart_quantity = 100; // Limit max quantity of all products
        $max_cart_products = 20; // limit products

        // limit the max add products to the cart added 
        if (Tools::getValue('op', 'up') == 'up' && $mode == 'add'){
            if ($total_cart_products >= $max_cart_products ){
                $this->errors[] = sprintf(Tools::displayError('Przekroczono limit %s dodanych produktów do koszyka'), $max_cart_products);
            } else if ($total_cart_quantity + $this->qty > $max_cart_quantity) {
                $this->errors[] = sprintf(Tools::displayError('Przekroczono limit %s ilości sztuk w koszyku'), $max_cart_quantity);
            }
        }

        // Check product quantity availability
        if ($this->id_product_attribute) {
            if (!Product::isAvailableWhenOutOfStock($product->out_of_stock) && !Attribute::checkAttributeQty($this->id_product_attribute, $qty_to_check)) {
                $this->errors[] = Tools::displayError('There isn\'t enough product in stock.', !Tools::getValue('ajax'));
            }
        } elseif ($product->hasAttributes()) {
            $minimumQuantity = ($product->out_of_stock == 2) ? !Configuration::get('PS_ORDER_OUT_OF_STOCK') : !$product->out_of_stock;
            $this->id_product_attribute = Product::getDefaultAttribute($product->id, $minimumQuantity);
            // @todo do something better than a redirect admin !!
            if (!$this->id_product_attribute) {
                Tools::redirectAdmin($this->context->link->getProductLink($product));
            } elseif (!Product::isAvailableWhenOutOfStock($product->out_of_stock) && !Attribute::checkAttributeQty($this->id_product_attribute, $qty_to_check)) {
                $this->errors[] = Tools::displayError('There isn\'t enough product in stock.', !Tools::getValue('ajax'));
            }
        } elseif (!$product->checkQty($qty_to_check)) {
            $this->errors[] = Tools::displayError('There isn\'t enough product in stock.', !Tools::getValue('ajax'));
        }

        // If no errors, process product addition
        if (!$this->errors && $mode == 'add') {
            // Add cart if no cart found
            if (!$this->context->cart->id) {
                if (Context::getContext()->cookie->id_guest) {
                    $guest = new Guest(Context::getContext()->cookie->id_guest);
                    $this->context->cart->mobile_theme = $guest->mobile_theme;
                }
                $this->context->cart->add();
                if ($this->context->cart->id) {
                    $this->context->cookie->id_cart = (int)$this->context->cart->id;
                }
            }

            // Check customizable fields
            if (!$product->hasAllRequiredCustomizableFields() && !$this->customization_id) {
                $this->errors[] = Tools::displayError('Please fill in all of the required fields, and then save your customizations.', !Tools::getValue('ajax'));
            }

            if (!$this->errors) {
                $cart_rules = $this->context->cart->getCartRules();
                $available_cart_rules = CartRule::getCustomerCartRules($this->context->language->id, (isset($this->context->customer->id) ? $this->context->customer->id : 0), true, true, true, $this->context->cart, false, true);
                $update_quantity = $this->context->cart->updateQty($this->qty, $this->id_product, $this->id_product_attribute, $this->customization_id, Tools::getValue('op', 'up'), $this->id_address_delivery);
                if ($update_quantity < 0) {
                    // If product has attribute, minimal quantity is set with minimal quantity of attribute
                    $minimal_quantity = ($this->id_product_attribute) ? Attribute::getAttributeMinimalQty($this->id_product_attribute) : $product->minimal_quantity;
                    $this->errors[] = sprintf(Tools::displayError('You must add %d minimum quantity', !Tools::getValue('ajax')), $minimal_quantity);
                } elseif (!$update_quantity) {
                    $this->errors[] = Tools::displayError('You already have the maximum quantity available for this product.', !Tools::getValue('ajax'));
                } elseif ((int)Tools::getValue('allow_refresh')) {
                    // If the cart rules has changed, we need to refresh the whole cart
                    $cart_rules2 = $this->context->cart->getCartRules();
                    if (count($cart_rules2) != count($cart_rules)) {
                        $this->ajax_refresh = true;
                    } elseif (count($cart_rules2)) {
                        $rule_list = array();
                        foreach ($cart_rules2 as $rule) {
                            $rule_list[] = $rule['id_cart_rule'];
                        }
                        foreach ($cart_rules as $rule) {
                            if (!in_array($rule['id_cart_rule'], $rule_list)) {
                                $this->ajax_refresh = true;
                                break;
                            }
                        }
                    } else {
                        $available_cart_rules2 = CartRule::getCustomerCartRules($this->context->language->id, (isset($this->context->customer->id) ? $this->context->customer->id : 0), true, true, true, $this->context->cart, false, true);
                        if (count($available_cart_rules2) != count($available_cart_rules)) {
                            $this->ajax_refresh = true;
                        } elseif (count($available_cart_rules2)) {
                            $rule_list = array();
                            foreach ($available_cart_rules2 as $rule) {
                                $rule_list[] = $rule['id_cart_rule'];
                            }
                            foreach ($cart_rules2 as $rule) {
                                if (!in_array($rule['id_cart_rule'], $rule_list)) {
                                    $this->ajax_refresh = true;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }

        $removed = CartRule::autoRemoveFromCart();
        CartRule::autoAddToCart();
        if (count($removed) && (int)Tools::getValue('allow_refresh')) {
            $this->ajax_refresh = true;
        }
    }


3) Wywalić te ogromne koszyki ze sklepu
4) Sprawdzić w logach apache IP bota wyszukując seokicks i na początku będziecie mieli ich IP. Bot jest na tyle bezczelny, że wchodzi w crawl z tym samym ciastkiem i jego IP to w moim przypadku 95.216.2.43. Wystarczy w htaccess w głównym folderze sklepu dodać w wolnej linii taki kod

5) Do robots.txt dodać regułę Disallow: /*?add= by profilaktycznie odsiać roboty które przestrzegają reguł, ponieważ Prestashop w wersji 1.6 i chyba w 1.7 nie dodaje domyślnie tej reguły do tego pliku.
 

Deny from 95.216.2.43 95.216.11.210

Napisałem do autorów by coś z tymi zrobili.
----------------------------------------------------------------------------------------------------------------------------------------------------------------
EDYCJA:

Otrzymałem odpowiedź twórców crawlera i ogólnie byli zdziwieni tym, że do koszyka dodaje się poprzez przekazanie parametru z URL koszyka oraz to, że Prestashop domyślnie do robots.txt nie dodaje Disallow: /*?add= ponieważ ich robot przestrzega reguł. No wszystko byłoby spoko gdyby nie sam fakt, że u mnie robots.txt miał odpowiednie reguły blokujące wszelakie takie akcje więc nie powinno się to wydarzyć. Ponoć w 1.7 nie powinno się to dziać.

Edited by hakeryk2
  • Like 1

Share this post


Link to post
Share on other sites

Jeśli bot ignoruje zapisy w robots.txt to w angielskiej sekcji jest darmowy moduł który banuje automatycznie takie boty.

Share this post


Link to post
Share on other sites

Nie ma sensu blokada pojedynczych IP, możesz w htacces zablokować wybrane/uciążliwe boty po useragent (ale nie stosuj popularnych list bo 99% tych botów jest zazwyczaj i tak nieaktywna i tylko obciąża serwer).

Możesz na routerze/firewallu (ostatecznie htaccess) zablokować np. 95.216.0.0/16 - to wszystko Hetznera a 1 regułą masz trochę więcej adresów ;)

Jak masz możliwość możesz przyciąć całego ASa - AS24940.

Share this post


Link to post
Share on other sites

@turbo_rabbit mam opcache włączony już od dawien dawna, czasami tylko lubi coś popsuć z cache smarty/twig więc warto ustawić

opcache_reset();

na początku funkcji czyszczącej cache w Module.php i _clearCache:)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×

Important Information

Cookies ensure the smooth running of our services. Using these, you accept the use of cookies. Learn More