Magento multisite a Nginx

Napisany przez admin, dodany w Niedziele 26 października 2014r. w kategorii Praktyczne porady Tagi: , , ,  •  Komentowanie nie jest możliwe

Wiele osób ma problem z poprawną konfiguracją Nginx-a i obsługą wielu stron lub sklepów w Magento.
W praktyce jest to wyjątkowo proste. Magento domyślnie obsługuje te wersje za pomocą dwóch zmiennych.

/* Store or website code */
$mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : '';

/* Run store or run website */
$mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store';

Mage::run($mageRunCode, $mageRunType);

W przypadku Nginx-a ustawiamy je poleceniem

          fastcgi_param MAGE_RUN_TYPE $MAGE_RUN_TYPE;
          fastcgi_param MAGE_RUN_CODE $MAGE_RUN_CODE;

Najprościej to zrobić dodając na początku pliku konfiguracyjnego prostą regułę. Np w jednym z naszych sklepów wygląda to tak :

server {
	listen		*:8080;
	server_name	domena.pl domena.de;
	access_log	/var/log/nginx/access.log;
	error_log	/var/log/nginx/error.log;
	root		/var/www/domena.pl;
        set $MAGE_RUN_TYPE "";
        set $MAGE_RUN_CODE "";
        if ($host ~* ".de$") {
                  set $MAGE_RUN_TYPE "website";
                  set $MAGE_RUN_CODE "de";
        } 
.....
....
..

W tym przypadku wykrywamy że domena kończy się na de, i tym samym ustawiamy że Nginx ma ustawić dla zmiennych taki kod sklepu.

Potem już tylko w miejscu odpowiedzialnym za nasze php-fpm dodajemy :

	### php
	location ~* \.php$ {

		try_files $uri 			=404;
		fastcgi_pass 			unix:/var/run/php5-fpm.sock;
		fastcgi_index   		index.php;
		fastcgi_param   		SCRIPT_FILENAME    $document_root$fastcgi_script_name;
		fastcgi_param   		SCRIPT_NAME        $fastcgi_script_name;
		include         		fastcgi_params;
		fastcgi_read_timeout 	3600;
		fastcgi_param 			MAGE_RUN_TYPE $MAGE_RUN_TYPE;
		fastcgi_param 			MAGE_RUN_CODE $MAGE_RUN_CODE;
	}

Nie zapomnijmy tylko w samym magento stworzyć odpowiedni website o kodzie de ;)

Magento na sterydach, nowy serwer newcraft.eu

Napisany przez admin, dodany w Niedziele 26 października 2014r. w kategorii Nasze realizacje, Opis systemu Tagi: , , , ,  •  Komentowanie nie jest możliwe

Kilka dni temu podpisaliśmy z naszym klientem nową umowę o współpracy. W tej chili nasza firma zajmuje się kompleksowym wsparciem dla sklepu opartego o Magento firmy newcraft.eu. Naszym pierwszym krokiem była migracja sklepu na serwer dedykowany, poprzedni hosting współdzielony był stanowczo niewystarczający dla tak prężnie rozwijającej się firmy.

Już teraz wiemy iż była to dobra decyzja, sklep natychmiast przyspieszył i zaczął działać jak na Magento przystało. Migracja nie przebiegła może tak gładko jakbyśmy się tego spodziewali i termin jej przeprowadzenia musieliśmy dwukrotnie przekładać. Jednak ostatecznie wszystko się udało, a czas niedostępności sklepu był mniejszy niż 20 minut. Dla pierwszych klientów sklep był widoczny już po 10 minutach od rozpoczęcie operacji.

Największego problemu przysporzył nam plugin html2pdf, który okazało się, że trzymał wszystkie ścieżki do plików w postaci bezwzględnej i wymagane było zaktualizowanie kilkunastu tysięcy rekordów w bazie danych do obsługi nowej lokalizacji sklepu.

Aktualne środowisko to wydajna maszyna dedykowana wpięta do naszego systemu zarządzania serwerami „Puppet„. Jako serwer www pracuje zoptymalizowana wersja Nginx a dzięki php w formie socketów fpm całość działa z prędkością błyskawicy. Średni czas ładowania strony spadł z 2s do zaledwie 400ms. Ponadto z powodu charakterystyki sklepu konieczne okazało się zastosowanie na froncie serwera Varnish w celu optymalizacji czasu ładowania treści statycznych. A obsługa sesji została przeniesiona do Couchbase aby odciążyć dysk od operacji IO związanych za zapisem wielu małych plików sesji.

W sklepie jeszcze sporo pracy przed nami abyśmy poczuli się w pełni zadowoleni ale widzimy iż objęta przez nas droga jest dobra. Aktualnie w planach mamy poprawę kilku błędów jakie poczynili poprzedni programiści i dalszą optymalizacje sklepu w miejscach gdzie ciągle nie jesteśmy zadowoleni z prędkości działania Magento.

Jesteśmy przekonani, że dzięki współpracy z nami nowa odsłona sklepu newcraft.eu już nigdy nie zawiedzie a strona z informacją o błędzie odejdzie do historii. Jednocześnie po raz kolejny przekonaliśmy się, że wynajem specjalistów, czy może inaczej praca na godziny, jest najlepszą formą pracy w branży e-commerce.

Nowy odświeżony szablon michelson.pl

Napisany przez admin, dodany w Niedziele 26 października 2014r. w kategorii Nasze realizacje Tagi: , , ,  •  Komentowanie nie jest możliwe

Kilka dni temu zakończyliśmy prace nad migracją sklepu naszego klienta do wersji Magento 1.9.0.1, stara wersja sklepu pracowała pod kontrolą Magento w wersji 1.6.1.0.
Prócz aktualizacji samego silnika modyfikacji doczekała się i szata graficzna. Wygląd strony przeszedł gruntowną metamorfozę. Aktualna wersja jest dużo lżejsza a jej celem było lepsze wyeksponowanie produktów. Aby uzyskać taki efekt zaimplementowane zostały algorytmy image sharpening-u pomagające wyostrzyć każde zdjęcie niezależnie od jego wielkości. Teraz każdy pierścionek wygląda wyjątkowo ślicznie ;)

michelson_v2

Dzięki tym zabiegom użytkownik może skupić się bardziej na aspekcie wizualnym strony oraz zamieszczonych na niej produktach. Strona przygotowana została w oparciu o responsive web design dzięki czemu sklep prezentuje się bardzo ładnie na urządzeniach mobilnych. Liczba kolumn dynamicznie jest dostosowywana do wielkości ekranu a menu przy małych rozdzielczościach jest zwijane nie zajmując zbędnego miejsca. Wersje responsywne również kładą nacisk na wyeksponowanie produktu i funkcjonalności. A prezentują się tak :

michelson_v3

Również od strony serwerowej, pojawiło się sporo zmian, cała platforma została przeniesiona na nową maszynę z wydajnym procesorem i dużą ilością pamięci ram. Środowisko zostało przygotowane z założeniem wpięcia go w klaster w niedalekiej przyszłości. Za serwowanie witryny odpowiada teraz najnowsza wersja wydajnego serwera nginx a php działa w oparciu o bezpieczną i wydajną technologię php-fpm. Zrezygnowaliśmy z APC na rzecz OPcache który podczas wywołania skryptu sprawdza czy kod pośredni dla wywołanej wersji skryptu jest dostępny – jeśli tak to wywołuje istniejącą już kod pośredni. Natomiast Couchbase przechowuje cache Magento i Sesje co pozwoliło mocno ulżyć IO Dysku. Baza danych została zmigrowana do Percony zapewniając dużą stabilność.

Na samym froncie stoi Varnich odpowiedzialny głównie za chacowanie treści statycznych, co dodatkowo odciąża serwer www i znacząco przyspiesza ładowanie się strony.

Dzięki naszemu doświadczeniu cały proces przełączania sklepu i migracji trwał mniej niż 20 minut, a godziny jego przeprowadzenia zostały wybrane na czas najmniejszego obciążenia sklepu.

Szybki import stanów magazynowych w Magento

Napisany przez admin, dodany w Niedziele 26 października 2014r. w kategorii Nasze rozwiązania, Praktyczne porady Tagi: , ,  •  Komentowanie nie jest możliwe

Często zachodzi potrzeba zaimportowania stanów magazynowych z innego systemu. Najczęściej w tym celu wykorzystywane jest API, jednak ma ono jedną podstawową wadę, jest bardzo powolne. Kiedy potrzebujemy w krótkim czasie dokonać zmiany kilku tysięcy produktów, to wykorzystanie API może być nie najlepszym rozwiązaniem z powodu jego bardzo dużej pracochłonności i narzutu na zasoby.

Innym popularnym rozwiązaniem jest napisanie własnego skryptu mającego realizować to zadanie w oparciu o modele Magento.
Poniżej prezentujemy przykładowy skrypt importujący stany magazynowe na podstawie pliku CSV, oczywiście jako źródło wejściowe posłużyć inna baza danych lub API programu magazynowego.

ini_set("display_errors", 1);
ini_set("memory_limit","1024M");
 
require_once 'app/Mage.php';
umask(0);
 
$currentStore = Mage::app()->getStore()->getId();
Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
 
$path = getcwd().'/WebStockInput.csv';
$readfile = file ($path);
for ($i=1; $i < count($readfile); $i++ ) {
    $fields = split("\t",$readfile[$i]);
     
    $product = Mage::getModel('catalog/product');
    $product->load($product->getIdBySku($fields[0]));
    // get product's stock data such quantity, in_stock etc
    $stockData = $product->getStockData();
 
    // update stock data using new data
    $stockData['qty'] = $fields[2];
    $stockData['is_in_stock'] = 1;
 
    // then set product's stock data to update
    $product->setStockData($stockData);
    try {
        $product->save();
    }
    catch (Exception $ex) {
        echo $ex->getMessage();
    }
 
}
 
Mage::app()->setCurrentStore($currentStore);
?>

Niestety i to rozwiązanie nie należy do zbyt szybkich. Dlatego poniżej prezentujemy jak my to robimy. Nasz skrypt pozwala na zmianę stanów kilku tysięcy produktów w mniej niż 120 sekund. Został przetestowany w wielu rozwiązaniach i jak na razie sprawdza się bardzo dobrze. Jeżeli zajdzie potrzeba dostosowania go do współpracy z innymi programami magazynowymi lub źródłami danych zachęcamy do kontaktu z nami.

Konfiguracja
Aby skrypt działał poprawnie należy ustawić zmienną $workdir na folder z plikiem wejściowym o nazwie magento2.csv.
W tym też folderze skrypt zapisze raport z wynikiem swojej pracy.

//Centerkom.pl - najlepsze rozwiązania Magento

$workdir - "/var/www/import/"

function getmicrotime(){list($usec, $sec) = explode(" ",microtime());return ((float)$usec + (float)$sec);}
$time_start = getmicrotime();


$mageFilename = "../app/Mage.php";
require_once $mageFilename;
Mage::setIsDeveloperMode(true);
ini_set("display_errors", 1);
umask(0);
Mage::app("admin");
Mage::register("isSecureArea", 1);
Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID);
 
set_time_limit(0);
ini_set("memory_limit","1024M");
 
/***************** UTILITY FUNCTIONS ********************/
function _getConnection($type = "core_read"){
    return Mage::getSingleton("core/resource")->getConnection($type);
}
 
function _getTableName($tableName){
    return Mage::getSingleton("core/resource")->getTableName($tableName);
}
 
function _getAttributeId($attribute_code = "price"){
    $connection = _getConnection("core_read");
    $sql = "SELECT attribute_id
                FROM " . _getTableName("eav_attribute") . "
            WHERE
                entity_type_id = ?
                AND attribute_code = ?";
    $entity_type_id = _getEntityTypeId();
    return $connection->fetchOne($sql, array($entity_type_id, $attribute_code));
}
 
function _getEntityTypeId($entity_type_code = "catalog_product"){
    $connection = _getConnection("core_read");
    $sql        = "SELECT entity_type_id FROM " . _getTableName("eav_entity_type") . " WHERE entity_type_code = ?";
    return $connection->fetchOne($sql, array($entity_type_code));
}
 
function _checkIfSkuExists($sku){
    $connection = _getConnection("core_read");
    $sql        = "SELECT COUNT(*) AS count_no FROM " . _getTableName("catalog_product_entity") . " WHERE sku = ?";
    $count      = $connection->fetchOne($sql, array($sku));
    if($count > 0){
        return true;
    }else{
        return false;
    }
}
 
function _getIdFromSku($sku){
    $connection = _getConnection("core_read");
    $sql        = "SELECT entity_id FROM " . _getTableName("catalog_product_entity") . " WHERE sku = ?";
    return $connection->fetchOne($sql, array($sku));
}
 
function _updateStocks($data){
    $connection     = _getConnection("core_write");
    $sku            = $data[0];
    $newQty         = $data[1];
    $productId      = _getIdFromSku($sku);
    $attributeId    = _getAttributeId();
 
    $sql            = "UPDATE " . _getTableName("cataloginventory_stock_item") . " csi,
                       " . _getTableName("cataloginventory_stock_status") . " css
                       SET
                       csi.qty = ?,
                       csi.is_in_stock = ?,
                       css.qty = ?,
                       css.stock_status = ?
                       WHERE
                       csi.product_id = ?
                       AND csi.product_id = css.product_id";
    $isInStock      = $newQty > 0 ? 1 : 0;
    $stockStatus    = $newQty > 0 ? 1 : 0;
    $connection->query($sql, array($newQty, $isInStock, $newQty, $stockStatus, $productId));
}
/***************** UTILITY FUNCTIONS ********************/
 
$csv                = new Varien_File_Csv();
$data               = $csv->getData($workdir/"magento2.csv"); //path to csv
array_shift($data);
 
$message = "";

$count   = 1;
$count_success = 0;
foreach($data as $_data){
    if(_checkIfSkuExists($_data[0])){
        try{
            _updateStocks($_data);
            $message .= $count . "> Success:: Qty (" . $_data[1] . ") of Sku (" . $_data[0] . ") has been updated. \n";
            //echo $count . "> Success:: Qty (" . $_data[1] . ") of Sku (" . $_data[0] . ") has been updated. \n";
            $count_success++;
 
        }catch(Exception $e){
            $message .=  $count ."> Error:: while Upating  Qty (" . $_data[1] . ") of Sku (" . $_data[0] . ") => ".$e->getMessage()."\n";
            echo $count ."> Error:: while Upating  Qty (" . $_data[1] . ") of Sku (" . $_data[0] . ") => ".$e->getMessage()."\n";
        }
    }else{
        $message .=  $count ."> Error:: Product with Sku (" . $_data[0] . ") does't exist.\n";
        //echo  $count ."> Error:: Product with Sku (" . $_data[0] . ") does't exist.\n";
    }
    $count++;
}


$fp = fopen($workdir.date("Ymd"), 'w');
$time_end = getmicrotime();
$time = $time_end - $time_start;

fwrite($fp, $message);
fwrite($fp, "\nZmieniono :".$count_success);
fwrite($fp, "\nCzas wygenerowania strony $time s"); 
fwrite($fp, "\nmemory_get_usage ".((memory_get_usage()/1024)/1024));
fwrite($fp, "\nreal_memory_get_usage ".((memory_get_usage(true)/1024)/1024));

Współpraca przy newcraft.eu

Napisany przez admin, dodany w Niedziele 26 października 2014r. w kategorii Nasze realizacje Tagi:  •  Komentowanie nie jest możliwe

Wdrożenie przy którym mieliśmy okazję współuczestniczyć, nasza rola ograniczyła się do konfiguracji i instalacji Magento na serwerze klienta.
Ponadto specjalnie na potrzeby systemy newcraft.ey wykonania kilka modułów umożliwiających osiągnięcie zamierzonego przez klienta efektu.