소스 검색

perf(product): speed up price index rebuild

Full price reindex rewrote every row on each run and cost one SELECT plus
one UPDATE per row through the repository.

- Compare decimal columns at a fixed scale so unchanged rows are skipped;
  array_diff_assoc read MySQL's "199.0000" and the computed 199.0 as different
- Write new and changed rows with a chunked upsert instead of per-row
  Repository::update(), which also restores the missing timestamps
- Replace the cursorPaginate loops with chunkById in the price, flat and
  inventory indexers; cursorPaginate mutates the builder it is called on, so
  the reused repository query stacked one cursor WHERE group per round
- Compute flexible variant indices once and aggregate them into the
  product-level index instead of walking the variants twice
- Prefetch catalog_rule_products for the whole batch
- Add --prune to drop index rows whose product or variant no longer exists,
  and --batch-size to tune chunking

Local run over 30k index rows: 79.7s / 60,428 queries -> 3.3s / 35 queries,
with identical output.

Co-authored-by: Cursor <cursoragent@cursor.com>
chengwl 3 주 전
부모
커밋
41428e651b

+ 27 - 12
packages/Longyi/Core/src/Helpers/Indexers/Price/FlexibleVariant.php

@@ -47,21 +47,25 @@ class FlexibleVariant extends AbstractType
      */
      */
     public function getIndices(): array
     public function getIndices(): array
     {
     {
-        $variants = $this->getSaleableVariants();
+        $variantIndices = [];
 
 
-        $minPrices = [];
-        $maxPrices = [];
-        $regularMinPrices = [];
-        $regularMaxPrices = [];
+        foreach ($this->getSaleableVariants() as $variant) {
+            $variantIndices[] = $this->setVariant($variant)->getVariantIndices();
+        }
 
 
-        foreach ($variants as $variant) {
-            $vi = $this->setVariant($variant)->getVariantIndices();
+        return $this->aggregateIndices($variantIndices);
+    }
 
 
-            $minPrices[] = $vi['min_price'];
-            $maxPrices[] = $vi['max_price'];
-            $regularMinPrices[] = $vi['regular_min_price'];
-            $regularMaxPrices[] = $vi['regular_max_price'];
-        }
+    /**
+     * Roll already computed variant indices up into the product-level index, so callers
+     * that walk the variants themselves do not pay for a second pass.
+     */
+    public function aggregateIndices(array $variantIndices): array
+    {
+        $minPrices = array_column($variantIndices, 'min_price');
+        $maxPrices = array_column($variantIndices, 'max_price');
+        $regularMinPrices = array_column($variantIndices, 'regular_min_price');
+        $regularMaxPrices = array_column($variantIndices, 'regular_max_price');
 
 
         return [
         return [
             'min_price'         => ! empty($minPrices) ? min($minPrices) : 0,
             'min_price'         => ! empty($minPrices) ? min($minPrices) : 0,
@@ -192,6 +196,17 @@ class FlexibleVariant extends AbstractType
         };
         };
     }
     }
 
 
+    /**
+     * Seed the parent product's catalog rule entries, letting batch callers prefetch them
+     * for every product at once instead of one query per product.
+     */
+    public function setCatalogRuleProducts(array $rules): static
+    {
+        $this->catalogRuleProductsCache = $rules;
+
+        return $this;
+    }
+
     /**
     /**
      * Get catalog_rule_products for the parent product (cached per indexer run).
      * Get catalog_rule_products for the parent product (cached per indexer run).
      */
      */

+ 11 - 1
packages/Webkul/Product/src/Console/Commands/Indexer.php

@@ -22,7 +22,7 @@ class Indexer extends Command
      *
      *
      * @var string
      * @var string
      */
      */
-    protected $signature = 'indexer:index {--type=*} {--mode=*}';
+    protected $signature = 'indexer:index {--type=*} {--mode=*} {--prune} {--batch-size=}';
 
 
     /**
     /**
      * The console command description.
      * The console command description.
@@ -62,6 +62,10 @@ class Indexer extends Command
 
 
             $indexer = app($this->indexers[$indexerId]);
             $indexer = app($this->indexers[$indexerId]);
 
 
+            if ($this->option('batch-size') && method_exists($indexer, 'setBatchSize')) {
+                $indexer->setBatchSize((int) $this->option('batch-size'));
+            }
+
             if ($mode == 'full') {
             if ($mode == 'full') {
                 $indexer->reindexFull();
                 $indexer->reindexFull();
             } else {
             } else {
@@ -69,6 +73,12 @@ class Indexer extends Command
                     $indexer->reindexSelective();
                     $indexer->reindexSelective();
                 }
                 }
             }
             }
+
+            if ($this->option('prune') && method_exists($indexer, 'pruneOrphanIndices')) {
+                $deleted = $indexer->pruneOrphanIndices();
+
+                $this->info("Pruned {$deleted} orphan {$indexerId} index rows.");
+            }
         }
         }
 
 
         $end = microtime(true);
         $end = microtime(true);

+ 11 - 21
packages/Webkul/Product/src/Helpers/Indexers/Flat.php

@@ -67,27 +67,17 @@ class Flat extends AbstractIndexer
      */
      */
     public function reindexFull()
     public function reindexFull()
     {
     {
-        while (true) {
-            $paginator = $this->productRepository
-                ->with([
-                    'variants',
-                    'attribute_family',
-                    'attribute_values',
-                    'variants.attribute_family',
-                    'variants.attribute_values',
-                ])
-                ->cursorPaginate($this->batchSize);
-
-            $this->reindexBatch($paginator->items());
-
-            if (! $cursor = $paginator->nextCursor()) {
-                break;
-            }
-
-            request()->query->add(['cursor' => $cursor->encode()]);
-        }
-
-        request()->query->remove('cursor');
+        $this->productRepository
+            ->getModel()
+            ->newQuery()
+            ->with([
+                'variants',
+                'attribute_family',
+                'attribute_values',
+                'variants.attribute_family',
+                'variants.attribute_values',
+            ])
+            ->chunkById($this->batchSize, fn ($products) => $this->reindexBatch($products));
     }
     }
 
 
     /**
     /**

+ 10 - 20
packages/Webkul/Product/src/Helpers/Indexers/Inventory.php

@@ -80,26 +80,16 @@ class Inventory extends AbstractIndexer
      */
      */
     public function reindexFull()
     public function reindexFull()
     {
     {
-        while (true) {
-            $paginator = $this->productRepository
-                ->with([
-                    'inventories',
-                    'ordered_inventories',
-                    'inventory_indices',
-                ])
-                ->whereIn('type', ['simple', 'virtual'])
-                ->cursorPaginate($this->batchSize);
-
-            $this->reindexBatch($paginator->items());
-
-            if (! $cursor = $paginator->nextCursor()) {
-                break;
-            }
-
-            request()->query->add(['cursor' => $cursor->encode()]);
-        }
-
-        request()->query->remove('cursor');
+        $this->productRepository
+            ->getModel()
+            ->newQuery()
+            ->with([
+                'inventories',
+                'ordered_inventories',
+                'inventory_indices',
+            ])
+            ->whereIn('type', ['simple', 'virtual'])
+            ->chunkById($this->batchSize, fn ($products) => $this->reindexBatch($products));
     }
     }
 
 
     /**
     /**

+ 279 - 134
packages/Webkul/Product/src/Helpers/Indexers/Price.php

@@ -2,7 +2,10 @@
 
 
 namespace Webkul\Product\Helpers\Indexers;
 namespace Webkul\Product\Helpers\Indexers;
 
 
+use Illuminate\Database\Eloquent\Model;
 use Illuminate\Support\Carbon;
 use Illuminate\Support\Carbon;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
 use Webkul\Customer\Repositories\CustomerGroupRepository;
 use Webkul\Customer\Repositories\CustomerGroupRepository;
 use Webkul\Product\Repositories\ProductPriceIndexRepository;
 use Webkul\Product\Repositories\ProductPriceIndexRepository;
 use Webkul\Product\Repositories\ProductRepository;
 use Webkul\Product\Repositories\ProductRepository;
@@ -10,9 +13,66 @@ use Webkul\Product\Repositories\ProductRepository;
 class Price extends AbstractIndexer
 class Price extends AbstractIndexer
 {
 {
     /**
     /**
+     * Index table.
+     */
+    protected const TABLE = 'product_price_indices';
+
+    /**
+     * Decimal columns are compared at a fixed scale because MySQL hands them back as
+     * strings ("10.0000") while the indexers compute floats (10.0).
+     */
+    protected const DECIMAL_COLUMNS = [
+        'min_price',
+        'regular_min_price',
+        'max_price',
+        'regular_max_price',
+    ];
+
+    /**
+     * Columns backing the `ppi_priceable_group_channel_unique` index.
+     */
+    protected const UNIQUE_COLUMNS = [
+        'priceable_id',
+        'priceable_type',
+        'customer_group_id',
+        'channel_id',
+    ];
+
+    /**
+     * Relations every price indexer needs. Without them each product triggers its own
+     * queries for attribute values, group prices and catalog rules.
+     */
+    protected const EAGER_LOADS = [
+        'variants',
+        'attribute_family',
+        'attribute_values',
+        'variants.attribute_family',
+        'variants.attribute_values',
+        'price_indices',
+        'inventory_indices',
+        'variants.price_indices',
+        'variants.inventory_indices',
+        'customer_group_prices',
+        'variants.customer_group_prices',
+        'catalog_rule_prices',
+        'variants.catalog_rule_prices',
+        'flexibleVariants',
+        'flexibleVariants.price_indices',
+        'flexibleVariants.customer_group_prices',
+    ];
+
+    /**
+     * Rows sent per upsert statement.
+     */
+    protected const WRITE_CHUNK_SIZE = 500;
+
+    /**
+     * Products per chunk. Memory is driven by the variants and index rows hanging off each
+     * product, so this stays conservative and is tunable through `--batch-size`.
+     *
      * @var int
      * @var int
      */
      */
-    private $batchSize;
+    private $batchSize = self::BATCH_SIZE;
 
 
     /**
     /**
      * Channels
      * Channels
@@ -28,6 +88,13 @@ class Price extends AbstractIndexer
      */
      */
     protected $customerGroups;
     protected $customerGroups;
 
 
+    /**
+     * Price indexer per product type.
+     *
+     * @var array
+     */
+    protected $typeIndexers = [];
+
     /**
     /**
      * Create a new indexer instance.
      * Create a new indexer instance.
      *
      *
@@ -37,8 +104,18 @@ class Price extends AbstractIndexer
         protected CustomerGroupRepository $customerGroupRepository,
         protected CustomerGroupRepository $customerGroupRepository,
         protected ProductRepository $productRepository,
         protected ProductRepository $productRepository,
         protected ProductPriceIndexRepository $productPriceIndexRepository
         protected ProductPriceIndexRepository $productPriceIndexRepository
-    ) {
-        $this->batchSize = self::BATCH_SIZE;
+    ) {}
+
+    /**
+     * Number of products loaded per chunk.
+     *
+     * @return $this
+     */
+    public function setBatchSize(int $batchSize)
+    {
+        $this->batchSize = $batchSize;
+
+        return $this;
     }
     }
 
 
     /**
     /**
@@ -48,38 +125,9 @@ class Price extends AbstractIndexer
      */
      */
     public function reindexFull()
     public function reindexFull()
     {
     {
-        while (true) {
-            $paginator = $this->productRepository
-                ->with([
-                    'variants',
-                    'attribute_family',
-                    'attribute_values',
-                    'variants.attribute_family',
-                    'variants.attribute_values',
-                    'price_indices',
-                    'inventory_indices',
-                    'variants.price_indices',
-                    'variants.inventory_indices',
-                    'customer_group_prices',
-                    'variants.customer_group_prices',
-                    'catalog_rule_prices',
-                    'variants.catalog_rule_prices',
-                    'flexibleVariants',
-                    'flexibleVariants.price_indices',
-                    'flexibleVariants.customer_group_prices',
-                ])
-                ->cursorPaginate($this->batchSize);
-
-            $this->reindexBatch($paginator->items());
-
-            if (! $cursor = $paginator->nextCursor()) {
-                break;
-            }
-
-            request()->query->add(['cursor' => $cursor->encode()]);
-        }
-
-        request()->query->remove('cursor');
+        $this->productQuery()
+            ->with(self::EAGER_LOADS)
+            ->chunkById($this->batchSize, fn ($products) => $this->reindexBatch($products));
     }
     }
 
 
     /**
     /**
@@ -89,52 +137,30 @@ class Price extends AbstractIndexer
      */
      */
     public function reindexSelective()
     public function reindexSelective()
     {
     {
-        while (true) {
-            $paginator = $this->productRepository
-                ->distinct()
-                ->select('products.*')
-                ->with([
-                    'variants',
-                    'attribute_values',
-                    'variants.attribute_values',
-                    'price_indices',
-                    'inventory_indices',
-                    'variants.price_indices',
-                    'variants.inventory_indices',
-                    'customer_group_prices',
-                    'variants.customer_group_prices',
-                    'catalog_rule_prices',
-                    'variants.catalog_rule_prices',
-                    'flexibleVariants',
-                    'flexibleVariants.price_indices',
-                    'flexibleVariants.customer_group_prices',
-                ])
-                ->join('product_attribute_values as special_price_from_pav', function ($join) {
-                    $join->on('products.id', '=', 'special_price_from_pav.product_id')
-                        ->where('special_price_from_pav.attribute_id', self::SPECIAL_PRICE_FROM_ATTRIBUTE_ID);
-                })
-                ->join('product_attribute_values as special_price_to_pav', function ($join) {
-                    $join->on('products.id', '=', 'special_price_to_pav.product_id')
-                        ->where('special_price_to_pav.attribute_id', self::SPECIAL_PRICE_TO_ATTRIBUTE_ID);
-                })
-                ->leftJoin('catalog_rule_product_prices', 'products.id', '=', 'catalog_rule_product_prices.product_id')
-                ->where(function ($query) {
-                    return $query->orWhere('special_price_from_pav.date_value', Carbon::now()->format('Y-m-d'))
-                        ->orWhere('special_price_to_pav.date_value', Carbon::now()->subDays(1)->format('Y-m-d'))
-                        ->orWhere('catalog_rule_product_prices.rule_date', Carbon::now()->subDays(1)->format('Y-m-d'));
-                })
-                ->cursorPaginate($this->batchSize);
-
-            $this->reindexBatch($paginator->items());
-
-            if (! $cursor = $paginator->nextCursor()) {
-                break;
-            }
-
-            request()->query->add(['cursor' => $cursor->encode()]);
-        }
-
-        request()->query->remove('cursor');
+        $this->productQuery()
+            ->distinct()
+            ->select('products.*')
+            ->with(self::EAGER_LOADS)
+            ->join('product_attribute_values as special_price_from_pav', function ($join) {
+                $join->on('products.id', '=', 'special_price_from_pav.product_id')
+                    ->where('special_price_from_pav.attribute_id', self::SPECIAL_PRICE_FROM_ATTRIBUTE_ID);
+            })
+            ->join('product_attribute_values as special_price_to_pav', function ($join) {
+                $join->on('products.id', '=', 'special_price_to_pav.product_id')
+                    ->where('special_price_to_pav.attribute_id', self::SPECIAL_PRICE_TO_ATTRIBUTE_ID);
+            })
+            ->leftJoin('catalog_rule_product_prices', 'products.id', '=', 'catalog_rule_product_prices.product_id')
+            ->where(function ($query) {
+                return $query->orWhere('special_price_from_pav.date_value', Carbon::now()->format('Y-m-d'))
+                    ->orWhere('special_price_to_pav.date_value', Carbon::now()->subDays(1)->format('Y-m-d'))
+                    ->orWhere('catalog_rule_product_prices.rule_date', Carbon::now()->subDays(1)->format('Y-m-d'));
+            })
+            ->chunkById(
+                $this->batchSize,
+                fn ($products) => $this->reindexBatch($products),
+                'products.id',
+                'id'
+            );
     }
     }
 
 
     /**
     /**
@@ -144,7 +170,15 @@ class Price extends AbstractIndexer
      */
      */
     public function reindexBatch($products)
     public function reindexBatch($products)
     {
     {
-        $newIndices = [];
+        $products = $products instanceof Collection ? $products : collect($products);
+
+        if ($products->isEmpty()) {
+            return;
+        }
+
+        $catalogRuleProducts = $this->getCatalogRuleProducts($products);
+
+        $pending = [];
 
 
         foreach ($products as $product) {
         foreach ($products as $product) {
             $indexer = $this->getTypeIndexer($product)
             $indexer = $this->getTypeIndexer($product)
@@ -152,67 +186,63 @@ class Price extends AbstractIndexer
 
 
             $isFlexibleVariant = $product->type === 'flexible_variant';
             $isFlexibleVariant = $product->type === 'flexible_variant';
 
 
+            $variants = collect();
+
+            if ($isFlexibleVariant) {
+                $indexer->setCatalogRuleProducts($catalogRuleProducts[$product->id] ?? []);
+
+                $variants = $this->getSaleableVariants($product);
+            }
+
             foreach ($this->getChannels() as $channel) {
             foreach ($this->getChannels() as $channel) {
                 foreach ($this->getCustomerGroups() as $customerGroup) {
                 foreach ($this->getCustomerGroups() as $customerGroup) {
                     $indexer->setChannel($channel)->setCustomerGroup($customerGroup);
                     $indexer->setChannel($channel)->setCustomerGroup($customerGroup);
 
 
-                    // Index each flexible variant individually
-                    if ($isFlexibleVariant) {
-                        $variants = $product->relationLoaded('flexibleVariants')
-                            ? $product->flexibleVariants
-                            : $product->flexibleVariants()->get();
-
-                        foreach ($variants as $variant) {
-                            if (! $variant->isSaleable()) {
-                                continue;
-                            }
-
-                            $variantIndex = $indexer->setVariant($variant)->getVariantIndices();
-
-                            $this->upsertIndex(
-                                $variant->price_indices ?? collect(),
-                                $variantIndex,
-                                $newIndices,
-                                $channel->id,
-                                $customerGroup->id,
-                                $variant->id,
-                                get_class($variant)
-                            );
-                        }
+                    $variantIndices = [];
+
+                    foreach ($variants as $variant) {
+                        $variantIndices[] = $variantIndex = $indexer->setVariant($variant)->getVariantIndices();
+
+                        $this->collectIndex(
+                            $variant->price_indices ?? collect(),
+                            $variantIndex,
+                            $pending,
+                            $channel->id,
+                            $customerGroup->id,
+                            $variant->id
+                        );
                     }
                     }
 
 
                     // Product-level index (aggregated for flexible_variant, normal for others)
                     // Product-level index (aggregated for flexible_variant, normal for others)
-                    $newIndex = $indexer->getIndices();
+                    $newIndex = $isFlexibleVariant
+                        ? $indexer->aggregateIndices($variantIndices)
+                        : $indexer->getIndices();
 
 
-                    $this->upsertIndex(
+                    $this->collectIndex(
                         $product->price_indices,
                         $product->price_indices,
                         $newIndex,
                         $newIndex,
-                        $newIndices,
+                        $pending,
                         $channel->id,
                         $channel->id,
                         $customerGroup->id,
                         $customerGroup->id,
-                        $product->id,
-                        get_class($product)
+                        $product->id
                     );
                     );
                 }
                 }
             }
             }
         }
         }
 
 
-        if (! empty($newIndices)) {
-            $this->productPriceIndexRepository->insert($newIndices);
-        }
+        $this->writeIndices($pending);
     }
     }
 
 
     /**
     /**
-     * Upsert a single price index entry: update if exists, or queue for bulk insert.
+     * Queue a price index row for writing when it is new or its values moved.
      */
      */
-    protected function upsertIndex(
+    protected function collectIndex(
         $existingIndices,
         $existingIndices,
         array $newIndex,
         array $newIndex,
-        array &$newIndices,
+        array &$pending,
         int $channelId,
         int $channelId,
         int $customerGroupId,
         int $customerGroupId,
-        int $priceableId,
-        string $priceableType
+        int $priceableId
     ): void {
     ): void {
         $existing = $existingIndices
         $existing = $existingIndices
             ->where('channel_id', $channelId)
             ->where('channel_id', $channelId)
@@ -220,16 +250,31 @@ class Price extends AbstractIndexer
             ->where('priceable_id', $priceableId)
             ->where('priceable_id', $priceableId)
             ->first();
             ->first();
 
 
-        if ($existing) {
-            $oldIndex = collect($existing->toArray())
-                ->except('id', 'created_at', 'updated_at')
-                ->toArray();
+        if ($existing && ! $this->isIndexChanged($existing->getAttributes(), $newIndex)) {
+            return;
+        }
 
 
-            if ($this->isIndexChanged($oldIndex, $newIndex)) {
-                $this->productPriceIndexRepository->update($newIndex, $existing->id);
-            }
-        } else {
-            $newIndices[] = $newIndex;
+        $pending[] = $newIndex;
+    }
+
+    /**
+     * Insert new rows and refresh changed ones in bulk.
+     */
+    protected function writeIndices(array $indices): void
+    {
+        if (empty($indices)) {
+            return;
+        }
+
+        $update = array_values(array_diff(
+            array_keys($indices[0]),
+            self::UNIQUE_COLUMNS
+        ));
+
+        $model = $this->productPriceIndexRepository->getModel();
+
+        foreach (array_chunk($indices, self::WRITE_CHUNK_SIZE) as $chunk) {
+            $model->newQuery()->upsert($chunk, self::UNIQUE_COLUMNS, $update);
         }
         }
     }
     }
 
 
@@ -240,23 +285,123 @@ class Price extends AbstractIndexer
      */
      */
     public function isIndexChanged($oldIndex, $newIndex)
     public function isIndexChanged($oldIndex, $newIndex)
     {
     {
-        return (bool) count(array_diff_assoc($oldIndex, $newIndex));
+        foreach ($newIndex as $column => $value) {
+            $oldValue = $oldIndex[$column] ?? null;
+
+            if (in_array($column, self::DECIMAL_COLUMNS, true)) {
+                if (number_format((float) $oldValue, 4, '.', '') !== number_format((float) $value, 4, '.', '')) {
+                    return true;
+                }
+
+                continue;
+            }
+
+            if ((string) $oldValue !== (string) $value) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Drop index rows whose product or variant no longer exists. Polymorphic rows have
+     * no foreign key to cascade from, so deletions leave them behind forever.
+     */
+    public function pruneOrphanIndices(): int
+    {
+        $deleted = 0;
+
+        $types = DB::table(self::TABLE)
+            ->select('priceable_type')
+            ->distinct()
+            ->pluck('priceable_type');
+
+        foreach ($types as $type) {
+            if (! is_string($type) || ! is_subclass_of($type, Model::class)) {
+                continue;
+            }
+
+            $owner = new $type;
+
+            while (true) {
+                $ids = DB::table(self::TABLE)
+                    ->leftJoin($owner->getTable(), $owner->getQualifiedKeyName(), '=', self::TABLE.'.priceable_id')
+                    ->where(self::TABLE.'.priceable_type', $type)
+                    ->whereNull($owner->getQualifiedKeyName())
+                    ->limit(self::WRITE_CHUNK_SIZE)
+                    ->pluck(self::TABLE.'.id');
+
+                if ($ids->isEmpty()) {
+                    break;
+                }
+
+                $deleted += DB::table(self::TABLE)->whereIn('id', $ids)->delete();
+            }
+        }
+
+        return $deleted;
+    }
+
+    /**
+     * Catalog rule entries for every flexible variant product in the batch, keyed by
+     * product id, so the variant indexer does not query them one product at a time.
+     */
+    protected function getCatalogRuleProducts(Collection $products): array
+    {
+        $productIds = $products
+            ->where('type', 'flexible_variant')
+            ->pluck('id')
+            ->all();
+
+        if (empty($productIds)) {
+            return [];
+        }
+
+        return DB::table('catalog_rule_products')
+            ->whereIn('product_id', $productIds)
+            ->orderBy('sort_order')
+            ->orderBy('catalog_rule_id')
+            ->get()
+            ->groupBy('product_id')
+            ->map(fn ($rules) => $rules->all())
+            ->all();
+    }
+
+    /**
+     * Saleable flexible variants of a product.
+     */
+    protected function getSaleableVariants($product): Collection
+    {
+        $variants = $product->relationLoaded('flexibleVariants')
+            ? $product->flexibleVariants
+            : $product->flexibleVariants()->get();
+
+        return $variants->filter(fn ($variant) => $variant->isSaleable())->values();
+    }
+
+    /**
+     * Fresh query builder on the product model.
+     *
+     * @return \Illuminate\Database\Eloquent\Builder
+     */
+    protected function productQuery()
+    {
+        return $this->productRepository->getModel()->newQuery();
     }
     }
 
 
     /**
     /**
      * Returns indexer for product type
      * Returns indexer for product type
      *
      *
-     * @return string
+     * @return \Webkul\Product\Helpers\Indexers\Price\AbstractType
      */
      */
     public function getTypeIndexer($product)
     public function getTypeIndexer($product)
     {
     {
-        static $typeIndexers = [];
-
-        if (isset($typeIndexers[$product->type])) {
-            return $typeIndexers[$product->type];
+        if (isset($this->typeIndexers[$product->type])) {
+            return $this->typeIndexers[$product->type];
         }
         }
 
 
-        return $typeIndexers[$product->type] = $product->getTypeInstance()->getPriceIndexer();
+        return $this->typeIndexers[$product->type] = $product->getTypeInstance()->getPriceIndexer();
     }
     }
 
 
     /**
     /**