Chamada, filtragem e carregamento de coleções eficientes

15

No momento, estou reutilizando muitas coleções aninhadas nos loops foreach. É possível subir essas coisas alguns níveis? Atualmente, sou forçado a recarregar coleções com mais de 51k entidades, o que torna as coisas tremendamente mais lentas. Especificamente as coleções do kitinventory.

<?php
class Codespace_Module_Helper_Item extends other_one{

function functionOne($collection){
    ...
    $data = $collection->getData();
    foreach($data as $item){
        $this->_functionTwo($item);
    }
    ...
}

function _functionTwo($item){
    $model = Mage::getModel('catalog/product');
    $id = $model->getIdBySku($item['sku']);
    $inventoryStatus = Mage::getResourceSingleton('catalog/product')->getAttributeRawValue($id, 'product_inventory_status', 1);
    $invStatus = $model->getResource()->getAttribute('product_inventory_status')->getSource()->getOptionText($inventoryStatus);
    if ($invStatus && $id) {
        if ($invStatus !== 'Z') {
            $stockItem = Mage::getModel('cataloginventory/stock_item');
            $stockItem->setData(array());
            $stockItem->loadByProduct($id);
            if ($stockItem->getQty() != $item['quantity']) {
                $stockItem->setQty(item['quantity']);
                $stockItem->save();
                $this->functionThree($item['sku']);
            }
        }
    }
}

function functionThree($sku){
    $collectionOfKits = Mage::getModel('kitinventory/kitinventory')->getCollection()->addFieldToFilter('related_sku',$sku);
    if($collectionOfKits->getSize()){
        foreach($collectionOfKits as $kit){
            $kitSku = $kit->getSku();
            $kitCollection = Mage::getModel('kitinventory/kitinventory')->getCollection()->addFieldToFilter('kit_sku',$kitSku)->setOrder('related_sku','ASC');
            ...
            foreach($kitCollection as $component){
                $componentSkus[] = $component->getRelatedSku();
                $componentRequiredQuantity[] = $component->getRequiredQuantity();
            }
            $componentProductCollection = Mage::getModel('catalog/product')->getCollection();
            $componentProductCollection->joinField('qty',
                'cataloginventory/stock_item',
                'qty',
                'product_id=entity_id',
                '{{table}}.stock_id=1',
                'left');
            $componentProductCollection->addAttributeToFilter('sku', array('in' => $componentSkus));
            foreach($componentProductCollection as $component){
                $quantity = $component->getQty();
                ...
            }
            $kitId= Mage::getModel('catalog/product')->getIdBySku($kitSku)
            $kitStockItem = Mage::getModel('cataloginventory/stock_item')->loadByProduct($kitId);
            $this->functionFour($kitStockItem,$kitSku,$amountOfKitsPossible);
        }
    }
}

function functionFour($kitStockItem,$kitSku,$amountOfKitsPossible){
    ...
    $kitStockItem->setQty($quantity);
    $kitStockItem->save();
    ...
}

EDIT: esta é a funcionalidade atual que eu criei, ainda acho que existe uma maneira melhor de lidar com essas coleções.

easymoden00b
fonte
Para que tipo de coleção é passada functionOne($collection)? Em que ordem seria o tamanho / número de itens? É necessário fazer um loop sobre ele para obter os SKUs?
7ochem
@ 7ochem É uma coleção personalizada criada a partir de novos dados de inventário que obtemos de nosso sistema de gerenciamento de inventário. ele contém o nome do item, a quantidade do item disponível e o sku do item. Pode conter potencialmente 60k + elementos.
Easyden00b

Respostas:

9

Existem algumas coisas em que você pode trabalhar;

  • não passando por referência; portanto, usando memória extra, você pode passar objetos, mas as matrizes não podem ser passadas por referência por padrão. Ou adicione um &na declaração de parâmetro de função comofunction hello(array &$world)
  • cheques inválidos, se algo não existir, retorne imediatamente. Não tente encontrar algo que não está lá
  • a legibilidade pode ser difícil às vezes
    • adicione algum documento (para que você possa entender se o vir dentro de alguns dias, meses, anos)
    • ifdeclarações mais inteligentes para obter menos recuo
  • As funções devem ter apenas um propósito, atualizar o estoque ou atualizar os relacionados, não os dois, portanto, talvez até corte algumas funções em funções ainda menores. Tente criar essa lógica em sua mente e refaça a partir daí.
  • Dê uma olhada ->cleanModelCache()->clearInstance()de Mage_Core_Model_Model_Abstractpara limpar os dados subjacentes para alguns objetos, pode acelerar as coisas.
  • de grosseiro todas as outras coisas que já foram ditas.

Adicionada uma versão atualizada do seu código com algumas recomendações embutidas no seu código atual, eu poderia continuar um pouco, mas atualmente não adicionaria mais.

Função 1: Objetivo é caminhar pela coleção

    /**
     * Walk collection
     * 
     * @param Mage_Core_Model_Resource_Db_Collection_Abstract $collection
     * @return void
     */
    public function functionOne($collection)
    {
        // ...

        // Walk collection, create references instead of passing array data
        foreach ($collection as $item) {

            // Update stock for product
            if (!$this->_functionTwo($item)) {
                // Not updated, continue next
                continue;
            }

            // Update related products
            $this->_functionThree($item); // Use same object again, no extra memory is used
        }

        // ...
    }

Função 2: Objetivo é atualizar o estoque se alterado

    /**
     * Update stock item if changed, returns true if updated
     * 
     * @param Mage_Core_Model_Model_Abstract $item
     * @return bool
     */
    function _functionTwo($item)
    {
        $model = Mage::getModel('catalog/product');
        /** @var $model Mage_Catalog_Model_Product */

        $id = $model->getIdBySku($item->getData('sku'));

        if (!$id) {
            // no id found, so stop looking nothing up
            return false;
        }

        // Get option value for store 1
        $inventoryStatus = $model->getResource()
                ->getAttributeRawValue($id, 'product_inventory_status', 1);

        if (!$inventoryStatus) {
            // No need for another lookup in db, because the status isn't set
            return false;
        }

        $invStatus = $model->getResource()
                ->getAttribute('product_inventory_status')
                ->setStoreId(0) // Get admin value
                ->getSource()
                ->getOptionText($inventoryStatus);

        if (!$invStatus) {
            // No need for another lookup in db, because the status has no text
            return false;
        }

        if ($invStatus === 'Z') {
            // Inventory status to not change something
            return false;
        }

        $stockItem = Mage::getModel('cataloginventory/stock_item');
        /** @var $stockItem Mage_CatalogInventory_Model_Stock_Item */

        // $stockItem->setData(array()); // unneeded piece of code
        $stockItem->loadByProduct($id);

        if ($stockItem->getQty() == $item->getData('quantity')) {
            // Valid stock
            return false;
        }

        // Update stock
        $stockItem->setQty($item->getData('quantity'));
        $stockItem->save();

        // End function and call function three separately, does something else
        return true;
    }

Função 3: Objetivo de atualizar itens de estoque relacionados

    /**
     * Update related stock items, return false if no related items are found
     * 
     * @param Mage_Core_Model_Model_Abstract $item
     * @return bool
     */
    function functionThree($item)
    {

        $collectionOfKits = Mage::getModel('kitinventory/kitinventory')
                ->getCollection()
                ->addFieldToFilter('related_sku', $item->getData('sku')); // Check if your indexes are set on these columns

        if (!$collectionOfKits->getSize()) {
            // Nothing found to relate to
            return false;
        }

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

        // Walk kits
        foreach ($collectionOfKits as $kit) {

            // getData is slightly faster then getSku(unless you've implemented it in your model)
            // getSku -> __call('getSku') -> get -> lowercase('sku') -> getData('sku') | note, Magento has some internal caching in this 
            $kitSku = $kit->getData('sku');

            $kitCollection = Mage::getModel('kitinventory/kitinventory')
                    ->getCollection()
                    ->addFieldToFilter('kit_sku', $kitSku)
                    ->setOrder('related_sku', 'ASC');

            // Use just a fetchAll to create a fast db query
            $select = $kitCollection->getSelect();

            $select->reset(Zend_Db_Select::COLUMNS)
                    ->distinct()
                    ->columns('related_sku')
                    ->columns('required_quantity');

            // Fetch component sku
            $componentSkus = $connection->fetchAll($select, 0);

            // Fetch required quantity
            $componentRequiredQuantity = $connection->fetchCol($select, 1);

            // ...

            $componentProductCollection = Mage::getModel('catalog/product')
                    ->getCollection()
                    ->joinField('qty',
                    'cataloginventory/stock_item',
                    'qty',
                    'product_id = entity_id',
                    '{{table}}.stock_id = 1',
                    'left');
            $componentProductCollection->addAttributeToFilter('sku', array('in' => $componentSkus));

            // Next line will invoke a load on the product collection
            foreach ($componentProductCollection as $component) {
                $quantity = $component->getQty();

                // ...

            }
            // You could choose to do a fetchAll here instead to get just the data you need
            $connection = $componentProductCollection->getConnection();

            foreach ($connection->fetchAll($componentProductCollection->getSelect()) as $row) {
                // Will have a array here
                $quantity = $row['quantity'];

                // ... -- do not not which funky magic happens here
            }


            $kitId = Mage::getModel('catalog/product')
                    ->getIdBySku($kitSku);
            if (!$kitId) {
                // No id
                continue;
            }

            // You could also take a look if you can sum the stock and do a single update instead
            $kitStockItem = Mage::getModel('cataloginventory/stock_item')
                    ->loadByProduct($kitId);
            $this->functionFour($kitStockItem, $kitSku, $amountOfKitsPossible);

            // Or something like this, update single field
            $connection->update($kitStockItem->getResource()->getMainTable(), array('qty' => $quantity), 'item_id = ' . $kitStockItem->getId());
        }

        return true;
    }

Função 4: teve que fazer algumas suposições de sorte (ou azar), pois agora é uma função inútil, pode ser adicionada como na Função 3.

    /**
     * Save stock item if changed and something else, rather not say ;-)
     * 
     * @param Mage_Catalog_Inventory_Model_Stock_Item $kitStockItem
     * @param string $kitSku
     * @param int $amountOfKitsPossible Guessed it
     */
    function functionFour($kitStockItem, $kitSku, $amountOfKitsPossible)
    {

        // ...

        // Do not know the rest of the code, so I wouldn't know which I could optimize here
        // If it isn't to serious, you could look at a single query and not hitting extra functions

        // Check if changed
        if ($quantity !=$kitStockItem->getData('qty')) {
            $kitStockItem->setQty($quantity);
            $kitStockItem->save();
        }        

        // ...

    }
}
Jeroen
fonte
Você é o cara. Estou relativamente certo de que isso funcionará, e se isso mostra uma melhoria distinta no tempo de processamento, pode ser minha referência para manipular coleções!
easymoden00b
Alguns pequenos erros, mas é muito melhor construído do que o meu.
Easyden00b
5

Eu queria adicionar isso como um comentário, mas ainda não tenho representante suficiente. Veja como as redes principais do Magento associam a quantidade do produto à coleção de catálogos / produtos aqui: https://github.com/OpenMage/magento-mirror/blob/magento-1.9/app/code/core/Mage/Adminhtml /Block/Catalog/Product/Grid.php#L65

Se você ingressar na tabela para obter o qty, não precisará chamar isso em um loop: Mage::getModel('cataloginventory/stock_item')->loadByProduct($product)->getQty();

$productCollection = Mage::getModel('catalog/product')->getCollection();
$productCollection->joinField('qty',
    'cataloginventory/stock_item',
    'qty',
    'product_id=entity_id',
    '{{table}}.stock_id=1',
    'left');
$productCollection->addAttributeToFilter('sku',array('in' => $relatedSkus));
foreach($productCollection as $product){
    $quantity = $product->getQty();
    ...// now you have your qty without having to load the product model.
}

A outra alternativa é verificar se você pode armazenar em cache os resultados desse processo intensivo do sistema. Talvez você possa criar uma segunda tabela de banco de dados para armazenar os resultados e atualizá-la como faria um índice magento.

Eric Seastrand
fonte
Gostaria de compartilhar como eu poderia chamar essas informações de maneira semelhante ao código acima? Além disso, se eu gerar as coleções de produtos e atribuí-las a uma variável de classe, eu poderia chamar essa coleção por todo o código? A filtragem (conforme mostrado no código) afetaria a variável de classe ou permaneceria inalterada se eu atribuísse essa coleção filtrada a outra variável?
Easyden00b
Adicionei um exemplo de como ingressar no campo, mas essa é apenas uma pequena otimização. Sim: você pode salvar os resultados como variáveis ​​de classe e chamá-los em outro lugar. Mas: Eu acho que você vai ter que instanciar um novo modelo a cada vez que você quiser filtragem mudança (pode derrotar o efeito.)
Eric Seastrand
Obrigado, parece que temi. Obrigado por este exemplo de otimização. Algum outro em que você pode pensar? Explicarei um pouco os usos de cada coleção no exemplo de código acima.
easymoden00b
3

Você não precisa recarregar o modelo repetidamente, Mage::getModel() basta uma referência sem saber como é difícil dizer se seus modelos de recursos são difíceis de dizer se ele é reinicializado toda vez na memória e nesses loops você acaba vazando / ficando sem memória causando possível troca de disco.

Uma coleção para governar todos eles. Refatorando as funções para fazer referência apenas a uma coleção. É o mesmo com SQL padrão e programação procedural. Demore um pouco mais a investigar suas coleções e modelos de recursos sobre como você pode obter todos os dados necessários do SQL uma vez, talvez duas vezes e, em seguida, ter memória suficiente no local, além de referenciar os dados para fazer o loop para exibição / manipulação. Também é mais fácil armazenar um resultado no cache do que muitos, esse também é o caso dos mecanismos de cache internos do MySQL, pois solicitações frequentes que são grandes o suficiente causam o mesmo problema de troca de disco.

Salve a E / S

Vinai tem um bom exemplo de implementação da mesma abordagem:

Referências :

B00MER
fonte