diff --git a/vendor/magento/module-catalog/Model/Indexer/Category/Product.php b/vendor/magento/module-catalog/Model/Indexer/Category/Product.php
index af1cda41d8c..c18404bda1f 100644
--- a/vendor/magento/module-catalog/Model/Indexer/Category/Product.php
+++ b/vendor/magento/module-catalog/Model/Indexer/Category/Product.php
@@ -137,10 +137,11 @@ class Product implements \Magento\Framework\Indexer\ActionInterface, \Magento\Fr
 
         /** @var Product\Action\Rows $action */
         $action = $this->rowsActionFactory->create();
-        if ($indexer->isWorking()) {
+        if ($indexer->isScheduled()) {
             $action->execute($ids, true);
+        } else {
+            $action->execute($ids);
         }
-        $action->execute($ids);
 
         return $this;
     }
diff --git a/vendor/magento/module-catalog/Model/Indexer/Category/Product/Action/Rows.php b/vendor/magento/module-catalog/Model/Indexer/Category/Product/Action/Rows.php
index 3bd49107675..24c9227bb64 100644
--- a/vendor/magento/module-catalog/Model/Indexer/Category/Product/Action/Rows.php
+++ b/vendor/magento/module-catalog/Model/Indexer/Category/Product/Action/Rows.php
@@ -5,6 +5,24 @@
  */
 namespace Magento\Catalog\Model\Indexer\Category\Product\Action;
 
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\Indexer\CacheContext;
+use Magento\Framework\Event\ManagerInterface as EventManagerInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Store\Model\StoreManagerInterface;
+use Magento\Framework\DB\Query\Generator as QueryGenerator;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Catalog\Model\Config;
+use Magento\Catalog\Model\Category;
+use Magento\Framework\Indexer\IndexerRegistry;
+use Magento\Catalog\Model\Indexer\Product\Category as ProductCategoryIndexer;
+
+/**
+ * Action for partial reindex
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
 class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction
 {
     /**
@@ -14,6 +32,47 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
      */
     protected $limitationByCategories;
 
+    /**
+     * @var CacheContext
+     */
+    private $cacheContext;
+
+    /**
+     * @var EventManagerInterface|null
+     */
+    private $eventManager;
+
+    /**
+     * @var IndexerRegistry
+     */
+    private $indexerRegistry;
+
+    /**
+     * @param ResourceConnection $resource
+     * @param StoreManagerInterface $storeManager
+     * @param Config $config
+     * @param QueryGenerator|null $queryGenerator
+     * @param MetadataPool|null $metadataPool
+     * @param CacheContext|null $cacheContext
+     * @param EventManagerInterface|null $eventManager
+     * @param IndexerRegistry|null $indexerRegistry
+     */
+    public function __construct(
+        ResourceConnection $resource,
+        StoreManagerInterface $storeManager,
+        Config $config,
+        QueryGenerator $queryGenerator = null,
+        MetadataPool $metadataPool = null,
+        CacheContext $cacheContext = null,
+        EventManagerInterface $eventManager = null,
+        IndexerRegistry $indexerRegistry = null
+    ) {
+        parent::__construct($resource, $storeManager, $config, $queryGenerator, $metadataPool);
+        $this->cacheContext = $cacheContext ?: ObjectManager::getInstance()->get(CacheContext::class);
+        $this->eventManager = $eventManager ?: ObjectManager::getInstance()->get(EventManagerInterface::class);
+        $this->indexerRegistry = $indexerRegistry ?: ObjectManager::getInstance()->get(IndexerRegistry::class);
+    }
+
     /**
      * Refresh entities index
      *
@@ -25,14 +84,58 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
     {
         $this->limitationByCategories = $entityIds;
         $this->useTempTable = $useTempTable;
+        $indexer = $this->indexerRegistry->get(ProductCategoryIndexer::INDEXER_ID);
+        $workingState = $indexer->isWorking();
 
-        $this->removeEntries();
+        if ($useTempTable && !$workingState && $indexer->isScheduled()) {
+            foreach ($this->storeManager->getStores() as $store) {
+                $this->connection->truncateTable($this->getIndexTable($store->getId()));
+            }
+        } else {
+            $this->removeEntries();
+        }
 
         $this->reindex();
 
+        if ($useTempTable && !$workingState && $indexer->isScheduled()) {
+            foreach ($this->storeManager->getStores() as $store) {
+                $removalCategoryIds = array_diff($this->limitationByCategories, [$this->getRootCategoryId($store)]);
+                $this->connection->delete(
+                    $this->tableMaintainer->getMainTable($store->getId()),
+                    ['category_id IN (?)' => $removalCategoryIds]
+                );
+                $select = $this->connection->select()
+                    ->from($this->tableMaintainer->getMainReplicaTable($store->getId()));
+                $this->connection->query(
+                    $this->connection->insertFromSelect(
+                        $select,
+                        $this->tableMaintainer->getMainTable($store->getId()),
+                        [],
+                        AdapterInterface::INSERT_ON_DUPLICATE
+                    )
+                );
+            }
+        }
+
+        $this->registerCategories($entityIds);
+        $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]);
+
         return $this;
     }
 
+    /**
+     * Register categories assigned to products
+     *
+     * @param array $categoryIds
+     * @return void
+     */
+    private function registerCategories(array $categoryIds)
+    {
+        if ($categoryIds) {
+            $this->cacheContext->registerEntities(Category::CACHE_TAG, $categoryIds);
+        }
+    }
+
     /**
      * Return array of all category root IDs + tree root ID
      *
diff --git a/vendor/magento/module-catalog/Model/Indexer/Product/Category/Action/Rows.php b/vendor/magento/module-catalog/Model/Indexer/Product/Category/Action/Rows.php
index cb708695255..e62252d52b9 100644
--- a/vendor/magento/module-catalog/Model/Indexer/Product/Category/Action/Rows.php
+++ b/vendor/magento/module-catalog/Model/Indexer/Product/Category/Action/Rows.php
@@ -15,6 +15,9 @@ use Magento\Framework\EntityManager\MetadataPool;
 use Magento\Framework\Event\ManagerInterface as EventManagerInterface;
 use Magento\Framework\Indexer\CacheContext;
 use Magento\Store\Model\StoreManagerInterface;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\Indexer\IndexerRegistry;
+use Magento\Catalog\Model\Indexer\Category\Product as CategoryProductIndexer;
 
 /**
  * Category rows indexer.
@@ -40,6 +43,11 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
      */
     private $eventManager;
 
+    /**
+     * @var IndexerRegistry
+     */
+    private $indexerRegistry;
+
     /**
      * @param ResourceConnection $resource
      * @param StoreManagerInterface $storeManager
@@ -48,6 +56,7 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
      * @param MetadataPool|null $metadataPool
      * @param CacheContext|null $cacheContext
      * @param EventManagerInterface|null $eventManager
+     * @param IndexerRegistry|null $indexerRegistry
      */
     public function __construct(
         ResourceConnection $resource,
@@ -56,11 +65,13 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
         QueryGenerator $queryGenerator = null,
         MetadataPool $metadataPool = null,
         CacheContext $cacheContext = null,
-        EventManagerInterface $eventManager = null
+        EventManagerInterface $eventManager = null,
+        IndexerRegistry $indexerRegistry = null
     ) {
         parent::__construct($resource, $storeManager, $config, $queryGenerator, $metadataPool);
         $this->cacheContext = $cacheContext ?: ObjectManager::getInstance()->get(CacheContext::class);
         $this->eventManager = $eventManager ?: ObjectManager::getInstance()->get(EventManagerInterface::class);
+        $this->indexerRegistry = $indexerRegistry ?: ObjectManager::getInstance()->get(IndexerRegistry::class);
     }
 
     /**
@@ -78,12 +89,37 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
 
         $this->limitationByProducts = $idsToBeReIndexed;
         $this->useTempTable = $useTempTable;
+        $indexer = $this->indexerRegistry->get(CategoryProductIndexer::INDEXER_ID);
+        $workingState = $indexer->isWorking();
 
         $affectedCategories = $this->getCategoryIdsFromIndex($idsToBeReIndexed);
 
-        $this->removeEntries();
-
+        if ($useTempTable && !$workingState && $indexer->isScheduled()) {
+            foreach ($this->storeManager->getStores() as $store) {
+                $this->connection->truncateTable($this->getIndexTable($store->getId()));
+            }
+        } else {
+            $this->removeEntries();
+        }
         $this->reindex();
+        if ($useTempTable && !$workingState && $indexer->isScheduled()) {
+            foreach ($this->storeManager->getStores() as $store) {
+                $this->connection->delete(
+                    $this->tableMaintainer->getMainTable($store->getId()),
+                    ['product_id IN (?)' => $this->limitationByProducts]
+                );
+                $select = $this->connection->select()
+                    ->from($this->tableMaintainer->getMainReplicaTable($store->getId()));
+                $this->connection->query(
+                    $this->connection->insertFromSelect(
+                        $select,
+                        $this->tableMaintainer->getMainTable($store->getId()),
+                        [],
+                        AdapterInterface::INSERT_ON_DUPLICATE
+                    )
+                );
+            }
+        }
 
         $affectedCategories = array_merge($affectedCategories, $this->getCategoryIdsFromIndex($idsToBeReIndexed));
 
diff --git a/vendor/magento/module-catalog/Observer/CategoryProductIndexer.php b/vendor/magento/module-catalog/Observer/CategoryProductIndexer.php
index ca87efaa874..bdee84762ca 100644
--- a/vendor/magento/module-catalog/Observer/CategoryProductIndexer.php
+++ b/vendor/magento/module-catalog/Observer/CategoryProductIndexer.php
@@ -8,6 +8,7 @@ declare(strict_types=1);
 namespace Magento\Catalog\Observer;
 
 use Magento\Catalog\Model\Indexer\Category\Product\Processor;
+use Magento\Catalog\Model\Indexer\Category\Flat\State as FlatState;
 use Magento\Framework\Event\Observer;
 use Magento\Framework\Event\ObserverInterface;
 
@@ -21,12 +22,21 @@ class CategoryProductIndexer implements ObserverInterface
      */
     private $processor;
 
+    /**
+     * @var FlatState
+     */
+    private $flatState;
+
     /**
      * @param Processor $processor
+     * @param FlatState $flatState
      */
-    public function __construct(Processor $processor)
-    {
+    public function __construct(
+        Processor $processor,
+        FlatState $flatState
+    ) {
         $this->processor = $processor;
+        $this->flatState = $flatState;
     }
 
     /**
@@ -35,7 +45,7 @@ class CategoryProductIndexer implements ObserverInterface
     public function execute(Observer $observer): void
     {
         $productIds = $observer->getEvent()->getProductIds();
-        if (!empty($productIds) && $this->processor->isIndexerScheduled()) {
+        if (!empty($productIds) && $this->processor->isIndexerScheduled() && $this->flatState->isFlatEnabled()) {
             $this->processor->markIndexerAsInvalid();
         }
     }
diff --git a/vendor/magento/module-catalog-search/Model/Indexer/Fulltext/Plugin/Product/Category/Action/Rows.php b/vendor/magento/module-catalog-search/Model/Indexer/Fulltext/Plugin/Product/Category/Action/Rows.php
new file mode 100644
index 00000000000..2b1844deb11
--- /dev/null
+++ b/vendor/magento/module-catalog-search/Model/Indexer/Fulltext/Plugin/Product/Category/Action/Rows.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+declare(strict_types=1);
+
+namespace Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Product\Category\Action;
+
+use Magento\Framework\Indexer\IndexerRegistry;
+use Magento\CatalogSearch\Model\Indexer\Fulltext as FulltextIndexer;
+use Magento\Catalog\Model\Indexer\Product\Category\Action\Rows as ActionRows;
+
+/**
+ * Catalog search indexer plugin for catalog category products assignment
+ */
+class Rows
+{
+    /**
+     * @var IndexerRegistry
+     */
+    private $indexerRegistry;
+
+    /**
+     * @param IndexerRegistry $indexerRegistry
+     */
+    public function __construct(IndexerRegistry $indexerRegistry)
+    {
+        $this->indexerRegistry = $indexerRegistry;
+    }
+
+    /**
+     * Reindex after catalog category product reindex
+     *
+     * @param ActionRows $subject
+     * @param ActionRows $result
+     * @param array $entityIds
+     * @return ActionRows
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function afterExecute(ActionRows $subject, ActionRows $result, array $entityIds): ActionRows
+    {
+        if (!empty($entityIds)) {
+            $indexer = $this->indexerRegistry->get(FulltextIndexer::INDEXER_ID);
+            if ($indexer->isScheduled()) {
+                $indexer->reindexList($entityIds);
+            }
+        }
+        return $result;
+    }
+}
diff --git a/vendor/magento/module-catalog-search/etc/di.xml b/vendor/magento/module-catalog-search/etc/di.xml
index 28d5035308d..fb31279d0a4 100644
--- a/vendor/magento/module-catalog-search/etc/di.xml
+++ b/vendor/magento/module-catalog-search/etc/di.xml
@@ -75,6 +75,9 @@
     <type name="Magento\Catalog\Model\Product\Action">
         <plugin name="catalogsearchFulltextMassAction" type="Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Product\Action"/>
     </type>
+    <type name="Magento\Catalog\Model\Indexer\Product\Category\Action\Rows">
+        <plugin name="catalogsearchFulltextCategoryAssignment" type="Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Product\Category\Action\Rows"/>
+    </type>
     <type name="Magento\Store\Model\ResourceModel\Store">
         <plugin name="catalogsearchFulltextIndexerStoreView" type="Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Store\View" />
     </type>
diff --git a/vendor/magento/module-elasticsearch/Observer/CategoryProductIndexer.php b/vendor/magento/module-elasticsearch/Observer/CategoryProductIndexer.php
index fd2734bb713..e2b3e18a0ff 100644
--- a/vendor/magento/module-elasticsearch/Observer/CategoryProductIndexer.php
+++ b/vendor/magento/module-elasticsearch/Observer/CategoryProductIndexer.php
@@ -11,6 +11,7 @@ use Magento\CatalogSearch\Model\Indexer\Fulltext\Processor;
 use Magento\Elasticsearch\Model\Config;
 use Magento\Framework\Event\Observer;
 use Magento\Framework\Event\ObserverInterface;
+use Magento\Catalog\Model\Indexer\Category\Flat\State as FlatState;
 
 /**
  * Checks if a category has changed products and depends on indexer configuration.
@@ -27,14 +28,24 @@ class CategoryProductIndexer implements ObserverInterface
      */
     private $processor;
 
+    /**
+     * @var FlatState
+     */
+    private $flatState;
+
     /**
      * @param Config $config
      * @param Processor $processor
+     * @param FlatState $flatState
      */
-    public function __construct(Config $config, Processor $processor)
-    {
+    public function __construct(
+        Config $config,
+        Processor $processor,
+        FlatState $flatState
+    ) {
         $this->processor = $processor;
         $this->config = $config;
+        $this->flatState = $flatState;
     }
 
     /**
@@ -47,7 +58,7 @@ class CategoryProductIndexer implements ObserverInterface
         }
 
         $productIds = $observer->getEvent()->getProductIds();
-        if (!empty($productIds) && $this->processor->isIndexerScheduled()) {
+        if (!empty($productIds) && $this->processor->isIndexerScheduled() && $this->flatState->isFlatEnabled()) {
             $this->processor->markIndexerAsInvalid();
         }
     }
