Hogyan fejlessz extrém gyors Magento indexelő megoldást?

Az adatbázisban lévő adatoknak egy részét feldolgozatlanul, úgymond nyers formában jelenítjük meg a felhasználónak, más adatokat feldolgozva jelenítünk meg, és vannak adatokat, melyeket egyáltalán nem jelenítünk meg. Az adatokat a lehető legkisebb egységekre bontva kell tárolni. Ez az adatbázis normalizálás egyik alapfeltétele, amely meghatározza, hogy az adatbázis tábla egy oszlopa egy elemi érték legyen.

A normalizált adatbázis következménye a hatékony információ tárolás, viszont ez lassítja a feldolgozást és a megjelenítést. Nem normalizált adatbázis esetén megjelennek az anomáliák, így ebből nem engedhetünk. Viszont a felhasználónak gyorsan szeretnénk megjeleníteni az adatot. Megoldás: az adatokat egy közös táblába szervezzük, melyből feldolgozás nélkül tudjuk az adatok megjeleníteni. A közös adatokat tartalmazó tábla a flat tábla, és az ezt karbantartó eljárás az indexelés. A cikk bemutatja azokat a szemléleteket, melyekkel a Magento keretrendszerét kihasználva gyorsíthatjuk a feldolgozást és megjelenítést, anélkül, hogy engednénk az adatbázis normalizáltságából.

 

A cikk a következő témákat fogja érinteni:

  • A nem normalizált adatbázis anomáliái
  • Indexelővel kapcsolatban támasztott követelmények
  • Eseményvezérelt indexelés
  • Indexelés megvalósítása Magento-ban
  • Konkluzió

 

magento indexelés flat tábla fejlesztő

 

A nem normalizált adatbázis anomáliái

 

Jelen cikkben csak az anomáliák okát és következményeit tárgyaljuk, azoknak a kezelésére nem térünk ki.

A normalizált adatbázis előfeltétele, hogy a tábla minden oszlopa, vagyis egy rekord minden eleme, egyetlen elemi érték legyen. Ne legyenek megegyező sorok, és a sorok sorrendje ne hordozzon információt.

 

Háromféle anomália jelentkezhet nem normalizált adatbázis esetén:

  1. Módosítási
  2. Beírási
  3. Törlési

 

  1. Módosítási anomália akkor jelentkezik, ha egy attribútum több táblában szerepel. Ez esetben módosításkor több helyen kell módosítani, ha ez nem történik meg, akkor az adatbázisunk inkonzisztens lesz.
  2. Beírási anomália akkor keletkezik, ha egy információ hiánya miatt egy sort nem tudunk felvinni. Ennek eredménye információvesztés.
  3. Ha olyan adatokat törlünk, melyekre még szükség lenne, akkor törlési anomáliáról beszélünk. Ez esetben is információvesztés a következmény.

Ahhoz, hogy az anomáliákat elkerüljük, adatbázisunkat normalizálnunk kell adatbázis normálformák szerint.

 

Indexelővel kapcsolatban támasztott követelmények

 

Az indexelő feladata, hogy gyorsítsa az adatok megjelenítését. Viszont, ahogy a fentiekben kiderült, az indexelt tábla nem elemi adatokat tartalmaz, hanem már feldolgozott adatokat. Így már elemi szinten megbukik a normalizáltsági teszten. Ezért bizonyos követelményeket kell vele szemben támasztani.

 

A fő követelményt nagyon egyszerű megfogalmazni:

  • Ha egy flat táblát törlünk rendszerből, akkor az ne okozzon anomáliát.
  • A rendszer flat tábla nélkül is működjön. -> Flat tábla nem a rendszer része.

 

Az első követelményt úgy tudjuk teljesíteni, ha a táblába csak az indexelő eljárás ír, és minden teljes indexelés előtt töröljük a táblát.

Mi van akkor, ha olyan adatot törlünk, amire szükség lett volna?

A válasz triviális. Nem tudunk olyan adatot törölni, a flat tábla nem a rendszer része, csak segít megjeleníteni az adatokat. A rendszernek nélküle is kell tudni működnie.

tips Példa: Összeállítjuk a flat táblát, de közben törlünk egy rekordot, melyre már nem volt szükség. Ekkor a flat táblában szereplő adatok már nem lesznek aktuálisak. Amikor újraindexelünk, lesznek olyan adatok, melyek már nem kellenek. Ha nem töröljük a flat táblát, akkor ellenőriznünk kellene, hogy van-e létjogosultsága az adott értékeknek, ami hosszú és bonyolult folyamat. Az egész tábla törlése és újra felépítése a legjobb megoldás. Ezt a későbbiekben említett egyéb okok is alátámasztják.

A gyorsulás oka, hogy csak azok az adatok szerepelnek a táblában, melyekre szükség van. Viszont ezeket az adatokat karban kell tartani, az igényeknek megfelelően a lehető legfrissebb állapotot biztosítani. A rendszer legfőbb szempontja a gyorsaság és a hatékonyság. Viszont az alrendszerek feladatmegoldási hatékonysága között nagyon nagy eltérések vannak.

 

Általános megoldás:

magento indexelés folyamat flat tábla

1. Indexelési folyamat vázlata

 

Az ábrán a lépések a következők:

  1. PHP lekéri az indexelendő rekordokat. Ezeket több táblából, több modellen keresztül.
  2. Ezután a MySQL elküldi a kért adatokat.
  3. A PHP feldolgozza a rekordokat ciklusok segítségével. Gyakran több egymásba ágyazott ciklussal.
  4. PHP egyenként visszaküldi a rekordokat.
  5. MySQL visszaírja.

Előnye: Egyszerű, átlátható logika.

Hátránya: Nagyon lassú. A flat táblával kapcsolatos követelmények kielégítésére alkalmatlan. Vannak olyan rendszerek, ahol kielégítő teljesítményt nyújt, de használata nem ajánlott.

 

Optimális megoldás:

optimális magento indexelési folyamat flat tábla

2. Indexelés folyamata

 

Optimalizált lépések:

  1. A PHP modellek segítségével összeállítja a flat táblát visszaadó lekérdezést.
  2. MySQL ezt a SELECT-et végrehajtja és beírja a kapott táblát az adatbázisba.

Előnye: Nagyon gyors, esetenként akár 30-szoros gyorsulás. Kiküszöböli a két rendszer közötti kommunikáció veszteséget. Kielégíti a követelményeket.

Hátránya: A lekérdezés gyakran nagyon bonyolult és átláthatatlan. Ritkán előfordulhat olyan eset, amikor a MySQL nem rendelkezik a megfelelő tudással, hogy a feldolgozott adatot elő tudja állítani. Ekkor egy optimalizált hibrid megoldást kell használni, amely feladatfüggő.

 

Eseményvezérelt indexelés

 

Az indexelés folyamatát indíthatjuk manuálisan, esetleg cron segítségével. Ebben az esetben teljes reindexelésre van szükség, hisz nem tudjuk, mely rekordok nem aktuálisak. Dilemmát okoz az is, hogy milyen gyakran fusson a reindex.

A teljes reindex nélkülözhetetlen, hiszen sebesség szempontjából meghatározó, hogy csak a releváns adatok vannak a táblában. Viszont minden egyes módosulás után lefuttatni költséges, és többet veszítünk vele, mint amit nyerünk. Ennek kiküszöbölése érdekében ki kell alakítani olyan folyamatokat, amelyek csak adott sorokat indexelnek. Ez nagyon egyszerű, hiszen a SELECT lekérdezés WHERE feltételében leszűrjük a rekordokat az adott feltétel szerint.

Az adatbázis táblákat úgy alakítjuk ki, hogy a beszúrásnál a MySQL automatikusan tudja, hogy egy rekord új, vagy már létező rekord és csak módosítani kell.

 

Két dolgot kell meghatározni:

  1. Mikor fusson a reindex?
  2. Mely rekordok legyenek reindexelve?

 

Akkor kell futnia a reindexnek, ha egy adat a forrás táblában megváltozik, és azokon a rekordon, melyben az adat van.

Megvalósítás: Eseményeket definiálunk, melyek bekövetkeztekor tudjuk, hogy változtak adatok a forrás táblában. Ha az esemény bekövetkezik, akkor az adott rekordokra lefuttatjuk az indexet.

 

Indexelés megvalósítása Magento-ban

 

Flat tábla kialakítása

A flat táblát úgy kell kialakítani, hogy beszúrásnál a MySQL meg tudja állapítani, hogy új rekorddal vagy egy létező rekorddal van dolga. Erre a megoldás az egyedi index.

config.xml: Meg kell adnunk a táblánk nevét.

<entities>
    <index_table>
        <table>custom_index_table</table>
    </index_table>
</entities>

Regisztráljuk az indexelőt:

<global>
    ......
    <index>
        <indexer>
            <some_key>
                <model>module/model</model>
            </some_key>
        </indexer>
    </index>
    ......
</global>

Code: Installer-ben hozzáadjuk az unique indexet.

$table->addIndex(
    $installer->getIdxName(
        'your_namespace/your_table',
        array(
            'column1',
            'column2',
            'column3',
        ),
        Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE
    ),
    array(
        'column1',
        'column2',
        'column3',
    ),
    array('type' => Varien_Db_Adapter_Interface::INDEX_TYPE_UNIQUE)
);

 

Ezzel semlegesítettük, hogy a rekordok többszörösen is szerepeljenek a táblában.

 

Az index folyamat megvalósítása

A tényleges függvényeket egy helper-ben valósítjuk meg.

3 függvényt kell implementálnuk:

  • runReindex($id) – privát
  • reindexAll() – publikus
  • reindexById($id) – publikus

 

Runindex metódus

 

Első körben beállítjuk az adatbázis adatptert:

$resource = Mage::getSingleton('core/resource');
$adapter = $resource->getConnection('core_write');

Ezután lekérdezzük a modellt, melyhez hozzákapcsoljuk (join) a többi táblát:

$collection = Mage::getModel('namespace/model')
    ->getCollection()
    ->removeAllFieldsFromSelect()
    ->removeFieldFromSelect('id');

A SELECT összes oszlopát kivesszük, hogy azt majd az indextáblához tudjuk igazítani. Ezután összekapcsoljuk (join) a táblákat, melyekből még szükség van adatra.

Például ORDER ITEM join:

$collection->getSelect()->joinLeft(
    array('order_item' => Mage::getSingleton('core/resource')->getTableName('sales/order_item')),
    'order_item.order_id = main_table.order_id',
    array()
);

Ezután beállítjuk a flat táblával ekvivalens oszlopneveket és oszlopsorrendet tartó struktúrát.

$columns = array(
    'column1',
    'column2',
    'column3',
);

$collection->getSelect()
    ->columns('wishlist_item.product_id AS column1')
    ->columns('GROUP_CONCAT(customer_id SEPARATOR ",") AS column2')    ->columns('SUM(wishlist_item.qty) AS column3');

Előállítjuk a flat táblát adó lekérdezést:

$select = $collection->getSelect();

Futtatjuk a lekérdezést és beszúrjuk a táblába:

$sql = $adapter->insertFromSelect($select,
    Mage::getSingleton('core/resource')->getTableName('namespace /custom_index_table'),
    $columns,
    Varien_Db_Adapter_Interface::INSERT_ON_DUPLICATE);

$adapter->query($sql);

 

Amint látjuk, a kommunikáció minimális a két alrendszer között. A PHP átadja a flat táblát visszaadó lekérdezést. A MySQL futtatja ezt és beírja az adatbázisba.

 

ReindexById metódus

A SELECT rekordjait le kell szűrni:

$collection->getSelect()->where('id = '.$id); 

 

ReindexAll

Az index táblát ürítjük. Lekérdezzük az összes rekord azonosítóját és meghívjuk a runReindex($id) metódust.

 

Események jelzése

<?php
class Namespace_Model_Model extends Mage_Sales_Model_Order_Item
{
    const ENTITY = 'namespace_model_model';

     /**
      * Before Delete
      */
     protected function _beforeDelete()
     {
         parent::_beforeDelete();

         Mage::getSingleton('index/indexer')->logEvent(
             $this, self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE
         );
     }

     /**
      * Before Save
      */
     protected function _beforeSave()
     {
         parent::_beforeSave();

         Mage::getSingleton('index/indexer')->logEvent(
            $this, self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE
         );
     }

     /*
      * After Save Commit
      */
     protected function _afterSaveCommit()
     {
         parent::_afterSaveCommit();

         Mage::getSingleton('index/indexer')->indexEvents(
             self::ENTITY, Mage_Index_Model_Event::TYPE_SAVE
         );
     }

     /*
      * After Delete Commit
      */
     protected function _afterDeleteCommit()
     {
         parent::_afterDeleteCommit();

         Mage::getSingleton('index/indexer')->indexEvents(
             self::ENTITY, Mage_Index_Model_Event::TYPE_DELETE
         );
     }

 }

Az adat 2 esetben változhat. Módosítás és törlés esetén, így ezekre kell az eseményeket ütemezni. Amint látjuk, a Magento megkülönbözteti az index eseményeket. A programozó belátása a későbbiekben, hogy melyik eseményeket kell figyelnie az indexelőnek.

Ha egy olyan eseményre akar feliratkozni az indexer, amely nincs jelezve, és a Magento magban (core) található, akkor felül kell írni az eredeti osztályt. Ennek az osztálynak az eredeti osztályból kell származnia.

 

Magento Indexer megvalósítás

A modulunk modell könyvtárába létre kell hozni az indexelő osztályt, amely figyeli az eseményeket és futtatja az indexelő folyamatokat. Ennek az osztálynak a Mage_Index_Model_Indexer_Abstract osztályból kell származnia.

class Namespace_Model_Indexer extends Mage_Index_Model_Indexer_Abstract

Következő lépésben fel kell iratkozni az eseményekre, ezt egy osztály tömbbe deklaráljuk:

/**
 * Index matched Entities array
 *
 * @var array
 */
protected $_matchedEntities = array(
    Namespace_Model_Model::ENTITY => array(
        Mage_Index_Model_Event::TYPE_SAVE,
        Mage_Index_Model_Event::TYPE_MASS_ACTION,
        Mage_Index_Model_Event::TYPE_DELETE
    ),
);

 

Kicsit feljebb deklaráltuk a modell eseményeit. A fenti kódból láthatjuk az osztályban található ENTITY konstans funkcióját. Ezzel azonosítjuk a modellt. Meg kell valósítani az absztrakt metódusokat:

/**
 * @return bool
 */
public function isVisible()
{
    return true;
} 

/**
 * Retrieve Indexer name
 *
 * @return string
 */
public function getName()
{
    return Mage::helper('namespace')->__('Custom indexer');
} 

/**
 * Retrieve Indexer description
 *
 * @return string
 */
public function getDescription()
{
    return Mage::helper('namespace')->__('Reorganize custom flat data');
} 

/**
 * Rebuild all index data
 */
public function reindexAll()
{
    Mage::helper('namespace/indexer')->reindexAll();
}

 

Esemény felismerés és kezelés

Ezt a _registerEvent metódus megvalósításán keresztül tudjuk megtenni.

/**
 * Register indexer required data inside event object
 *
 * @param   Mage_Index_Model_Event $event
 */
protected function _registerEvent(Mage_Index_Model_Event $event)
{
    $dataObj = $event->getDataObject();
    if($event->getType() == Mage_Index_Model_Event::TYPE_SAVE){
        $event->addNewData('id, $dataObj->getId());
    }elseif($event->getType() == Mage_Index_Model_Event::TYPE_DELETE){
        $event->addNewData('id, $dataObj->getId());
    }
}

Detektáljuk, milyen esemény történt, és hozzáadjuk az indexeléshez szükséges adatokat. Példánkban a modell azonosítót, mivel ez alapján indexelünk. De az események kezelése lehet egyedi, és igényelhet különböző adatokat.

 

Indexelés futtatás

 A tényleges indexelés a _proccessEvent metóduson keresztül történik.

/**
 * Process event based on event state data
 *
 * @param   Mage_Index_Model_Event $event
 */
protected function _processEvent(Mage_Index_Model_Event $event)
{
    $data = $event->getNewData();
    if(!empty($data['id'])){
        Mage::helper('namespace/indexer')->reindexById((int)$data['id']);
    }
}

 

Összefoglalás

A gyorsaság optimalizálása minden rendszer esetében az elsődleges szempontok között szokott lenni. (Kivéve a banki alkalmazásokat, ahol a biztonság az egyetlen szempont.) A flat táblák a megjelenítés gyorsaságát tudják biztosítani, mellyel már van egy piros pontunk a felhasználónál.

Használatuk összetett entitások esetén javasolt, ahol nagyon sok táblában van az információ. Mivel a szűk keresztmetszetet az adatbázis szegmens okozza, így a problémát itt kell kezelni, és a szegmensek között a kommunikációt minimalizálni kell. A flat táblák használatának előnye a gyorsaság, mellyel elérjük, hogy a felhasználó kényelmesen tudja böngészni oldalunkat, és gyorsan megtalálja, amit keres.

 

0 válaszok

Hagyjon egy választ

Want to join the discussion?
Feel free to contribute!

Vélemény, hozzászólás?

Az email címet nem tesszük közzé.