diff --git a/vendor/magento/module-sales/Model/GridAsyncInsert.php b/vendor/magento/module-sales/Model/GridAsyncInsert.php
index 43b4b3a5b346d..29a7097b2a0be 100644
--- a/vendor/magento/module-sales/Model/GridAsyncInsert.php
+++ b/vendor/magento/module-sales/Model/GridAsyncInsert.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Sales\Model;
+use Magento\Framework\Lock\LockManagerInterface;
+use Psr\Log\LoggerInterface;
+
/**
* Sales entity grids indexing observer.
*
@@ -27,23 +30,46 @@ class GridAsyncInsert
*/
protected $globalConfig;
+ /**
+ * @var LockManagerInterface|null
+ */
+ private $lockManager;
+
+ /**
+ * @var LoggerInterface|null
+ */
+ private $logger;
+
+ /**
+ * @var string
+ */
+ private $lockName;
+
/**
* @param \Magento\Sales\Model\ResourceModel\GridInterface $entityGrid
* @param \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig
+ * @param LockManagerInterface|null $lockManager
+ * @param LoggerInterface|null $logger
+ * @param string $lockName
*/
public function __construct(
\Magento\Sales\Model\ResourceModel\GridInterface $entityGrid,
- \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig
+ \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig,
+ ?LockManagerInterface $lockManager = null,
+ ?LoggerInterface $logger = null,
+ string $lockName = ''
) {
$this->entityGrid = $entityGrid;
$this->globalConfig = $globalConfig;
+ $this->lockManager = $lockManager;
+ $this->logger = $logger;
+ $this->lockName = $lockName;
}
/**
- * Handles asynchronous insertion of the new entity into
- * corresponding grid during cron job.
+ * Handles asynchronous insertion of the new entity into corresponding grid during cron job.
*
- * Also method is used in the next events:
+ * Also, method is used in the next events:
*
* - config_data_dev_grid_async_indexing_disabled
*
@@ -55,7 +81,23 @@ public function __construct(
public function asyncInsert()
{
if ($this->globalConfig->getValue('dev/grid/async_indexing')) {
- $this->entityGrid->refreshBySchedule();
+ if ($this->lockManager && $this->lockName !== '') {
+ if (!$this->lockManager->lock($this->lockName, 0)) {
+ if ($this->logger) {
+ $this->logger->warning(
+ sprintf('Grid async insert is locked: %s, skipping run', $this->lockName)
+ );
+ }
+ return;
+ }
+ try {
+ $this->entityGrid->refreshBySchedule();
+ } finally {
+ $this->lockManager->unlock($this->lockName);
+ }
+ } else {
+ $this->entityGrid->refreshBySchedule();
+ }
}
}
}
diff --git a/vendor/magento/module-sales/Model/ResourceModel/Grid.php b/vendor/magento/module-sales/Model/ResourceModel/Grid.php
index 70588c3c5140b..af13c039651bc 100644
--- a/vendor/magento/module-sales/Model/ResourceModel/Grid.php
+++ b/vendor/magento/module-sales/Model/ResourceModel/Grid.php
@@ -54,7 +54,12 @@ class Grid extends AbstractGrid
/**
* Order grid rows batch size
*/
- const BATCH_SIZE = 100;
+ public const BATCH_SIZE = 100;
+
+ /**
+ * Maximum reconciliation iterations per cron run.
+ */
+ private const MAX_REFRESH_ITERATIONS = 1000;
/**
* @param Context $context
@@ -130,24 +135,28 @@ public function refresh($value, $field = null)
public function refreshBySchedule()
{
$lastUpdatedAt = null;
- $notSyncedIds = $this->notSyncedDataProvider->getIds($this->mainTableName, $this->gridTableName);
- foreach (array_chunk($notSyncedIds, self::BATCH_SIZE) as $bunch) {
- $select = $this->getGridOriginSelect()->where($this->mainTableName . '.entity_id IN (?)', $bunch);
- $fetchResult = $this->getConnection()->fetchAll($select);
- $this->getConnection()->insertOnDuplicate(
- $this->getTable($this->gridTableName),
- $fetchResult,
- array_keys($this->columns)
- );
-
- $timestamps = array_column($fetchResult, 'updated_at');
- if ($timestamps) {
- $lastUpdatedAt = max(max($timestamps), $lastUpdatedAt);
+ $iteration = 0;
+ while ($iteration < self::MAX_REFRESH_ITERATIONS) {
+ $iteration++;
+ $notSyncedIds = $this->notSyncedDataProvider->getIds($this->mainTableName, $this->gridTableName);
+ if (empty($notSyncedIds)) {
+ break;
+ }
+ foreach (array_chunk($notSyncedIds, self::BATCH_SIZE) as $bunch) {
+ $select = $this->getGridOriginSelect()->where($this->mainTableName . '.entity_id IN (?)', $bunch);
+ $fetchResult = $this->getConnection()->fetchAll($select);
+ $this->getConnection()->insertOnDuplicate(
+ $this->getTable($this->gridTableName),
+ $fetchResult,
+ array_keys($this->columns)
+ );
+
+ $timestamps = array_column($fetchResult, 'updated_at');
+ if ($timestamps) {
+ $lastUpdatedAt = max(max($timestamps), $lastUpdatedAt);
+ $this->lastUpdateTimeCache->save($this->gridTableName, $lastUpdatedAt);
+ }
}
- }
-
- if ($lastUpdatedAt) {
- $this->lastUpdateTimeCache->save($this->gridTableName, $lastUpdatedAt);
}
}
diff --git a/vendor/magento/module-sales/Model/ResourceModel/Provider/Query/IdListBuilder.php b/vendor/magento/module-sales/Model/ResourceModel/Provider/Query/IdListBuilder.php
index 193c161da11fe..7788f1ccee66b 100644
--- a/vendor/magento/module-sales/Model/ResourceModel/Provider/Query/IdListBuilder.php
+++ b/vendor/magento/module-sales/Model/ResourceModel/Provider/Query/IdListBuilder.php
@@ -82,14 +82,20 @@ private function getConnection(): AdapterInterface
}
/**
- * Builds select object.
+ * Builds a select object.
*
* @param string $mainTableName
* @param string $gridTableName
+ * @param int|null $startEntityId
+ * @param int|null $endEntityId
* @return Select
*/
- public function build(string $mainTableName, string $gridTableName): Select
- {
+ public function build(
+ string $mainTableName,
+ string $gridTableName,
+ ?int $startEntityId = null,
+ ?int $endEntityId = null
+ ): Select {
$select = $this->getConnection()->select()
->from(['main_table' => $mainTableName], ['main_table.entity_id'])
->joinLeft(
@@ -98,6 +104,12 @@ public function build(string $mainTableName, string $gridTableName): Select
[]
);
+ if ($startEntityId !== null) {
+ $select->where('main_table.entity_id > ?', $startEntityId);
+ }
+ if ($endEntityId !== null) {
+ $select->where('main_table.entity_id <= ?', $endEntityId);
+ }
$select->where('grid_table.entity_id IS NULL');
$select->limit(Grid::BATCH_SIZE);
foreach ($this->additionalGridTables as $table) {
diff --git a/vendor/magento/module-sales/Model/ResourceModel/Provider/UpdatedIdListProvider.php b/vendor/magento/module-sales/Model/ResourceModel/Provider/UpdatedIdListProvider.php
index ea62519573787..95a84ea00eb7f 100644
--- a/vendor/magento/module-sales/Model/ResourceModel/Provider/UpdatedIdListProvider.php
+++ b/vendor/magento/module-sales/Model/ResourceModel/Provider/UpdatedIdListProvider.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Sales\Model\ResourceModel\Provider;
+use Magento\Framework\FlagManager;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
@@ -15,6 +16,16 @@
*/
class UpdatedIdListProvider implements NotSyncedDataProviderInterface
{
+ /**
+ * Number of entity IDs scanned per reconciliation range.
+ */
+ private const ENTITY_ID_SCAN_RANGE = 10000;
+
+ /**
+ * Prefix for persisted per-grid cursor flag.
+ */
+ private const GRID_CURSOR_FLAG_PREFIX = 'sales_grid_async_last_entity_id_';
+
/**
* @var ResourceConnection
*/
@@ -30,17 +41,25 @@ class UpdatedIdListProvider implements NotSyncedDataProviderInterface
*/
private $idListQueryBuilder;
+ /**
+ * @var FlagManager
+ */
+ private $flagManager;
+
/**
* NotSyncedDataProvider constructor.
* @param ResourceConnection $resourceConnection
* @param IdListBuilder|null $idListQueryBuilder
+ * @param FlagManager|null $flagManager
*/
public function __construct(
ResourceConnection $resourceConnection,
- ?IdListBuilder $idListQueryBuilder = null
+ ?IdListBuilder $idListQueryBuilder = null,
+ ?FlagManager $flagManager = null
) {
$this->resourceConnection = $resourceConnection;
$this->idListQueryBuilder = $idListQueryBuilder ?? ObjectManager::getInstance()->get(IdListBuilder::class);
+ $this->flagManager = $flagManager ?? ObjectManager::getInstance()->get(FlagManager::class);
}
/**
@@ -50,8 +69,29 @@ public function getIds($mainTableName, $gridTableName)
{
$mainTableName = $this->resourceConnection->getTableName($mainTableName);
$gridTableName = $this->resourceConnection->getTableName($gridTableName);
- $select = $this->idListQueryBuilder->build($mainTableName, $gridTableName);
- return $this->getConnection()->fetchAll($select, [], \Zend_Db::FETCH_COLUMN);
+ $maxEntityId = $this->getMaxEntityId($mainTableName);
+ if ($maxEntityId === 0) {
+ return [];
+ }
+
+ $cursorFlagCode = self::GRID_CURSOR_FLAG_PREFIX . $gridTableName;
+ $lastProcessedEntityId = $this->getLastProcessedEntityId($cursorFlagCode, $maxEntityId);
+ if ($lastProcessedEntityId >= $maxEntityId) {
+ return [];
+ }
+ $scanUntilEntityId = min($lastProcessedEntityId + self::ENTITY_ID_SCAN_RANGE, $maxEntityId);
+ $select = $this->idListQueryBuilder->build(
+ $mainTableName,
+ $gridTableName,
+ $lastProcessedEntityId,
+ $scanUntilEntityId
+ );
+ $ids = $this->getConnection()->fetchAll($select, [], \Zend_Db::FETCH_COLUMN);
+ if (empty($ids)) {
+ $this->flagManager->saveFlag($cursorFlagCode, $scanUntilEntityId);
+ }
+
+ return $ids;
}
/**
@@ -67,4 +107,38 @@ private function getConnection()
return $this->connection;
}
+
+ /**
+ * Returns maximum entity ID for source table.
+ *
+ * @param string $mainTableName
+ * @return int
+ */
+ private function getMaxEntityId(string $mainTableName): int
+ {
+ $select = $this->getConnection()->select()->from(
+ ['main_table' => $mainTableName],
+ ['max_entity_id' => new \Zend_Db_Expr('MAX(main_table.entity_id)')]
+ );
+ $maxEntityId = (int)$this->getConnection()->fetchOne($select);
+
+ return max(0, $maxEntityId);
+ }
+
+ /**
+ * Returns last processed entity ID for a grid cursor.
+ *
+ * @param string $cursorFlagCode
+ * @param int $maxEntityId
+ * @return int
+ */
+ private function getLastProcessedEntityId(string $cursorFlagCode, int $maxEntityId): int
+ {
+ $storedCursor = $this->flagManager->getFlagData($cursorFlagCode);
+ if (!is_numeric($storedCursor)) {
+ return max(0, $maxEntityId - self::ENTITY_ID_SCAN_RANGE);
+ }
+
+ return max(0, min((int)$storedCursor, $maxEntityId));
+ }
}
diff --git a/vendor/magento/module-sales/etc/di.xml b/vendor/magento/module-sales/etc/di.xml
index 3234ad12b3f3a..17488ec3cc86d 100644
--- a/vendor/magento/module-sales/etc/di.xml
+++ b/vendor/magento/module-sales/etc/di.xml
@@ -267,21 +267,29 @@
Magento\Sales\Model\ResourceModel\Order\Grid
+ Magento\Framework\Lock\LockManagerInterface
+ grid_async_insert_sales_order
Magento\Sales\Model\ResourceModel\Order\Invoice\Grid
+ Magento\Framework\Lock\LockManagerInterface
+ grid_async_insert_sales_invoice
ShipmentGridAggregator
+ Magento\Framework\Lock\LockManagerInterface
+ grid_async_insert_sales_shipment
CreditmemoGridAggregator
+ Magento\Framework\Lock\LockManagerInterface
+ grid_async_insert_sales_creditmemo