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ć.
.png.022b5452a8f28f552bc9430097a16da2.png)