diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php
index 349a4772633ed086905183a7cc826b26782967d6..de647a34619dfe0355b9ff79277e3fc3ec24d131 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/ChildrenUrlRewriteGenerator.php
@@ -7,6 +7,9 @@ namespace Magento\CatalogUrlRewrite\Model\Category;
 
 use Magento\Catalog\Model\Category;
 use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGeneratorFactory;
+use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
+use Magento\Framework\App\ObjectManager;
 
 class ChildrenUrlRewriteGenerator
 {
@@ -16,16 +19,25 @@ class ChildrenUrlRewriteGenerator
     /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGeneratorFactory */
     protected $categoryUrlRewriteGeneratorFactory;
 
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider
      * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGeneratorFactory $categoryUrlRewriteGeneratorFactory
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
         ChildrenCategoriesProvider $childrenCategoriesProvider,
-        CategoryUrlRewriteGeneratorFactory $categoryUrlRewriteGeneratorFactory
+        CategoryUrlRewriteGeneratorFactory $categoryUrlRewriteGeneratorFactory,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->childrenCategoriesProvider = $childrenCategoriesProvider;
         $this->categoryUrlRewriteGeneratorFactory = $categoryUrlRewriteGeneratorFactory;
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -33,19 +45,22 @@ class ChildrenUrlRewriteGenerator
      *
      * @param int $storeId
      * @param \Magento\Catalog\Model\Category $category
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generate($storeId, Category $category)
+    public function generate($storeId, Category $category, $rootCategoryId = null)
     {
-        $urls = [];
-        foreach ($this->childrenCategoriesProvider->getChildren($category) as $childCategory) {
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) {
             $childCategory->setStoreId($storeId);
             $childCategory->setData('save_rewrites_history', $category->getData('save_rewrites_history'));
-            $urls = array_merge(
-                $urls,
-                $this->categoryUrlRewriteGeneratorFactory->create()->generate($childCategory)
+            /** @var CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator */
+            $categoryUrlRewriteGenerator = $this->categoryUrlRewriteGeneratorFactory->create();
+            $mergeDataProvider->merge(
+                $categoryUrlRewriteGenerator->generate($childCategory, false, $rootCategoryId)
             );
         }
-        return $urls;
+
+        return $mergeDataProvider->getData();
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php
index d4581744bcb07ec86b60e34e5c80ce0e46878d85..c994b424641470bf728765594460266f3c5311f2 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/CurrentUrlRewritesRegenerator.php
@@ -5,13 +5,8 @@
  */
 namespace Magento\CatalogUrlRewrite\Model\Category;
 
-use Magento\Catalog\Model\Category;
-use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
 use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
 use Magento\UrlRewrite\Model\OptionProvider;
-use Magento\UrlRewrite\Model\UrlFinderInterface;
-use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
-use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
 
 class CurrentUrlRewritesRegenerator
 {
@@ -21,25 +16,53 @@ class CurrentUrlRewritesRegenerator
     /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory */
     protected $urlRewriteFactory;
 
-    /** @var UrlFinderInterface */
-    protected $urlFinder;
+    /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite */
+    private $urlRewritePrototype;
 
-    /** @var \Magento\Catalog\Model\Category */
+    /**
+     * @var \Magento\Catalog\Model\Category
+     * @deprecated
+     */
     protected $category;
 
+    /**
+     * @var \Magento\UrlRewrite\Model\UrlFinderInterface
+     * @deprecated
+     */
+    protected $urlFinder;
+
+    /** @var \Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder */
+    private $urlRewriteFinder;
+
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator
      * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory
-     * @param UrlFinderInterface $urlFinder
+     * @param \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder
+     * @param \Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder|null $urlRewriteFinder
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
-        CategoryUrlPathGenerator $categoryUrlPathGenerator,
-        UrlRewriteFactory $urlRewriteFactory,
-        UrlFinderInterface $urlFinder
+        \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator,
+        \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory $urlRewriteFactory,
+        \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder,
+        \Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder $urlRewriteFinder = null,
+        \Magento\UrlRewrite\Model\MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->categoryUrlPathGenerator = $categoryUrlPathGenerator;
         $this->urlRewriteFactory = $urlRewriteFactory;
+        $this->urlRewritePrototype = $urlRewriteFactory->create();
         $this->urlFinder = $urlFinder;
+        $this->urlRewriteFinder = $urlRewriteFinder ?: \Magento\Framework\App\ObjectManager::getInstance()
+            ->get(\Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder::class);
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory =  \Magento\Framework\App\ObjectManager::getInstance()->get(
+                \Magento\UrlRewrite\Model\MergeDataProviderFactory::class
+            );
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -47,70 +70,70 @@ class CurrentUrlRewritesRegenerator
      *
      * @param int $storeId
      * @param \Magento\Catalog\Model\Category $category
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generate($storeId, Category $category)
+    public function generate($storeId, \Magento\Catalog\Model\Category $category, $rootCategoryId = null)
     {
-        $this->category = $category;
-
-        $currentUrlRewrites = $this->urlFinder->findAllByData(
-            [
-                UrlRewrite::STORE_ID => $storeId,
-                UrlRewrite::ENTITY_ID => $category->getId(),
-                UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE,
-            ]
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        $currentUrlRewrites = $this->urlRewriteFinder->findAllByData(
+            $category->getEntityId(),
+            $storeId,
+            CategoryUrlRewriteGenerator::ENTITY_TYPE,
+            $rootCategoryId
         );
 
-        $urlRewrites = [];
         foreach ($currentUrlRewrites as $rewrite) {
-            if ($rewrite->getIsAutogenerated()) {
-                $urlRewrites = array_merge($urlRewrites, $this->generateForAutogenerated($rewrite, $storeId));
-            } else {
-                $urlRewrites = array_merge($urlRewrites, $this->generateForCustom($rewrite, $storeId));
-            }
+            $mergeDataProvider->merge(
+                $rewrite->getIsAutogenerated()
+                ? $this->generateForAutogenerated($rewrite, $storeId, $category)
+                : $this->generateForCustom($rewrite, $storeId, $category)
+            );
         }
-        return $urlRewrites;
+
+        return $mergeDataProvider->getData();
     }
 
     /**
      * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewrite $url
      * @param int $storeId
-     * @return array
+     * @param \Magento\Catalog\Model\Category|null $category
+     * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForAutogenerated($url, $storeId)
+    protected function generateForAutogenerated($url, $storeId, \Magento\Catalog\Model\Category $category = null)
     {
-        $urls = [];
-        if ($this->category->getData('save_rewrites_history')) {
-            $targetPath = $this->categoryUrlPathGenerator->getUrlPathWithSuffix($this->category, $storeId);
+        if ($category->getData('save_rewrites_history')) {
+            $targetPath = $this->categoryUrlPathGenerator->getUrlPathWithSuffix($category, $storeId);
             if ($url->getRequestPath() !== $targetPath) {
-                $urls[$url->getRequestPath() . '_' . $storeId] = $this->urlRewriteFactory->create()
-                    ->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE)
-                    ->setEntityId($this->category->getId())
+                $generatedUrl = clone $this->urlRewritePrototype;
+                $generatedUrl->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE)
+                    ->setEntityId($category->getEntityId())
                     ->setRequestPath($url->getRequestPath())
                     ->setTargetPath($targetPath)
                     ->setRedirectType(OptionProvider::PERMANENT)
                     ->setStoreId($storeId)
                     ->setIsAutogenerated(0);
+                return [$generatedUrl];
             }
         }
-        return $urls;
+        return [];
     }
 
     /**
      * @param \Magento\UrlRewrite\Service\V1\Data\UrlRewrite $url
      * @param int $storeId
-     * @return array
+     * @param \Magento\Catalog\Model\Category|null $category
+     * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForCustom($url, $storeId)
+    protected function generateForCustom($url, $storeId, \Magento\Catalog\Model\Category $category = null)
     {
-        $urls = [];
         $targetPath = !$url->getRedirectType()
             ? $url->getTargetPath()
-            : $this->categoryUrlPathGenerator->getUrlPathWithSuffix($this->category, $storeId);
+            : $this->categoryUrlPathGenerator->getUrlPathWithSuffix($category, $storeId);
         if ($url->getRequestPath() !== $targetPath) {
-            $urls[$url->getRequestPath() . '_' . $storeId] = $this->urlRewriteFactory->create()
-                ->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE)
-                ->setEntityId($this->category->getId())
+            $generatedUrl = clone $this->urlRewritePrototype;
+            $generatedUrl->setEntityType(CategoryUrlRewriteGenerator::ENTITY_TYPE)
+                ->setEntityId($category->getEntityId())
                 ->setRequestPath($url->getRequestPath())
                 ->setTargetPath($targetPath)
                 ->setRedirectType($url->getRedirectType())
@@ -118,7 +141,8 @@ class CurrentUrlRewritesRegenerator
                 ->setDescription($url->getDescription())
                 ->setIsAutogenerated(0)
                 ->setMetadata($url->getMetadata());
+            return [$generatedUrl];
         }
-        return $urls;
+        return [];
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php
index 709baf9e730f9a4ce0dab84799d71b7d39071406..db4b4951a8c8120cc27e6a162399414f9e62c5c4 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Storage.php
@@ -5,30 +5,30 @@
  */
 namespace Magento\CatalogUrlRewrite\Model\Category\Plugin;
 
-use Magento\CatalogUrlRewrite\Model\Category\ProductFactory;
 use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
 use Magento\UrlRewrite\Model\StorageInterface;
 use Magento\UrlRewrite\Model\UrlFinderInterface;
 use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\CatalogUrlRewrite\Model\ResourceModel\Category\Product;
 
 class Storage
 {
     /** @var UrlFinderInterface */
-    protected $urlFinder;
+    private $urlFinder;
 
-    /** @var ProductFactory */
-    protected $productFactory;
+    /** @var Product */
+    private $productResource;
 
     /**
      * @param UrlFinderInterface $urlFinder
-     * @param ProductFactory $productFactory
+     * @param Product $productResource
      */
     public function __construct(
         UrlFinderInterface $urlFinder,
-        ProductFactory $productFactory
+        Product $productResource
     ) {
         $this->urlFinder = $urlFinder;
-        $this->productFactory = $productFactory;
+        $this->productResource = $productResource;
     }
 
     /**
@@ -50,7 +50,7 @@ class Storage
             ];
         }
         if ($toSave) {
-            $this->productFactory->create()->getResource()->saveMultiple($toSave);
+            $this->productResource->saveMultiple($toSave);
         }
     }
 
@@ -62,14 +62,7 @@ class Storage
      */
     public function beforeDeleteByData(StorageInterface $object, array $data)
     {
-        $toRemove = [];
-        $records = $this->urlFinder->findAllByData($data);
-        foreach ($records as $record) {
-            $toRemove[] = $record->getUrlRewriteId();
-        }
-        if ($toRemove) {
-            $this->productFactory->create()->getResource()->removeMultiple($toRemove);
-        }
+        $this->productResource->removeMultipleByProductCategory($data);
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php
index b3feb4bfd9caa7cec9b29585dbebb3dfcf7bef48..7e5d22399e91f04428ecc60f0503bff70aae0649 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Store/Group.php
@@ -35,7 +35,7 @@ class Group
     /** @var ProductUrlRewriteGenerator */
     protected $productUrlRewriteGenerator;
 
-    /** @var StoreManagerInterface  */
+    /** @var StoreManagerInterface */
     protected $storeManager;
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php
index bc09d90d461c4862908587f40915be325096a706..e40aa612002693fe50158e5543fee1858ad68fc8 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/CategoryBasedProductRewriteGenerator.php
@@ -7,11 +7,6 @@ namespace Magento\CatalogUrlRewrite\Model;
 
 use Magento\Catalog\Model\Category;
 use Magento\Catalog\Model\Product;
-use Magento\CatalogUrlRewrite\Model\Product\CanonicalUrlRewriteGenerator;
-use Magento\CatalogUrlRewrite\Model\Product\CategoriesUrlRewriteGenerator;
-use Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator;
-use Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator;
-use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
 use Magento\Store\Model\Store;
 use Magento\Catalog\Model\Product\Visibility;
 
@@ -28,7 +23,7 @@ class CategoryBasedProductRewriteGenerator
     private $productScopeRewriteGenerator;
 
     /**
-     * @param ProductScopeRewriteGenerator $productUrlRewriteGenerator
+     * @param ProductScopeRewriteGenerator $productScopeRewriteGenerator
      */
     public function __construct(
         ProductScopeRewriteGenerator $productScopeRewriteGenerator
@@ -41,9 +36,10 @@ class CategoryBasedProductRewriteGenerator
      *
      * @param \Magento\Catalog\Model\Product $product
      * @param \Magento\Catalog\Model\Category $category
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generate(Product $product, Category $category)
+    public function generate(Product $product, Category $category, $rootCategoryId = null)
     {
         if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE) {
             return [];
@@ -52,10 +48,14 @@ class CategoryBasedProductRewriteGenerator
         $storeId = $product->getStoreId();
 
         $urls = $this->productScopeRewriteGenerator->isGlobalScope($storeId)
-            ? $this->productScopeRewriteGenerator->generateForGlobalScope([$category], $product)
-            : $this->productScopeRewriteGenerator->generateForSpecificStoreView($storeId, [$category], $product);
+            ? $this->productScopeRewriteGenerator->generateForGlobalScope([$category], $product, $rootCategoryId)
+            : $this->productScopeRewriteGenerator->generateForSpecificStoreView(
+                $storeId,
+                [$category],
+                $product,
+                $rootCategoryId
+            );
 
-        $this->product = null;
         return $urls;
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php
index 5748b69ea3ce470d932ed94f0f7009376b64b24d..84ecb5b8a53e89880d772abefaec10356f727cfa 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGenerator.php
@@ -12,6 +12,8 @@ use Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator;
 use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
 use Magento\Store\Model\Store;
 use Magento\Catalog\Api\CategoryRepositoryInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
 
 class CategoryUrlRewriteGenerator
 {
@@ -21,7 +23,10 @@ class CategoryUrlRewriteGenerator
     /** @var StoreViewService */
     protected $storeViewService;
 
-    /** @var \Magento\Catalog\Model\Category */
+    /**
+     * @var \Magento\Catalog\Model\Category
+     * @deprecated
+     */
     protected $category;
 
     /** @var \Magento\CatalogUrlRewrite\Model\Category\CanonicalUrlRewriteGenerator */
@@ -33,6 +38,9 @@ class CategoryUrlRewriteGenerator
     /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator */
     protected $childrenUrlRewriteGenerator;
 
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @var bool
      */
@@ -44,66 +52,83 @@ class CategoryUrlRewriteGenerator
      * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator $childrenUrlRewriteGenerator
      * @param \Magento\CatalogUrlRewrite\Service\V1\StoreViewService $storeViewService
      * @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
         CanonicalUrlRewriteGenerator $canonicalUrlRewriteGenerator,
         CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator,
         ChildrenUrlRewriteGenerator $childrenUrlRewriteGenerator,
         StoreViewService $storeViewService,
-        CategoryRepositoryInterface $categoryRepository
+        CategoryRepositoryInterface $categoryRepository,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->storeViewService = $storeViewService;
         $this->canonicalUrlRewriteGenerator = $canonicalUrlRewriteGenerator;
         $this->childrenUrlRewriteGenerator = $childrenUrlRewriteGenerator;
         $this->currentUrlRewritesRegenerator = $currentUrlRewritesRegenerator;
         $this->categoryRepository = $categoryRepository;
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
-     * {@inheritdoc}
+     * @param \Magento\Catalog\Model\Category $category
+     * @param bool $overrideStoreUrls
+     * @param int|null $rootCategoryId
+     * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generate($category, $overrideStoreUrls = false)
+    public function generate($category, $overrideStoreUrls = false, $rootCategoryId = null)
     {
-        $this->category = $category;
-        $this->overrideStoreUrls = $overrideStoreUrls;
+        if ($rootCategoryId === null) {
+            $rootCategoryId = $category->getEntityId();
+        }
 
-        $storeId = $this->category->getStoreId();
+        $storeId = $category->getStoreId();
         $urls = $this->isGlobalScope($storeId)
-            ? $this->generateForGlobalScope()
-            : $this->generateForSpecificStoreView($storeId);
+            ? $this->generateForGlobalScope($category, $overrideStoreUrls, $rootCategoryId)
+            : $this->generateForSpecificStoreView($storeId, $category, $rootCategoryId);
 
-        $this->category = null;
         return $urls;
     }
 
     /**
      * Generate list of urls for global scope
      *
+     * @param \Magento\Catalog\Model\Category|null $category
+     * @param bool $overrideStoreUrls
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForGlobalScope()
-    {
-        $urls = [];
-        $categoryId = $this->category->getId();
-        foreach ($this->category->getStoreIds() as $storeId) {
+    protected function generateForGlobalScope(
+        Category $category = null,
+        $overrideStoreUrls = false,
+        $rootCategoryId = null
+    ) {
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        $categoryId = $category->getId();
+        foreach ($category->getStoreIds() as $storeId) {
             if (!$this->isGlobalScope($storeId)
-                && $this->isOverrideUrlsForStore($storeId, $categoryId)
+                && $this->isOverrideUrlsForStore($storeId, $categoryId, $overrideStoreUrls)
             ) {
-                $this->updateCategoryUrlForStore($storeId);
-                $urls = array_merge($urls, $this->generateForSpecificStoreView($storeId));
+                $this->updateCategoryUrlForStore($storeId, $category);
+                $mergeDataProvider->merge($this->generateForSpecificStoreView($storeId, $category, $rootCategoryId));
             }
         }
-        return $urls;
+        $result = $mergeDataProvider->getData();
+        return $result;
     }
 
     /**
      * @param int $storeId
      * @param int $categoryId
+     * @param bool $overrideStoreUrls
      * @return bool
      */
-    protected function isOverrideUrlsForStore($storeId, $categoryId)
+    protected function isOverrideUrlsForStore($storeId, $categoryId, $overrideStoreUrls = false)
     {
-        return $this->overrideStoreUrls || !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore(
+        return $overrideStoreUrls || !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore(
             $storeId,
             $categoryId,
             Category::ENTITY
@@ -114,12 +139,18 @@ class CategoryUrlRewriteGenerator
      * Override url key and url path for category in specific Store
      *
      * @param int $storeId
+     * @param \Magento\Catalog\Model\Category|null $category
      * @return void
      */
-    protected function updateCategoryUrlForStore($storeId)
+    protected function updateCategoryUrlForStore($storeId, Category $category = null)
     {
-        $category = $this->categoryRepository->get($this->category->getId(), $storeId);
-        $this->category->addData(['url_key' => $category->getUrlKey(), 'url_path' => $category->getUrlPath()]);
+        $categoryFromRepository = $this->categoryRepository->get($category->getId(), $storeId);
+            $category->addData(
+                [
+                    'url_key' => $categoryFromRepository->getUrlKey(),
+                    'url_path' => $categoryFromRepository->getUrlPath()
+                ]
+            );
     }
 
     /**
@@ -137,15 +168,22 @@ class CategoryUrlRewriteGenerator
      * Generate list of urls per store
      *
      * @param string $storeId
+     * @param \Magento\Catalog\Model\Category|null $category
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForSpecificStoreView($storeId)
+    protected function generateForSpecificStoreView($storeId, Category $category = null, $rootCategoryId = null)
     {
-        $urls = array_merge(
-            $this->canonicalUrlRewriteGenerator->generate($storeId, $this->category),
-            $this->childrenUrlRewriteGenerator->generate($storeId, $this->category),
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category)
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        $mergeDataProvider->merge(
+            $this->canonicalUrlRewriteGenerator->generate($storeId, $category)
         );
-        return $urls;
+        $mergeDataProvider->merge(
+            $this->childrenUrlRewriteGenerator->generate($storeId, $category, $rootCategoryId)
+        );
+        $mergeDataProvider->merge(
+            $this->currentUrlRewritesRegenerator->generate($storeId, $category, $rootCategoryId)
+        );
+        return $mergeDataProvider->getData();
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryHashMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryHashMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..668517f46d89775c32ca28d7882cba0e9d7742f7
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryHashMap.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Catalog\Model\ResourceModel\CategoryFactory;
+use Magento\Catalog\Model\CategoryRepository;
+use Magento\Catalog\Api\Data\CategoryInterface;
+
+/**
+ * Map that holds data for category ids and its subcategories ids
+ */
+class DataCategoryHashMap implements HashMapInterface
+{
+    /** @var int[] */
+    private $hashMap = [];
+
+    /** @var CategoryRepository */
+    private $categoryRepository;
+
+    /** @var CategoryFactory */
+    private $categoryResourceFactory;
+
+    /**
+     * @param CategoryRepository $categoryRepository
+     * @param CategoryFactory $categoryResourceFactory
+     */
+    public function __construct(
+        CategoryRepository $categoryRepository,
+        CategoryFactory $categoryResourceFactory
+    ) {
+        $this->categoryRepository = $categoryRepository;
+        $this->categoryResourceFactory = $categoryResourceFactory;
+    }
+
+    /**
+     * Returns an array of categories ids that includes category identified by $categoryId and all its subcategories
+     *
+     * @param int $categoryId
+     * @return array
+     */
+    public function getAllData($categoryId)
+    {
+        if (!isset($this->hashMap[$categoryId])) {
+            $category = $this->categoryRepository->get($categoryId);
+            $this->hashMap[$categoryId] = $this->getAllCategoryChildrenIds($category);
+        }
+        return $this->hashMap[$categoryId];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getData($categoryId, $key)
+    {
+        $categorySpecificData = $this->getAllData($categoryId);
+        if (isset($categorySpecificData[$key])) {
+            return $categorySpecificData[$key];
+        }
+        return [];
+    }
+
+    /**
+     * Queries the database for sub-categories ids from a category
+     *
+     * @param CategoryInterface $category
+     * @return int[]
+     */
+    private function getAllCategoryChildrenIds(CategoryInterface $category)
+    {
+        $categoryResource = $this->categoryResourceFactory->create();
+        $connection = $categoryResource->getConnection();
+        $select = $connection->select()
+            ->from($categoryResource->getEntityTable(), 'entity_id')
+            ->where($connection->quoteIdentifier('path') . ' LIKE :c_path');
+        $bind = ['c_path' => $category->getPath() . '%'];
+        return $connection->fetchCol($select, $bind);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function resetData($categoryId)
+    {
+        unset($this->hashMap[$categoryId]);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUrlRewriteDatabaseMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUrlRewriteDatabaseMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..032ed5e13023e1de62c7c111ed8889d7e6dab6b6
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUrlRewriteDatabaseMap.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\TemporaryTableService;
+use Magento\Framework\DB\Select;
+use Magento\UrlRewrite\Model\MergeDataProvider;
+
+/**
+ * Map that holds data for category url rewrites entity
+ */
+class DataCategoryUrlRewriteDatabaseMap implements DatabaseMapInterface
+{
+    const ENTITY_TYPE = 'category';
+
+    /** @var string[] */
+    private $createdTableAdapters = [];
+
+    /** @var HashMapPool */
+    private $hashMapPool;
+
+    /** @var ResourceConnection */
+    private $connection;
+
+    /** @var TemporaryTableService */
+    private $temporaryTableService;
+
+    /**
+     * @param ResourceConnection $connection
+     * @param HashMapPool $hashMapPool,
+     * @param TemporaryTableService $temporaryTableService
+     */
+    public function __construct(
+        ResourceConnection $connection,
+        HashMapPool $hashMapPool,
+        TemporaryTableService $temporaryTableService
+    ) {
+        $this->connection = $connection;
+        $this->hashMapPool = $hashMapPool;
+        $this->temporaryTableService = $temporaryTableService;
+    }
+
+    /**
+     * Generates data from categoryId and stores it into a temporary table
+     *
+     * @param int $categoryId
+     * @return void
+     */
+    private function generateTableAdapter($categoryId)
+    {
+        if (!isset($this->createdTableAdapters[$categoryId])) {
+            $this->createdTableAdapters[$categoryId] = $this->generateData($categoryId);
+        }
+    }
+
+    /**
+     * Queries the database for all category url rewrites that are affected by the category identified by $categoryId
+     * It returns the name of the temporary table where the resulting data is stored
+     *
+     * @param int $categoryId
+     * @return string
+     */
+    private function generateData($categoryId)
+    {
+        $urlRewritesConnection = $this->connection->getConnection();
+        $select = $urlRewritesConnection->select()
+            ->from(
+                ['e' => $this->connection->getTableName('url_rewrite')],
+                ['e.*', 'hash_key' => new \Zend_Db_Expr(
+                    "CONCAT(e.store_id,'" . MergeDataProvider::SEPARATOR . "', e.entity_id)"
+                )
+                ]
+            )
+            ->where('entity_type = ?', self::ENTITY_TYPE)
+            ->where(
+                $urlRewritesConnection->prepareSqlCondition(
+                    'entity_id',
+                    [
+                        'in' => array_merge(
+                            $this->hashMapPool->getDataMap(DataCategoryUsedInProductsHashMap::class, $categoryId)
+                                ->getAllData($categoryId),
+                            $this->hashMapPool->getDataMap(DataCategoryHashMap::class, $categoryId)
+                                ->getAllData($categoryId)
+                        )
+                    ]
+                )
+            );
+        $mapName = $this->temporaryTableService->createFromSelect(
+            $select,
+            $this->connection->getConnection(),
+            [
+                'PRIMARY' => ['url_rewrite_id'],
+                'HASHKEY_ENTITY_STORE' => ['hash_key'],
+                'ENTITY_STORE' => ['entity_id', 'store_id']
+            ]
+        );
+        return $mapName;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroyTableAdapter($categoryId)
+    {
+        $this->hashMapPool->resetMap(DataCategoryUsedInProductsHashMap::class, $categoryId);
+        $this->hashMapPool->resetMap(DataCategoryHashMap::class, $categoryId);
+        if (isset($this->createdTableAdapters[$categoryId])) {
+            $this->temporaryTableService->dropTable($this->createdTableAdapters[$categoryId]);
+            unset($this->createdTableAdapters[$categoryId]);
+        }
+    }
+
+    /**
+     * Gets data by criteria from a map identified by a category Id
+     *
+     * @param int $categoryId
+     * @param string $key
+     * @return array
+     */
+    public function getData($categoryId, $key)
+    {
+        $this->generateTableAdapter($categoryId);
+        $urlRewritesConnection = $this->connection->getConnection();
+        $select = $urlRewritesConnection->select()->from(['e' => $this->createdTableAdapters[$categoryId]]);
+        if (strlen($key) > 0) {
+            $select->where('hash_key = ?', $key);
+        }
+
+        return $urlRewritesConnection->fetchAll($select);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUsedInProductsHashMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUsedInProductsHashMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..65200ba1f413d63f06f0d9b319fb6a339d55ae43
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataCategoryUsedInProductsHashMap.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\App\ResourceConnection;
+
+/**
+ * Map that holds data for categories used by products found in root category
+ */
+class DataCategoryUsedInProductsHashMap implements HashMapInterface
+{
+    /** @var int[] */
+    private $hashMap = [];
+
+    /** @var HashMapPool */
+    private $hashMapPool;
+
+    /** @var ResourceConnection */
+    private $connection;
+
+    /**
+     * @param ResourceConnection $connection
+     * @param HashMapPool $hashMapPool
+     */
+    public function __construct(
+        ResourceConnection $connection,
+        HashMapPool $hashMapPool
+    ) {
+        $this->connection = $connection;
+        $this->hashMapPool = $hashMapPool;
+    }
+
+    /**
+     * Returns an array of product ids for all DataProductHashMap list,
+     * that occur in other categories not part of DataCategoryHashMap list
+     *
+     * @param int $categoryId
+     * @return array
+     */
+    public function getAllData($categoryId)
+    {
+        if (!isset($this->hashMap[$categoryId])) {
+            $productsLinkConnection = $this->connection->getConnection();
+            $select = $productsLinkConnection->select()
+                ->from($this->connection->getTableName('catalog_category_product'), ['category_id'])
+                ->where(
+                    $productsLinkConnection->prepareSqlCondition(
+                        'product_id',
+                        [
+                            'in' => $this->hashMapPool->getDataMap(
+                                DataProductHashMap::class,
+                                $categoryId
+                            )->getAllData($categoryId)
+                        ]
+                    )
+                )
+                ->where(
+                    $productsLinkConnection->prepareSqlCondition(
+                        'category_id',
+                        [
+                            'nin' => $this->hashMapPool->getDataMap(
+                                DataCategoryHashMap::class,
+                                $categoryId
+                            )->getAllData($categoryId)
+                        ]
+                    )
+                )->group('category_id');
+
+            $this->hashMap[$categoryId] = $productsLinkConnection->fetchCol($select);
+        }
+        return $this->hashMap[$categoryId];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getData($categoryId, $key)
+    {
+        $categorySpecificData = $this->getAllData($categoryId);
+        if (isset($categorySpecificData[$key])) {
+            return $categorySpecificData[$key];
+        }
+        return [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function resetData($categoryId)
+    {
+        $this->hashMapPool->resetMap(DataProductHashMap::class, $categoryId);
+        $this->hashMapPool->resetMap(DataCategoryHashMap::class, $categoryId);
+        unset($this->hashMap[$categoryId]);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductHashMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductHashMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..4b107043459a3c041181961f5cb0a9c02eeb1a41
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductHashMap.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
+use Magento\Framework\App\ResourceConnection;
+
+/**
+ * Map that holds data for products ids from a category and subcategories
+ */
+class DataProductHashMap implements HashMapInterface
+{
+    /** @var int[] */
+    private $hashMap = [];
+
+    /** @var CollectionFactory */
+    private $collectionFactory;
+
+    /** @var HashMapPool */
+    private $hashMapPool;
+
+    /** @var ResourceConnection */
+    private $connection;
+
+    /**
+     * @param CollectionFactory $collectionFactory
+     * @param HashMapPool $hashMapPool
+     * @param ResourceConnection $connection
+     */
+    public function __construct(
+        CollectionFactory $collectionFactory,
+        HashMapPool $hashMapPool,
+        ResourceConnection $connection
+    ) {
+        $this->collectionFactory = $collectionFactory;
+        $this->hashMapPool = $hashMapPool;
+        $this->connection = $connection;
+    }
+
+    /**
+     * Returns an array of ids of all visible products and assigned to a category and all its subcategories
+     *
+     * @param int $categoryId
+     * @return array
+     */
+    public function getAllData($categoryId)
+    {
+        if (!isset($this->hashMap[$categoryId])) {
+            $productsCollection = $this->collectionFactory->create();
+            $productsCollection->getSelect()
+                ->joinInner(
+                    ['cp' => $this->connection->getTableName('catalog_category_product')],
+                    'cp.product_id = e.entity_id',
+                    []
+                )
+                ->where(
+                    $productsCollection->getConnection()->prepareSqlCondition(
+                        'cp.category_id',
+                        [
+                            'in' => $this->hashMapPool->getDataMap(
+                                DataCategoryHashMap::class,
+                                $categoryId
+                            )->getAllData($categoryId)
+                        ]
+                    )
+                )->group('e.entity_id');
+            $this->hashMap[$categoryId] = $productsCollection->getAllIds();
+        }
+        return $this->hashMap[$categoryId];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getData($categoryId, $key)
+    {
+        $categorySpecificData = $this->getAllData($categoryId);
+        if (isset($categorySpecificData[$key])) {
+            return $categorySpecificData[$key];
+        }
+        return [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function resetData($categoryId)
+    {
+        $this->hashMapPool->resetMap(DataCategoryHashMap::class, $categoryId);
+        unset($this->hashMap[$categoryId]);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductUrlRewriteDatabaseMap.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductUrlRewriteDatabaseMap.php
new file mode 100644
index 0000000000000000000000000000000000000000..19c701d9445c60884be9cbab2b29839bd24a39b2
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DataProductUrlRewriteDatabaseMap.php
@@ -0,0 +1,124 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\TemporaryTableService;
+use Magento\Framework\DB\Select;
+use Magento\UrlRewrite\Model\MergeDataProvider;
+
+/**
+ * Map that holds data for category url rewrites entity
+ */
+class DataProductUrlRewriteDatabaseMap implements DatabaseMapInterface
+{
+    const ENTITY_TYPE = 'product';
+
+    /** @var string[] */
+    private $createdTableAdapters = [];
+
+    /** @var HashMapPool */
+    private $hashMapPool;
+
+    /** @var ResourceConnection */
+    private $connection;
+
+    /** @var TemporaryTableService */
+    private $temporaryTableService;
+
+    /**
+     * @param ResourceConnection $connection
+     * @param HashMapPool $hashMapPool,
+     * @param TemporaryTableService $temporaryTableService
+     */
+    public function __construct(
+        ResourceConnection $connection,
+        HashMapPool $hashMapPool,
+        TemporaryTableService $temporaryTableService
+    ) {
+        $this->connection = $connection;
+        $this->hashMapPool = $hashMapPool;
+        $this->temporaryTableService = $temporaryTableService;
+    }
+
+    /**
+     * Generates data from categoryId and stores it into a temporary table
+     *
+     * @param int $categoryId
+     * @return void
+     */
+    private function generateTableAdapter($categoryId)
+    {
+        if (!isset($this->createdTableAdapters[$categoryId])) {
+            $this->createdTableAdapters[$categoryId] = $this->generateData($categoryId);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getData($categoryId, $key)
+    {
+        $this->generateTableAdapter($categoryId);
+        $urlRewritesConnection = $this->connection->getConnection();
+        $select = $urlRewritesConnection->select()
+            ->from(['e' => $this->createdTableAdapters[$categoryId]])
+            ->where('hash_key = ?', $key);
+        return $urlRewritesConnection->fetchAll($select);
+    }
+
+    /**
+     * Queries the database for all category url rewrites that are affected by the category identified by $categoryId
+     * It returns the name of the temporary table where the resulting data is stored
+     *
+     * @param int $categoryId
+     * @return string
+     */
+    private function generateData($categoryId)
+    {
+        $urlRewritesConnection = $this->connection->getConnection();
+        $select = $urlRewritesConnection->select()
+            ->from(
+                ['e' => $this->connection->getTableName('url_rewrite')],
+                ['e.*', 'hash_key' => new \Zend_Db_Expr(
+                    "CONCAT(e.store_id,'" . MergeDataProvider::SEPARATOR . "', e.entity_id)"
+                )
+                ]
+            )
+            ->where('entity_type = ?', self::ENTITY_TYPE)
+            ->where(
+                $urlRewritesConnection->prepareSqlCondition(
+                    'entity_id',
+                    [
+                        'in' => $this->hashMapPool->getDataMap(DataProductHashMap::class, $categoryId)
+                            ->getAllData($categoryId)
+                    ]
+                )
+            );
+        $mapName = $this->temporaryTableService->createFromSelect(
+            $select,
+            $this->connection->getConnection(),
+            [
+                'PRIMARY' => ['url_rewrite_id'],
+                'HASHKEY_ENTITY_STORE' => ['hash_key'],
+                'ENTITY_STORE' => ['entity_id', 'store_id']
+            ]
+        );
+        return $mapName;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroyTableAdapter($categoryId)
+    {
+        $this->hashMapPool->resetMap(DataProductHashMap::class, $categoryId);
+        if (isset($this->createdTableAdapters[$categoryId])) {
+            $this->temporaryTableService->dropTable($this->createdTableAdapters[$categoryId]);
+            unset($this->createdTableAdapters[$categoryId]);
+        }
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapInterface.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..c2a9a89a33fb5dc689886e5e071c391f99eadd01
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapInterface.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\DB\Select;
+
+/**
+ * Interface for a mysql data type of a map
+ *
+ * Is used to get data by a unique key from a temporary table in mysql to prevent memory usage
+ * It internally holds the knowledge the creation of the actual data and it initializes itself when we call getData
+ * We should always call destroyTableAdapter when we don't need anymore the temporary tables
+ */
+interface DatabaseMapInterface
+{
+    /**
+     * Gets data by key from a map identified by a category Id
+     *
+     * The key is a unique identifier that matches the values of the index used to build the temporary table
+     *
+     * Example "1_2" where ids would correspond to store_id entity_id
+     *
+     * @param int $categoryId
+     * @param string $key
+     * @return array
+     */
+    public function getData($categoryId, $key);
+
+    /**
+     * Destroys data in the temporary table by categoryId
+     * It also destroys the data in other maps that are dependencies used to construct the data
+     *
+     * @param int $categoryId
+     * @return void
+     */
+    public function destroyTableAdapter($categoryId);
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapPool.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapPool.php
new file mode 100644
index 0000000000000000000000000000000000000000..bb94c1972a17c4e4afa1db555fc461cc21625c95
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/DatabaseMapPool.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\ObjectManagerInterface;
+
+/**
+ * Pool for database maps
+ */
+class DatabaseMapPool
+{
+    /**
+     * @var DatabaseMapInterface[]
+     */
+    private $dataArray = [];
+
+    /**
+     * @var ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * Constructor
+     *
+     * @param ObjectManagerInterface $objectManager
+     */
+    public function __construct(
+        ObjectManagerInterface $objectManager
+    ) {
+        $this->objectManager = $objectManager;
+    }
+
+    /**
+     * Gets a map by instance and category Id
+     *
+     * @param string $instanceName
+     * @param int $categoryId
+     * @return DatabaseMapInterface
+     */
+    public function getDataMap($instanceName, $categoryId)
+    {
+        $key = $instanceName . '-' . $categoryId;
+        if (!isset($this->dataArray[$key])) {
+            $instance = $this->objectManager->create(
+                $instanceName,
+                [
+                    'category' => $categoryId
+                ]
+            );
+            if (!$instance instanceof DatabaseMapInterface) {
+                throw new \InvalidArgumentException(
+                    $instanceName . ' does not implement interface ' . DatabaseMapInterface::class
+                );
+            }
+            $this->dataArray[$key] = $instance;
+        }
+        return $this->dataArray[$key];
+    }
+
+    /**
+     * Resets a database map by instance and category Id
+     *
+     * @param string $instanceName
+     * @param int $categoryId
+     * @return void
+     */
+    public function resetMap($instanceName, $categoryId)
+    {
+        $key = $instanceName . '-' . $categoryId;
+        if (isset($this->dataArray[$key])) {
+            $this->dataArray[$key]->destroyTableAdapter($categoryId);
+            unset($this->dataArray[$key]);
+        }
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapInterface.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..341cdb1a4fdb1aba3375bace3a687e9d7fd273ee
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapInterface.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\DB\Select;
+
+/**
+ * Interface for a hash data map
+ *
+ * It is used for classes that will build hash maps and store them into memory
+ * The initialization is done transparently whenever getAllData or getData is called
+ * The map, upon initialization, might have a dependency on some other DataMapInterfaces
+ * The map has to free memory after we're done using it
+ * We need to destroy those maps too when calling resetData
+ */
+interface HashMapInterface
+{
+    /**
+     * Gets all data from a map identified by a category Id
+     *
+     * @param int $categoryId
+     * @return array
+     */
+    public function getAllData($categoryId);
+
+    /**
+     * Gets data by criteria from a map identified by a category Id
+     *
+     * @param int $categoryId
+     * @param string $key
+     * @return array
+     */
+    public function getData($categoryId, $key);
+
+    /**
+     * Resets current map by freeing memory and also to its dependencies
+     *
+     * @param int $categoryId
+     * @return void
+     */
+    public function resetData($categoryId);
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapPool.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapPool.php
new file mode 100644
index 0000000000000000000000000000000000000000..6606812a61995fa24a383170e67c6c99eb1caa90
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/HashMapPool.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Framework\ObjectManagerInterface;
+
+/**
+ * Pool for hash maps
+ */
+class HashMapPool
+{
+    /**
+     * @var HashMapInterface[]
+     */
+    private $dataArray = [];
+
+    /**
+     * @var ObjectManagerInterface
+     */
+    private $objectManager;
+
+    /**
+     * Constructor
+     *
+     * @param ObjectManagerInterface $objectManager
+     */
+    public function __construct(
+        ObjectManagerInterface $objectManager
+    ) {
+        $this->objectManager = $objectManager;
+    }
+
+    /**
+     * Gets a map by instance and category Id
+     *
+     * @param string $instanceName
+     * @param int $categoryId
+     * @return HashMapInterface
+     * @throws \Exception
+     */
+    public function getDataMap($instanceName, $categoryId)
+    {
+        $key = $instanceName . '-' . $categoryId;
+        if (!isset($this->dataArray[$key])) {
+            $instance = $this->objectManager->create(
+                $instanceName,
+                [
+                    'category' => $categoryId
+                ]
+            );
+            if (!$instance instanceof HashMapInterface) {
+                throw new \InvalidArgumentException(
+                    $instanceName . ' does not implement interface ' . HashMapInterface::class
+                );
+            }
+            $this->dataArray[$key] = $instance;
+        }
+        return $this->dataArray[$key];
+    }
+
+    /**
+     * Resets data in a hash map by instance name and category Id
+     *
+     * @param string $instanceName
+     * @param int $categoryId
+     * @return void
+     */
+    public function resetMap($instanceName, $categoryId)
+    {
+        $key = $instanceName . '-' . $categoryId;
+        if (isset($this->dataArray[$key])) {
+            $this->dataArray[$key]->resetData($categoryId);
+            unset($this->dataArray[$key]);
+        }
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Map/UrlRewriteFinder.php b/app/code/Magento/CatalogUrlRewrite/Model/Map/UrlRewriteFinder.php
new file mode 100644
index 0000000000000000000000000000000000000000..bcadfd848e8830461c380b86aeb32bdc116a6a96
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Map/UrlRewriteFinder.php
@@ -0,0 +1,125 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Model\Map;
+
+use Magento\Catalog\Model\Product;
+use Magento\UrlRewrite\Model\UrlFinderInterface;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
+
+/**
+ * Finds specific queried url rewrites identified by specific fields
+ *
+ * A group of identifiers specifies a query consumed by the client to retrieve existing url rewrites from the database
+ * Clients will query a map of DatabaseMapInterface type through this class resulting into a set of url rewrites results
+ * Each map type will fallback to a UrlFinderInterface by identifiers for unmapped values
+ */
+class UrlRewriteFinder
+{
+    const ENTITY_TYPE_CATEGORY = 'category';
+    const ENTITY_TYPE_PRODUCT = 'product';
+
+    /** @var DatabaseMapPool */
+    private $databaseMapPool;
+
+    /** @var UrlFinderInterface */
+    private $urlFinder;
+
+    /** @var UrlRewrite */
+    private $urlRewritePrototype;
+
+    /** @var array */
+    private $urlRewriteClassNames = [];
+
+    /**
+     * @param DatabaseMapPool $databaseMapPool
+     * @param UrlFinderInterface $urlFinder
+     * @param UrlRewriteFactory $urlRewriteFactory
+     * @param string[] $urlRewriteClassNames
+     */
+    public function __construct(
+        DatabaseMapPool $databaseMapPool,
+        UrlFinderInterface $urlFinder,
+        UrlRewriteFactory $urlRewriteFactory,
+        array $urlRewriteClassNames = []
+    ) {
+        $this->databaseMapPool = $databaseMapPool;
+        $this->urlFinder = $urlFinder;
+        $this->urlRewriteClassNames = $urlRewriteClassNames;
+        $this->urlRewritePrototype = $urlRewriteFactory->create();
+    }
+
+    /**
+     * Retrieves existing url rewrites filtered by identifiers from prebuild database maps
+     * This method will fall-back to by using UrlFinderInterface when map type is not found in configured list
+     *
+     * @param int $entityId
+     * @param int $storeId
+     * @param string $entityType
+     * @param int|null $rootCategoryId
+     * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
+     */
+    public function findAllByData($entityId, $storeId, $entityType, $rootCategoryId = null)
+    {
+        if ($rootCategoryId
+            && is_numeric($entityId)
+            && is_numeric($storeId)
+            && is_string($entityType)
+            && isset($this->urlRewriteClassNames[$entityType])
+        ) {
+            $map = $this->databaseMapPool->getDataMap($this->urlRewriteClassNames[$entityType], $rootCategoryId);
+            if ($map) {
+                $key = $storeId . '_' . $entityId;
+                return $this->arrayToUrlRewriteObject($map->getData($rootCategoryId, $key));
+            }
+        }
+
+        return $this->urlFinder->findAllByData(
+            [
+                UrlRewrite::STORE_ID => $storeId,
+                UrlRewrite::ENTITY_ID => $entityId,
+                UrlRewrite::ENTITY_TYPE => $entityType
+            ]
+        );
+    }
+
+    /**
+     * Transfers an array values to url rewrite object values
+     *
+     * @param array $data
+     * @return UrlRewrite[]
+     */
+    private function arrayToUrlRewriteObject(array $data)
+    {
+        foreach ($data as $key => $array) {
+            $data[$key] = $this->createUrlRewrite($array);
+        }
+        return $data;
+    }
+
+    /**
+     * Creates url rewrite object and sets $data to its properties by key->value
+     *
+     * @param array $data
+     * @return UrlRewrite
+     */
+    private function createUrlRewrite(array $data)
+    {
+        $dataObject = clone $this->urlRewritePrototype;
+        $dataObject->setUrlRewriteId($data['url_rewrite_id']);
+        $dataObject->setEntityType($data['entity_type']);
+        $dataObject->setEntityId($data['entity_id']);
+        $dataObject->setRequestPath($data['request_path']);
+        $dataObject->setTargetPath($data['target_path']);
+        $dataObject->setRedirectType($data['redirect_type']);
+        $dataObject->setStoreId($data['store_id']);
+        $dataObject->setDescription($data['description']);
+        $dataObject->setIsAutogenerated($data['is_autogenerated']);
+        $dataObject->setMetadata($data['metadata']);
+
+        return $dataObject;
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php
index 5f1f01f7b08a83222dd84df95838b69e0031a079..dee95f236841ef6c3c4ceb5e32eaf4de8cdda238 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php
@@ -14,20 +14,31 @@ use Magento\UrlRewrite\Model\UrlFinderInterface;
 use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
 use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator;
 use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
-use Magento\Store\Model\StoreManagerInterface;
+use Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder;
+use Magento\Framework\App\ObjectManager;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
 
 /**
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
 class CurrentUrlRewritesRegenerator
 {
-    /** @var Product */
+    /**
+     * @var Product
+     * @deprecated
+     */
     protected $product;
 
-    /** @var ObjectRegistry */
+    /**
+     * @var ObjectRegistry
+     * @deprecated
+     */
     protected $productCategories;
 
-    /** @var UrlFinderInterface */
+    /**
+     * @var UrlFinderInterface
+     * @deprecated
+     */
     protected $urlFinder;
 
     /** @var ProductUrlPathGenerator */
@@ -36,19 +47,38 @@ class CurrentUrlRewritesRegenerator
     /** @var UrlRewriteFactory */
     protected $urlRewriteFactory;
 
+    /** @var UrlRewrite */
+    private $urlRewritePrototype;
+
+    /** @var UrlRewriteFinder */
+    private $urlRewriteFinder;
+
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param UrlFinderInterface $urlFinder
      * @param ProductUrlPathGenerator $productUrlPathGenerator
      * @param UrlRewriteFactory $urlRewriteFactory
+     * @param UrlRewriteFinder|null $urlRewriteFinder
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
         UrlFinderInterface $urlFinder,
         ProductUrlPathGenerator $productUrlPathGenerator,
-        UrlRewriteFactory $urlRewriteFactory
+        UrlRewriteFactory $urlRewriteFactory,
+        UrlRewriteFinder $urlRewriteFinder = null,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->urlFinder = $urlFinder;
         $this->productUrlPathGenerator = $productUrlPathGenerator;
         $this->urlRewriteFactory = $urlRewriteFactory;
+        $this->urlRewritePrototype = $urlRewriteFactory->create();
+        $this->urlRewriteFinder = $urlRewriteFinder ?: ObjectManager::getInstance()->get(UrlRewriteFinder::class);
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -57,104 +87,100 @@ class CurrentUrlRewritesRegenerator
      * @param int $storeId
      * @param Product $product
      * @param ObjectRegistry $productCategories
+     * @param int|null $rootCategoryId
      * @return UrlRewrite[]
      */
-    public function generate($storeId, Product $product, ObjectRegistry $productCategories)
+    public function generate($storeId, Product $product, ObjectRegistry $productCategories, $rootCategoryId = null)
     {
-        $this->product = $product;
-        $this->productCategories = $productCategories;
-
-        $currentUrlRewrites = $this->urlFinder->findAllByData(
-            [
-                UrlRewrite::STORE_ID => $storeId,
-                UrlRewrite::ENTITY_ID => $this->product->getId(),
-                UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
-            ]
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        $currentUrlRewrites = $this->urlRewriteFinder->findAllByData(
+            $product->getEntityId(),
+            $storeId,
+            ProductUrlRewriteGenerator::ENTITY_TYPE,
+            $rootCategoryId
         );
 
-        $urlRewrites = [];
         foreach ($currentUrlRewrites as $currentUrlRewrite) {
-            $category = $this->retrieveCategoryFromMetadata($currentUrlRewrite);
+            $category = $this->retrieveCategoryFromMetadata($currentUrlRewrite, $productCategories);
             if ($category === false) {
                 continue;
             }
-            $url = $currentUrlRewrite->getIsAutogenerated()
-                ? $this->generateForAutogenerated($currentUrlRewrite, $storeId, $category)
-                : $this->generateForCustom($currentUrlRewrite, $storeId, $category);
-            $urlRewrites = array_merge($urlRewrites, $url);
+            $mergeDataProvider->merge(
+                $currentUrlRewrite->getIsAutogenerated()
+                ? $this->generateForAutogenerated($currentUrlRewrite, $storeId, $category, $product)
+                : $this->generateForCustom($currentUrlRewrite, $storeId, $category, $product)
+            );
         }
 
-        $this->product = null;
-        $this->productCategories = null;
-        return $urlRewrites;
+        return $mergeDataProvider->getData();
     }
 
     /**
      * @param UrlRewrite $url
      * @param int $storeId
      * @param Category|null $category
-     * @return array
+     * @param Product|null $product
+     * @return UrlRewrite[]
      */
-    protected function generateForAutogenerated($url, $storeId, $category)
+    protected function generateForAutogenerated($url, $storeId, $category, $product = null)
     {
-        if (!$this->product->getData('save_rewrites_history')) {
-            return [];
-        }
-        $targetPath = $this->productUrlPathGenerator->getUrlPathWithSuffix($this->product, $storeId, $category);
-        if ($url->getRequestPath() === $targetPath) {
-            return [];
+        if ($product->getData('save_rewrites_history')) {
+            $targetPath = $this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId, $category);
+            if ($url->getRequestPath() !== $targetPath) {
+                $generatedUrl = clone $this->urlRewritePrototype;
+                $generatedUrl->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
+                    ->setEntityId($product->getEntityId())
+                    ->setRequestPath($url->getRequestPath())
+                    ->setTargetPath($targetPath)
+                    ->setRedirectType(OptionProvider::PERMANENT)
+                    ->setStoreId($storeId)
+                    ->setDescription($url->getDescription())
+                    ->setIsAutogenerated(0)
+                    ->setMetadata($url->getMetadata());
+                return [$generatedUrl];
+            }
         }
-        return [
-            $this->urlRewriteFactory->create()
-                ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
-                ->setEntityId($this->product->getId())
-                ->setRequestPath($url->getRequestPath())
-                ->setTargetPath($targetPath)
-                ->setRedirectType(OptionProvider::PERMANENT)
-                ->setStoreId($storeId)
-                ->setDescription($url->getDescription())
-                ->setIsAutogenerated(0)
-                ->setMetadata($url->getMetadata())
-        ];
+        return [];
     }
 
     /**
      * @param UrlRewrite $url
      * @param int $storeId
      * @param Category|null $category
-     * @return array
+     * @param Product|null $product
+     * @return UrlRewrite[]
      */
-    protected function generateForCustom($url, $storeId, $category)
+    protected function generateForCustom($url, $storeId, $category, $product = null)
     {
         $targetPath = $url->getRedirectType()
-            ? $this->productUrlPathGenerator->getUrlPathWithSuffix($this->product, $storeId, $category)
+            ? $this->productUrlPathGenerator->getUrlPathWithSuffix($product, $storeId, $category)
             : $url->getTargetPath();
-        if ($url->getRequestPath() === $targetPath) {
-            return [];
-        }
-        return [
-            $this->urlRewriteFactory->create()
-                ->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
-                ->setEntityId($this->product->getId())
+        if ($url->getRequestPath() !== $targetPath) {
+            $generatedUrl = clone $this->urlRewritePrototype;
+            $generatedUrl->setEntityType(ProductUrlRewriteGenerator::ENTITY_TYPE)
+                ->setEntityId($product->getEntityId())
                 ->setRequestPath($url->getRequestPath())
                 ->setTargetPath($targetPath)
                 ->setRedirectType($url->getRedirectType())
                 ->setStoreId($storeId)
                 ->setDescription($url->getDescription())
                 ->setIsAutogenerated(0)
-                ->setMetadata($url->getMetadata())
-        ];
+                ->setMetadata($url->getMetadata());
+            return [$generatedUrl];
+        }
+        return [];
     }
 
     /**
      * @param UrlRewrite $url
+     * @param ObjectRegistry|null $productCategories
      * @return Category|null|bool
      */
-    protected function retrieveCategoryFromMetadata($url)
+    protected function retrieveCategoryFromMetadata($url, ObjectRegistry $productCategories = null)
     {
         $metadata = $url->getMetadata();
         if (isset($metadata['category_id'])) {
-            $category = $this->productCategories->get($metadata['category_id']);
+            $category = $productCategories->get($metadata['category_id']);
             return $category === null ? false : $category;
         }
         return null;
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php
index 5383709ade6e0d250a4086d6adc2b4a7bb6e1f4b..6e357459de8d3790c9e57624c1a7dbb1a9910268 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php
@@ -14,6 +14,8 @@ use Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator;
 use Magento\CatalogUrlRewrite\Service\V1\StoreViewService;
 use Magento\Store\Model\Store;
 use Magento\Store\Model\StoreManagerInterface;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
+use Magento\Framework\App\ObjectManager;
 
 /**
  * Class ProductScopeRewriteGenerator
@@ -56,6 +58,9 @@ class ProductScopeRewriteGenerator
      */
     private $canonicalUrlRewriteGenerator;
 
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param StoreViewService $storeViewService
      * @param StoreManagerInterface $storeManager
@@ -64,6 +69,7 @@ class ProductScopeRewriteGenerator
      * @param CategoriesUrlRewriteGenerator $categoriesUrlRewriteGenerator
      * @param CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator
      * @param AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
         StoreViewService $storeViewService,
@@ -72,7 +78,8 @@ class ProductScopeRewriteGenerator
         CanonicalUrlRewriteGenerator $canonicalUrlRewriteGenerator,
         CategoriesUrlRewriteGenerator $categoriesUrlRewriteGenerator,
         CurrentUrlRewritesRegenerator $currentUrlRewritesRegenerator,
-        AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator
+        AnchorUrlRewriteGenerator $anchorUrlRewriteGenerator,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->storeViewService = $storeViewService;
         $this->storeManager = $storeManager;
@@ -81,6 +88,10 @@ class ProductScopeRewriteGenerator
         $this->categoriesUrlRewriteGenerator = $categoriesUrlRewriteGenerator;
         $this->currentUrlRewritesRegenerator = $currentUrlRewritesRegenerator;
         $this->anchorUrlRewriteGenerator = $anchorUrlRewriteGenerator;
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -97,36 +108,44 @@ class ProductScopeRewriteGenerator
     /**
      * Generate url rewrites for global scope
      *
+     * @param \Magento\Framework\Data\Collection|\Magento\Catalog\Model\Category[] $productCategories
      * @param Product $product
-     * @param \Magento\Framework\Data\Collection $productCategories
+     * @param int|null $rootCategoryId
      * @return array
      */
-    public function generateForGlobalScope($productCategories, Product $product)
+    public function generateForGlobalScope($productCategories, Product $product, $rootCategoryId = null)
     {
-        $urls = [];
         $productId = $product->getEntityId();
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
 
         foreach ($product->getStoreIds() as $id) {
-            if (!$this->isGlobalScope($id)
-                && !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore($id, $productId, Product::ENTITY)
-            ) {
-                $urls = array_merge($urls, $this->generateForSpecificStoreView($id, $productCategories, $product));
+            if (!$this->isGlobalScope($id) &&
+                !$this->storeViewService->doesEntityHaveOverriddenUrlKeyForStore(
+                    $id,
+                    $productId,
+                    Product::ENTITY
+                )) {
+                $mergeDataProvider->merge(
+                    $this->generateForSpecificStoreView($id, $productCategories, $product, $rootCategoryId)
+                );
             }
         }
 
-        return $urls;
+        return $mergeDataProvider->getData();
     }
 
     /**
      * Generate list of urls for specific store view
      *
      * @param int $storeId
-     * @param \Magento\Framework\Data\Collection $productCategories
+     * @param \Magento\Framework\Data\Collection|Category[] $productCategories
      * @param \Magento\Catalog\Model\Product $product
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generateForSpecificStoreView($storeId, $productCategories, Product $product)
+    public function generateForSpecificStoreView($storeId, $productCategories, Product $product, $rootCategoryId = null)
     {
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
         $categories = [];
         foreach ($productCategories as $category) {
             if ($this->isCategoryProperForGenerating($category, $storeId)) {
@@ -134,23 +153,26 @@ class ProductScopeRewriteGenerator
             }
         }
         $productCategories = $this->objectRegistryFactory->create(['entities' => $categories]);
-        /**
-         * @var $urls \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
-         */
-        $urls = array_merge(
-            $this->canonicalUrlRewriteGenerator->generate($storeId, $product),
-            $this->categoriesUrlRewriteGenerator->generate($storeId, $product, $productCategories),
-            $this->currentUrlRewritesRegenerator->generate($storeId, $product, $productCategories),
+
+        $mergeDataProvider->merge(
+            $this->canonicalUrlRewriteGenerator->generate($storeId, $product)
+        );
+        $mergeDataProvider->merge(
+            $this->categoriesUrlRewriteGenerator->generate($storeId, $product, $productCategories)
+        );
+        $mergeDataProvider->merge(
+            $this->currentUrlRewritesRegenerator->generate(
+                $storeId,
+                $product,
+                $productCategories,
+                $rootCategoryId
+            )
+        );
+        $mergeDataProvider->merge(
             $this->anchorUrlRewriteGenerator->generate($storeId, $product, $productCategories)
         );
 
-        /* Reduce duplicates. Last wins */
-        $result = [];
-        foreach ($urls as $url) {
-            $result[$url->getTargetPath() . '-' . $url->getStoreId()] = $url;
-        }
-        $this->productCategories = null;
-        return $result;
+        return $mergeDataProvider->getData();
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php
index 0ff4877405477f163060443ef08de7bd71c2d4bb..a45b33bcc52a2570030c83a8b3052808a9aad144 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlRewriteGenerator.php
@@ -31,7 +31,10 @@ class ProductUrlRewriteGenerator
      */
     protected $storeViewService;
 
-    /** @var \Magento\Catalog\Model\Product */
+    /**
+     * @var \Magento\Catalog\Model\Product
+     * @deprecated
+     */
     protected $product;
 
     /**
@@ -119,26 +122,25 @@ class ProductUrlRewriteGenerator
      * Generate product url rewrites
      *
      * @param \Magento\Catalog\Model\Product $product
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    public function generate(Product $product)
+    public function generate(Product $product, $rootCategoryId = null)
     {
         if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE) {
             return [];
         }
 
-        $this->product = $product;
-        $storeId = $this->product->getStoreId();
+        $storeId = $product->getStoreId();
 
         $productCategories = $product->getCategoryCollection()
             ->addAttributeToSelect('url_key')
             ->addAttributeToSelect('url_path');
 
         $urls = $this->isGlobalScope($storeId)
-            ? $this->generateForGlobalScope($productCategories)
-            : $this->generateForSpecificStoreView($storeId, $productCategories);
+            ? $this->generateForGlobalScope($productCategories, $product, $rootCategoryId)
+            : $this->generateForSpecificStoreView($storeId, $productCategories, $product, $rootCategoryId);
 
-        $this->product = null;
         return $urls;
     }
 
@@ -159,11 +161,17 @@ class ProductUrlRewriteGenerator
      *
      * @deprecated
      * @param \Magento\Framework\Data\Collection $productCategories
+     * @param \Magento\Catalog\Model\Product|null $product
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForGlobalScope($productCategories)
+    protected function generateForGlobalScope($productCategories, $product = null, $rootCategoryId = null)
     {
-        return $this->getProductScopeRewriteGenerator()->generateForGlobalScope($productCategories, $this->product);
+        return $this->getProductScopeRewriteGenerator()->generateForGlobalScope(
+            $productCategories,
+            $product,
+            $rootCategoryId
+        );
     }
 
     /**
@@ -172,12 +180,18 @@ class ProductUrlRewriteGenerator
      * @deprecated
      * @param int $storeId
      * @param \Magento\Framework\Data\Collection $productCategories
+     * @param Product|null $product
+     * @param int|null $rootCategoryId
      * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
      */
-    protected function generateForSpecificStoreView($storeId, $productCategories)
-    {
+    protected function generateForSpecificStoreView(
+        $storeId,
+        $productCategories,
+        $product = null,
+        $rootCategoryId = null
+    ) {
         return $this->getProductScopeRewriteGenerator()
-            ->generateForSpecificStoreView($storeId, $productCategories, $this->product);
+            ->generateForSpecificStoreView($storeId, $productCategories, $product, $rootCategoryId);
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php
index f2043d305c4e8545e1d01e4da7cf0c9c0f511640..ead44dca8a0df9040de8f4e2b1f8c710e064518e 100644
--- a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php
+++ b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php
@@ -6,6 +6,7 @@
 namespace Magento\CatalogUrlRewrite\Model\ResourceModel\Category;
 
 use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
+use Magento\UrlRewrite\Model\Storage\DbStorage;
 
 class Product extends AbstractDb
 {
@@ -55,6 +56,8 @@ class Product extends AbstractDb
     }
 
     /**
+     * Removes data by primary key
+     *
      * @param array $removeData
      * @return int
      */
@@ -65,4 +68,36 @@ class Product extends AbstractDb
             ['url_rewrite_id in (?)' => $removeData]
         );
     }
+
+    /**
+     * Removes multiple entities from url_rewrite table using entities from catalog_url_rewrite_product_category
+     * Example: $filter = ['category_id' => [1, 2, 3], 'product_id' => [1, 2, 3]]
+     *
+     * @param array $filter
+     * @return int
+     */
+    public function removeMultipleByProductCategory(array $filter)
+    {
+        return $this->getConnection()->delete(
+            $this->getTable(self::TABLE_NAME),
+            ['url_rewrite_id in (?)' => $this->prepareSelect($filter)]
+        );
+    }
+
+    /**
+     * Prepare select statement for specific filter
+     *
+     * @param array $data
+     * @return \Magento\Framework\DB\Select
+     */
+    private function prepareSelect($data)
+    {
+        $select = $this->getConnection()->select();
+        $select->from($this->getTable(DbStorage::TABLE_NAME), 'url_rewrite_id');
+
+        foreach ($data as $column => $value) {
+            $select->where($this->getConnection()->quoteIdentifier($column) . ' IN (?)', $value);
+        }
+        return $select;
+    }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
index a4847fa11b1c40911d08831adbf86553d39fc90f..f76db7bef44d5b81936015bb033990c36205bc54 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
@@ -20,6 +20,9 @@ use Magento\UrlRewrite\Model\OptionProvider;
 use Magento\UrlRewrite\Model\UrlFinderInterface;
 use Magento\Framework\Event\ObserverInterface;
 use Magento\Catalog\Model\Product\Visibility;
+use Magento\Framework\App\ObjectManager;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
+
 /**
  * Class AfterImportDataObserver
  *
@@ -96,6 +99,9 @@ class AfterImportDataObserver implements ObserverInterface
         'visibility',
     ];
 
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param \Magento\Catalog\Model\ProductFactory $catalogProductFactory
      * @param \Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory $objectRegistryFactory
@@ -105,6 +111,7 @@ class AfterImportDataObserver implements ObserverInterface
      * @param UrlPersistInterface $urlPersist
      * @param UrlRewriteFactory $urlRewriteFactory
      * @param UrlFinderInterface $urlFinder
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      * @throws \InvalidArgumentException
      * @SuppressWarnings(PHPMD.ExcessiveParameterList)
      */
@@ -116,7 +123,8 @@ class AfterImportDataObserver implements ObserverInterface
         \Magento\Store\Model\StoreManagerInterface $storeManager,
         UrlPersistInterface $urlPersist,
         UrlRewriteFactory $urlRewriteFactory,
-        UrlFinderInterface $urlFinder
+        UrlFinderInterface $urlFinder,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->urlPersist = $urlPersist;
         $this->catalogProductFactory = $catalogProductFactory;
@@ -126,6 +134,10 @@ class AfterImportDataObserver implements ObserverInterface
         $this->storeManager = $storeManager;
         $this->urlRewriteFactory = $urlRewriteFactory;
         $this->urlFinder = $urlFinder;
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -258,24 +270,16 @@ class AfterImportDataObserver implements ObserverInterface
      */
     protected function generateUrls()
     {
-        /**
-         * @var $urls \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[]
-         */
-        $urls = array_merge(
-            $this->canonicalUrlRewriteGenerate(),
-            $this->categoriesUrlRewriteGenerate(),
-            $this->currentUrlRewritesRegenerate()
-        );
-
-        /* Reduce duplicates. Last wins */
-        $result = [];
-        foreach ($urls as $url) {
-            $result[$url->getTargetPath() . '-' . $url->getStoreId()] = $url;
-        }
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
+        $mergeDataProvider->merge($this->canonicalUrlRewriteGenerate());
+        $mergeDataProvider->merge($this->categoriesUrlRewriteGenerate());
+        $mergeDataProvider->merge($this->currentUrlRewritesRegenerate());
         $this->productCategories = null;
 
+        unset($this->products);
         $this->products = [];
-        return $result;
+
+        return $mergeDataProvider->getData();
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php
index 5a4946d50526aa6d3bb7ed3e61dcf135863cc52a..dd0250049a426187f674ac61ad60e8c78ac02f67 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php
@@ -8,54 +8,50 @@ namespace Magento\CatalogUrlRewrite\Observer;
 use Magento\Catalog\Model\Category;
 use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
 use Magento\CatalogUrlRewrite\Model\UrlRewriteBunchReplacer;
-use Magento\Framework\App\ObjectManager;
-use Magento\UrlRewrite\Model\UrlPersistInterface;
 use Magento\Framework\Event\ObserverInterface;
+use Magento\CatalogUrlRewrite\Model\Map\DatabaseMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap;
 
 class CategoryProcessUrlRewriteSavingObserver implements ObserverInterface
 {
     /** @var CategoryUrlRewriteGenerator */
-    protected $categoryUrlRewriteGenerator;
+    private $categoryUrlRewriteGenerator;
 
-    /** @var UrlPersistInterface */
-    protected $urlPersist;
-
-    /**
-     * @var UrlRewriteBunchReplacer
-     */
+    /** @var UrlRewriteBunchReplacer */
     private $urlRewriteBunchReplacer;
 
     /** @var UrlRewriteHandler */
-    protected $urlRewriteHandler;
+    private $urlRewriteHandler;
+
+    /** @var DatabaseMapPool */
+    private $databaseMapPool;
+
+    /** @var string[] */
+    private $dataUrlRewriteClassNames;
 
     /**
      * @param CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator
-     * @param UrlPersistInterface $urlPersist
      * @param UrlRewriteHandler $urlRewriteHandler
+     * @param UrlRewriteBunchReplacer $urlRewriteBunchReplacer
+     * @param DatabaseMapPool $databaseMapPool
+     * @param string[] $dataUrlRewriteClassNames
      */
     public function __construct(
         CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator,
-        UrlPersistInterface $urlPersist,
-        UrlRewriteHandler $urlRewriteHandler
+        UrlRewriteHandler $urlRewriteHandler,
+        UrlRewriteBunchReplacer $urlRewriteBunchReplacer,
+        DatabaseMapPool $databaseMapPool,
+        $dataUrlRewriteClassNames = [
+        DataCategoryUrlRewriteDatabaseMap::class,
+        DataProductUrlRewriteDatabaseMap::class
+        ]
     ) {
         $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator;
-        $this->urlPersist = $urlPersist;
         $this->urlRewriteHandler = $urlRewriteHandler;
-    }
-
-    /**
-     * Retrieve Url Rewrite Replacer based on bunches
-     *
-     * @deprecated
-     * @return UrlRewriteBunchReplacer
-     */
-    private function getUrlRewriteBunchReplacer()
-    {
-        if (!$this->urlRewriteBunchReplacer) {
-            $this->urlRewriteBunchReplacer = ObjectManager::getInstance()->get(UrlRewriteBunchReplacer::class);
-        }
-
-        return $this->urlRewriteBunchReplacer;
+        $this->urlRewriteBunchReplacer = $urlRewriteBunchReplacer;
+        $this->databaseMapPool = $databaseMapPool;
+        $this->dataUrlRewriteClassNames = $dataUrlRewriteClassNames;
     }
 
     /**
@@ -67,7 +63,7 @@ class CategoryProcessUrlRewriteSavingObserver implements ObserverInterface
     public function execute(\Magento\Framework\Event\Observer $observer)
     {
         /** @var Category $category */
-        $category = $observer->getEvent()->getCategory();
+        $category = $observer->getEvent()->getData('category');
         if ($category->getParentId() == Category::TREE_ROOT_ID) {
             return;
         }
@@ -75,12 +71,28 @@ class CategoryProcessUrlRewriteSavingObserver implements ObserverInterface
             || $category->dataHasChangedFor('is_anchor')
             || $category->getIsChangedProductList()
         ) {
-            $urlRewrites = array_merge(
-                $this->categoryUrlRewriteGenerator->generate($category),
-                $this->urlRewriteHandler->generateProductUrlRewrites($category)
-            );
+            $categoryUrlRewriteResult = $this->categoryUrlRewriteGenerator->generate($category);
+            $this->urlRewriteBunchReplacer->doBunchReplace($categoryUrlRewriteResult);
+
+            $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category);
+            $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult);
 
-            $this->getUrlRewriteBunchReplacer()->doBunchReplace($urlRewrites);
+            //frees memory for maps that are self-initialized in multiple classes that were called by the generators
+            $this->resetUrlRewritesDataMaps($category);
         }
     }
+
+    /**
+     * Resets used data maps to free up memory and temporary tables
+     *
+     * @param Category $category
+     * @return void
+     */
+    private function resetUrlRewritesDataMaps($category)
+    {
+        foreach ($this->dataUrlRewriteClassNames as $className) {
+            $this->databaseMapPool->resetMap($className, $category->getEntityId());
+        }
+
+    }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php
index ff3ca37f2e23ee7b7b3ab74f7e1f3b8a13e734eb..73b7c2b8167bb3bab80e6979c3687f89f8c58fa6 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php
@@ -13,6 +13,7 @@ use Magento\Framework\App\ObjectManager;
 use Magento\Framework\Event\Observer as EventObserver;
 use Magento\UrlRewrite\Model\UrlPersistInterface;
 use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\UrlRewrite\Model\MergeDataProviderFactory;
 
 class UrlRewriteHandler
 {
@@ -39,25 +40,34 @@ class UrlRewriteHandler
      */
     private $categoryBasedProductRewriteGenerator;
 
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider */
+    private $mergeDataProviderPrototype;
+
     /**
      * @param \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider
      * @param CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator
      * @param ProductUrlRewriteGenerator $productUrlRewriteGenerator
      * @param UrlPersistInterface $urlPersist
      * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
+     * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory
      */
     public function __construct(
         \Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider $childrenCategoriesProvider,
         CategoryUrlRewriteGenerator $categoryUrlRewriteGenerator,
         ProductUrlRewriteGenerator $productUrlRewriteGenerator,
         UrlPersistInterface $urlPersist,
-        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
+        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory,
+        MergeDataProviderFactory $mergeDataProviderFactory = null
     ) {
         $this->childrenCategoriesProvider = $childrenCategoriesProvider;
         $this->categoryUrlRewriteGenerator = $categoryUrlRewriteGenerator;
         $this->productUrlRewriteGenerator = $productUrlRewriteGenerator;
         $this->urlPersist = $urlPersist;
         $this->productCollectionFactory = $productCollectionFactory;
+        if (!isset($mergeDataProviderFactory)) {
+            $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class);
+        }
+        $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create();
     }
 
     /**
@@ -68,10 +78,10 @@ class UrlRewriteHandler
      */
     public function generateProductUrlRewrites(Category $category)
     {
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
         $this->isSkippedProduct = [];
         $saveRewriteHistory = $category->getData('save_rewrites_history');
         $storeId = $category->getStoreId();
-        $productUrls = [];
         if ($category->getAffectedProductIds()) {
             $this->isSkippedProduct = $category->getAffectedProductIds();
             $collection = $this->productCollectionFactory->create()
@@ -84,38 +94,54 @@ class UrlRewriteHandler
             foreach ($collection as $product) {
                 $product->setStoreId($storeId);
                 $product->setData('save_rewrites_history', $saveRewriteHistory);
-                $productUrls = array_merge($productUrls, $this->productUrlRewriteGenerator->generate($product));
+                $mergeDataProvider->merge(
+                    $this->productUrlRewriteGenerator->generate($product, $category->getEntityId())
+                );
             }
         } else {
-            $productUrls = array_merge(
-                $productUrls,
-                $this->getCategoryProductsUrlRewrites($category, $storeId, $saveRewriteHistory)
+            $mergeDataProvider->merge(
+                $this->getCategoryProductsUrlRewrites(
+                    $category,
+                    $storeId,
+                    $saveRewriteHistory,
+                    $category->getEntityId()
+                )
             );
         }
         foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) {
-            $productUrls = array_merge(
-                $productUrls,
-                $this->getCategoryProductsUrlRewrites($childCategory, $storeId, $saveRewriteHistory)
+            $mergeDataProvider->merge(
+                $this->getCategoryProductsUrlRewrites(
+                    $childCategory,
+                    $storeId,
+                    $saveRewriteHistory,
+                    $category->getEntityId()
+                )
             );
         }
-        return $productUrls;
+
+        return $mergeDataProvider->getData();
     }
 
     /**
      * @param Category $category
      * @param int $storeId
      * @param bool $saveRewriteHistory
+     * @param int|null $rootCategoryId
      * @return UrlRewrite[]
      */
-    public function getCategoryProductsUrlRewrites(Category $category, $storeId, $saveRewriteHistory)
-    {
+    public function getCategoryProductsUrlRewrites(
+        Category $category,
+        $storeId,
+        $saveRewriteHistory,
+        $rootCategoryId = null
+    ) {
+        $mergeDataProvider = clone $this->mergeDataProviderPrototype;
         /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */
         $productCollection = $category->getProductCollection()
             ->addAttributeToSelect('name')
             ->addAttributeToSelect('visibility')
             ->addAttributeToSelect('url_key')
             ->addAttributeToSelect('url_path');
-        $productUrls = [];
         foreach ($productCollection as $product) {
             if (in_array($product->getId(), $this->isSkippedProduct)) {
                 continue;
@@ -123,12 +149,12 @@ class UrlRewriteHandler
             $this->isSkippedProduct[] = $product->getId();
             $product->setStoreId($storeId);
             $product->setData('save_rewrites_history', $saveRewriteHistory);
-            $productUrls = array_merge(
-                $productUrls,
-                $this->getCategoryBasedProductRewriteGenerator()->generate($product, $category)
+            $mergeDataProvider->merge(
+                $this->getCategoryBasedProductRewriteGenerator()->generate($product, $category, $rootCategoryId)
             );
         }
-        return $productUrls;
+
+        return $mergeDataProvider->getData();
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php
index 2766bf630739e4fe0562249ee82253fe661786f6..f41d35f7688f3b4e2b88d1dfaa61ef1218d6a842 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/ChildrenUrlRewriteGeneratorTest.php
@@ -10,19 +10,22 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
 class ChildrenUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
 {
     /** @var \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator */
-    protected $childrenUrlRewriteGenerator;
+    private $childrenUrlRewriteGenerator;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $category;
+    private $category;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $childrenCategoriesProvider;
+    private $childrenCategoriesProvider;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $categoryUrlRewriteGeneratorFactory;
+    private $categoryUrlRewriteGeneratorFactory;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $categoryUrlRewriteGenerator;
+    private $categoryUrlRewriteGenerator;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
 
     protected function setUp()
     {
@@ -37,18 +40,29 @@ class ChildrenUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->categoryUrlRewriteGenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::class
         )->disableOriginalConstructor()->getMock();
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
+
         $this->childrenUrlRewriteGenerator = (new ObjectManager($this))->getObject(
             \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator::class,
             [
                 'childrenCategoriesProvider' => $this->childrenCategoriesProvider,
-                'categoryUrlRewriteGeneratorFactory' => $this->categoryUrlRewriteGeneratorFactory
+                'categoryUrlRewriteGeneratorFactory' => $this->categoryUrlRewriteGeneratorFactory,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory
             ]
         );
     }
 
     public function testNoChildrenCategories()
     {
-        $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, false)
+        $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, true)
             ->will($this->returnValue([]));
 
         $this->assertEquals([], $this->childrenUrlRewriteGenerator->generate('store_id', $this->category));
@@ -64,18 +78,28 @@ class ChildrenUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $childCategory->expects($this->once())->method('setStoreId')->with($storeId);
         $childCategory->expects($this->once())->method('setData')
             ->with('save_rewrites_history', $saveRewritesHistory);
-        $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, false)
+        $this->childrenCategoriesProvider->expects($this->once())->method('getChildren')->with($this->category, true)
             ->will($this->returnValue([$childCategory]));
         $this->category->expects($this->any())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue($saveRewritesHistory));
         $this->categoryUrlRewriteGeneratorFactory->expects($this->once())->method('create')
             ->will($this->returnValue($this->categoryUrlRewriteGenerator));
-        $this->categoryUrlRewriteGenerator->expects($this->once())->method('generate')->with($childCategory)
-            ->will($this->returnValue([['url-1', 'url-2']]));
+        $url1 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
+        $url1->setRequestPath('category-1')
+            ->setStoreId(1);
+        $url2 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
+        $url2->setRequestPath('category-2')
+            ->setStoreId(2);
+        $url3 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
+        $url3->setRequestPath('category-1')
+            ->setStoreId(1);
+        $this->categoryUrlRewriteGenerator->expects($this->once())->method('generate')
+            ->with($childCategory, false, 1)
+            ->will($this->returnValue([$url1, $url2, $url3]));
 
         $this->assertEquals(
-            [['url-1', 'url-2']],
-            $this->childrenUrlRewriteGenerator->generate($storeId, $this->category)
+            ['category-1_1'  => $url1, 'category-2_2' => $url2],
+            $this->childrenUrlRewriteGenerator->generate($storeId, $this->category, 1)
         );
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php
index 2f9a9463646f1f54382720c649e22565eec62683..483df828fb0446a046ca649de83f0511027d531c 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php
@@ -14,25 +14,25 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
 class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 {
     /** @var \Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator */
-    protected $currentUrlRewritesRegenerator;
-
-    /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $filter;
-
-    /** @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlFinder;
+    private $currentUrlRewritesRegenerator;
 
     /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */
-    protected $categoryUrlPathGenerator;
+    private $categoryUrlPathGenerator;
 
     /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */
-    protected $category;
+    private $category;
 
     /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlRewriteFactory;
+    private $urlRewriteFactory;
 
     /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlRewrite;
+    private $urlRewrite;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $urlRewriteFinder;
 
     protected function setUp()
     {
@@ -43,41 +43,50 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             ->disableOriginalConstructor()->getMock();
         $this->category = $this->getMockBuilder(\Magento\Catalog\Model\Category::class)
             ->disableOriginalConstructor()->getMock();
-        $this->filter = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\Filter::class)
-            ->disableOriginalConstructor()->getMock();
-        $this->filter->expects($this->any())->method('setStoreId')->will($this->returnSelf());
-        $this->filter->expects($this->any())->method('setEntityId')->will($this->returnSelf());
-        $this->urlFinder = $this->getMockBuilder(\Magento\UrlRewrite\Model\UrlFinderInterface::class)
-            ->disableOriginalConstructor()->getMock();
-        $this->categoryUrlPathGenerator = $this->getMockBuilder(
+        $this->categoryUrlPathGenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator::class
         )->disableOriginalConstructor()->getMock();
-        $this->currentUrlRewritesRegenerator = (new ObjectManager($this))->getObject(
+        $this->urlRewriteFinder = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder::class)
+            ->disableOriginalConstructor()->getMock();
+        $this->urlRewriteFactory->expects($this->once())->method('create')
+            ->willReturn($this->urlRewrite);
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
+
+        $this->currentUrlRewritesRegenerator = (new ObjectManager($this))->getObject(   
             \Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator::class,
             [
-                'urlFinder' => $this->urlFinder,
                 'categoryUrlPathGenerator' => $this->categoryUrlPathGenerator,
-                'urlRewriteFactory' => $this->urlRewriteFactory
+                'urlRewriteFactory' => $this->urlRewriteFactory,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory,
+                'urlRewriteFinder' => $this->urlRewriteFinder
             ]
         );
     }
 
     public function testIsAutogeneratedWithoutSaveRewriteHistory()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([[UrlRewrite::IS_AUTOGENERATED => 1]])));
         $this->category->expects($this->once())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue(false));
 
         $this->assertEquals(
             [],
-            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category)
+            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category, $this->category)
         );
     }
 
     public function testSkipGenerationForAutogenerated()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will(
                 $this->returnValue(
                     $this->getCurrentRewritesMocks(
@@ -94,7 +103,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             [],
-            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category)
+            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category, $this->category)
         );
     }
 
@@ -104,7 +113,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $targetPath = 'some-path.html';
         $storeId = 2;
         $categoryId = 12;
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will(
                 $this->returnValue(
                     $this->getCurrentRewritesMocks(
@@ -120,23 +129,24 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
                     )
                 )
             );
-        $this->category->expects($this->any())->method('getId')->will($this->returnValue($categoryId));
+
+        $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue($categoryId));
         $this->category->expects($this->once())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue(true));
         $this->categoryUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix')
             ->will($this->returnValue($targetPath));
 
-        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, OptionProvider::PERMANENT);
+        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, OptionProvider::PERMANENT, 0);
 
         $this->assertEquals(
             ['autogenerated.html_2' => $this->urlRewrite],
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category)
+            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category, $this->category)
         );
     }
 
     public function testSkipGenerationForCustom()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will(
                 $this->returnValue(
                     $this->getCurrentRewritesMocks(
@@ -155,7 +165,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             [],
-            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category)
+            $this->currentUrlRewritesRegenerator->generate('store_id', $this->category, $this->category)
         );
     }
 
@@ -166,7 +176,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $requestPath = 'generate-for-custom-without-redirect-type.html';
         $targetPath = 'custom-target-path.html';
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will(
                 $this->returnValue(
                     $this->getCurrentRewritesMocks(
@@ -184,16 +194,14 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
                 )
             );
         $this->categoryUrlPathGenerator->expects($this->never())->method('getUrlPathWithSuffix');
-        $this->category->expects($this->any())->method('getId')->will($this->returnValue($categoryId));
+        $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue($categoryId));
         $this->urlRewrite->expects($this->once())->method('setDescription')->with($description)
             ->will($this->returnSelf());
-        $this->urlRewriteFactory->expects($this->once())->method('create')
-            ->willReturn($this->urlRewrite);
-        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 0);
+        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 0, 0);
 
         $this->assertEquals(
             ['generate-for-custom-without-redirect-type.html_12' => $this->urlRewrite],
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category)
+            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category, $this->category)
         );
     }
 
@@ -204,7 +212,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $requestPath = 'generate-for-custom-without-redirect-type.html';
         $targetPath = 'generated-target-path.html';
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will(
                 $this->returnValue(
                     $this->getCurrentRewritesMocks(
@@ -223,16 +231,14 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             );
         $this->categoryUrlPathGenerator->expects($this->any())->method('getUrlPathWithSuffix')
             ->will($this->returnValue($targetPath));
-        $this->category->expects($this->any())->method('getId')->will($this->returnValue($categoryId));
+        $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue($categoryId));
         $this->urlRewrite->expects($this->once())->method('setDescription')->with($description)
             ->will($this->returnSelf());
-        $this->urlRewriteFactory->expects($this->once())->method('create')
-            ->willReturn($this->urlRewrite);
-        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 'code');
+        $this->prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, 'code', 0);
 
         $this->assertEquals(
             ['generate-for-custom-without-redirect-type.html_12' => $this->urlRewrite],
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category)
+            $this->currentUrlRewritesRegenerator->generate($storeId, $this->category, $this->category)
         );
     }
 
@@ -263,9 +269,16 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
      * @param mixed $requestPath
      * @param mixed $targetPath
      * @param mixed $redirectType
+     * @param int $isAutoGenerated
      */
-    protected function prepareUrlRewriteMock($storeId, $categoryId, $requestPath, $targetPath, $redirectType)
-    {
+    protected function prepareUrlRewriteMock(
+        $storeId,
+        $categoryId,
+        $requestPath,
+        $targetPath,
+        $redirectType,
+        $isAutoGenerated
+    ) {
         $this->urlRewrite->expects($this->any())->method('setStoreId')->with($storeId)
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setEntityId')->with($categoryId)
@@ -276,11 +289,14 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setTargetPath')->with($targetPath)
             ->will($this->returnSelf());
-        $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with(0)
+        $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with($isAutoGenerated)
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setRedirectType')->with($redirectType)
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setMetadata')->with([])->will($this->returnSelf());
+        $this->urlRewrite->expects($this->any())->method('getTargetPath')->willReturn($targetPath);
+        $this->urlRewrite->expects($this->any())->method('getRequestPath')->willReturn($requestPath);
+        $this->urlRewrite->expects($this->any())->method('getStoreId')->willReturn($storeId);
         $this->urlRewriteFactory->expects($this->any())->method('create')->will($this->returnValue($this->urlRewrite));
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php
index aad5e9ffec89811e2f21fca5716a9d9f0e2f0c40..927c451e6522865454220c4401ddbcc21a3f4b8f 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/Plugin/StorageTest.php
@@ -6,7 +6,6 @@
 namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Category\Plugin;
 
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-use Magento\CatalogUrlRewrite\Model\Category\ProductFactory;
 use Magento\UrlRewrite\Model\StorageInterface;
 use Magento\CatalogUrlRewrite\Model\Category\Plugin\Storage as CategoryStoragePlugin;
 use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
@@ -24,11 +23,6 @@ class StorageTest extends \PHPUnit_Framework_TestCase
      */
     private $plugin;
 
-    /**
-     * @var ProductFactory|\PHPUnit_Framework_MockObject_MockObject
-     */
-    private $productFactory;
-
     /**
      * @var UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject
      */
@@ -56,10 +50,6 @@ class StorageTest extends \PHPUnit_Framework_TestCase
 
     protected function setUp()
     {
-        $this->productFactory = $this->getMockBuilder(ProductFactory::class)
-            ->disableOriginalConstructor()
-            ->setMethods(['create'])
-            ->getMock();
         $this->storage = $this->getMockBuilder(StorageInterface::class)
             ->getMockForAbstractClass();
         $this->urlFinder = $this->getMockBuilder(UrlFinderInterface::class)
@@ -78,12 +68,15 @@ class StorageTest extends \PHPUnit_Framework_TestCase
         $this->plugin = (new ObjectManager($this))->getObject(
             CategoryStoragePlugin::class,
             [
-                'productFactory' => $this->productFactory,
-                'urlFinder' => $this->urlFinder
+                'urlFinder' => $this->urlFinder,
+                'productResource' => $this->productResourceModel
             ]
         );
     }
 
+    /**
+     * test AfterReplace method
+     */
     public function testAfterReplace()
     {
         $this->urlRewrite->expects(static::any())->method('getMetadata')->willReturn(['category_id' => '5']);
@@ -97,10 +90,20 @@ class StorageTest extends \PHPUnit_Framework_TestCase
 
         $this->urlFinder->expects(static::once())->method('findAllByData')->willReturn([$this->urlRewrite]);
 
-        $this->productFactory->expects(static::once())->method('create')->willReturn($this->product);
-        $this->product->expects(static::once())->method('getResource')->willReturn($this->productResourceModel);
         $this->productResourceModel->expects(static::once())->method('saveMultiple')->willReturnSelf();
 
         $this->plugin->afterReplace($this->storage, null, $productUrls);
     }
+
+    /**
+     * test BeforeDeleteByData method
+     */
+    public function testBeforeDeleteByData()
+    {
+        $data = [1, 2, 3];
+        $this->productResourceModel->expects(static::once())
+            ->method('removeMultipleByProductCategory')
+            ->with($data)->willReturnSelf();
+        $this->plugin->beforeDeleteByData($this->storage, $data);
+    }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php
index 035112a592916060e637ad284ea4845ebbfb5d66..32f310c33b8cbcf27cda92c279e14f39e39394e2 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryBasedProductRewriteGeneratorTest.php
@@ -12,12 +12,11 @@ use Magento\CatalogUrlRewrite\Model\ProductScopeRewriteGenerator;
 
 /**
  * Class CategoryBasedProductRewriteGeneratorTest
- * @package Magento\CatalogUrlRewrite\Test\Unit\Model
  */
 class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @var \PHPUnit_Framework_MockObject_MockObject
+     * @var ProductScopeRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject
      */
     private $productScopeRewriteGeneratorMock;
 
@@ -46,6 +45,7 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa
             ->disableOriginalConstructor()
             ->getMock();
         $storeId = 1;
+        $categoryId = 1;
         $urls = ['dummy-url.html'];
 
         $productMock->expects($this->once())
@@ -60,10 +60,10 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa
             ->willReturn(true);
         $this->productScopeRewriteGeneratorMock->expects($this->once())
             ->method('generateForGlobalScope')
-            ->with([$categoryMock], $productMock)
+            ->with([$categoryMock], $productMock, $categoryId)
             ->willReturn($urls);
 
-        $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock));
+        $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock, $categoryId));
     }
 
     public function testGenerationWithSpecificStore()
@@ -75,6 +75,7 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa
             ->disableOriginalConstructor()
             ->getMock();
         $storeId = 1;
+        $categoryId = 1;
         $urls = ['dummy-url.html'];
 
         $productMock->expects($this->once())
@@ -89,9 +90,9 @@ class CategoryBasedProductRewriteGeneratorTest extends \PHPUnit_Framework_TestCa
             ->willReturn(false);
         $this->productScopeRewriteGeneratorMock->expects($this->once())
             ->method('generateForSpecificStoreView')
-            ->with($storeId, [$categoryMock], $productMock)
+            ->with($storeId, [$categoryMock], $productMock, $categoryId)
             ->willReturn($urls);
 
-        $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock));
+        $this->assertEquals($urls, $this->generator->generate($productMock, $categoryMock, $categoryId));
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php
index d5abe69ff79777cf02067899eff0d434c76620a9..f36799f789fbdb3a9c287f4e14e1c3abab5b3c36 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlPathGeneratorTest.php
@@ -5,8 +5,7 @@
  */
 namespace Magento\CatalogUrlRewrite\Test\Unit\Model;
 
-use \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
-
+use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
 use Magento\Catalog\Model\Category;
 use Magento\Store\Model\ScopeInterface;
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php
index c2905eb9b425f243126547bda3791bf570ee6977..c3a2ebb47b156fe09ffdd12856f85473c1c733f7 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/CategoryUrlRewriteGeneratorTest.php
@@ -14,46 +14,58 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
 class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
 {
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $canonicalUrlRewriteGenerator;
+    private $canonicalUrlRewriteGenerator;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $currentUrlRewritesRegenerator;
+    private $currentUrlRewritesRegenerator;
 
     /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $childrenUrlRewriteGenerator;
+    private $childrenUrlRewriteGenerator;
 
     /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator */
-    protected $categoryUrlRewriteGenerator;
+    private $categoryUrlRewriteGenerator;
 
     /** @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService|\PHPUnit_Framework_MockObject_MockObject */
-    protected $storeViewService;
+    private $storeViewService;
 
     /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */
-    protected $category;
+    private $category;
 
     /** @var \Magento\Catalog\Api\CategoryRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */
-    protected $categoryRepository;
+    private $categoryRepository;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
 
     /**
      * Test method
      */
     protected function setUp()
     {
-        $this->currentUrlRewritesRegenerator = $this->getMockBuilder(
+        $this->currentUrlRewritesRegenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\Category\CurrentUrlRewritesRegenerator::class
         )->disableOriginalConstructor()->getMock();
-        $this->canonicalUrlRewriteGenerator = $this->getMockBuilder(
+        $this->canonicalUrlRewriteGenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\Category\CanonicalUrlRewriteGenerator::class
         )->disableOriginalConstructor()->getMock();
-        $this->childrenUrlRewriteGenerator = $this->getMockBuilder(
+        $this->childrenUrlRewriteGenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\Category\ChildrenUrlRewriteGenerator::class
         )->disableOriginalConstructor()->getMock();
         $this->storeViewService = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Service\V1\StoreViewService::class)
             ->disableOriginalConstructor()->getMock();
         $this->category = $this->getMock(\Magento\Catalog\Model\Category::class, [], [], '', false);
         $this->categoryRepository = $this->getMock(\Magento\Catalog\Api\CategoryRepositoryInterface::class);
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
 
-        $this->categoryUrlRewriteGenerator = (new ObjectManager($this))->getObject(
+        $this->categoryUrlRewriteGenerator = (new ObjectManager($this))->getObject(
             \Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator::class,
             [
                 'canonicalUrlRewriteGenerator' => $this->canonicalUrlRewriteGenerator,
@@ -61,6 +73,7 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
                 'currentUrlRewritesRegenerator' => $this->currentUrlRewritesRegenerator,
                 'storeViewService' => $this->storeViewService,
                 'categoryRepository' => $this->categoryRepository,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory
             ]
         );
     }
@@ -70,26 +83,32 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
      */
     public function testGenerationForGlobalScope()
     {
+        $categoryId = 1;
         $this->category->expects($this->any())->method('getStoreId')->will($this->returnValue(null));
         $this->category->expects($this->any())->method('getStoreIds')->will($this->returnValue([1]));
         $this->storeViewService->expects($this->once())->method('doesEntityHaveOverriddenUrlKeyForStore')
             ->will($this->returnValue(false));
         $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $canonical->setTargetPath('category-1')
+        $canonical->setRequestPath('category-1')
             ->setStoreId(1);
         $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate')
-            ->will($this->returnValue([$canonical]));
-        $children = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $children->setTargetPath('category-2')
+            ->will($this->returnValue(['category-1' => $canonical]));
+        $children1 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
+        $children1->setRequestPath('category-2')
+            ->setStoreId(2);
+        $children2 = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
+        $children2->setRequestPath('category-22')
             ->setStoreId(2);
         $this->childrenUrlRewriteGenerator->expects($this->any())->method('generate')
-            ->will($this->returnValue([$children]));
+            ->with(1, $this->category, $categoryId)
+            ->will($this->returnValue(['category-2' => $children1, 'category-1' => $children2]));
         $current = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $current->setTargetPath('category-3')
+        $current->setRequestPath('category-3')
             ->setStoreId(3);
         $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate')
-            ->will($this->returnValue([$current]));
-        $categoryForSpecificStore = $this->getMock(
+            ->with(1, $this->category, $categoryId)
+            ->will($this->returnValue(['category-3' => $current]));
+        $categoryForSpecificStore = $this->getMock(
             \Magento\Catalog\Model\Category::class,
             ['getUrlKey', 'getUrlPath'],
             [],
@@ -99,8 +118,13 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->categoryRepository->expects($this->once())->method('get')->willReturn($categoryForSpecificStore);
 
         $this->assertEquals(
-            [$canonical, $children, $current],
-            $this->categoryUrlRewriteGenerator->generate($this->category)
+            [
+                'category-1_1' => $canonical,
+                'category-2_2' => $children1,
+                'category-22_2' => $children2,
+                'category-3_3' => $current
+            ],
+            $this->categoryUrlRewriteGenerator->generate($this->category, false, $categoryId)
         );
     }
 
@@ -112,7 +136,7 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->category->expects($this->any())->method('getStoreId')->will($this->returnValue(1));
         $this->category->expects($this->never())->method('getStoreIds');
         $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $canonical->setTargetPath('category-1')
+        $canonical->setRequestPath('category-1')
             ->setStoreId(1);
         $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$canonical]));
@@ -121,7 +145,10 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([]));
 
-        $this->assertEquals([$canonical], $this->categoryUrlRewriteGenerator->generate($this->category));
+        $this->assertEquals(
+            ['category-1_1' => $canonical],
+            $this->categoryUrlRewriteGenerator->generate($this->category, 1)
+        );
     }
 
     /**
@@ -135,4 +162,18 @@ class CategoryUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals([], $this->categoryUrlRewriteGenerator->generate($this->category));
     }
+
+    /**
+     * Test method
+     */
+    public function testSkipGenerationForGlobalScopeWithCategory()
+    {
+        $this->category->expects($this->any())->method('getStoreIds')->will($this->returnValue([1, 2]));
+        $this->category->expects($this->any())->method('getEntityId')->will($this->returnValue(1));
+        $this->category->expects($this->any())->method('getStoreId')->will($this->returnValue(false));
+        $this->storeViewService->expects($this->exactly(2))->method('doesEntityHaveOverriddenUrlKeyForStore')
+            ->will($this->returnValue(true));
+
+        $this->assertEquals([], $this->categoryUrlRewriteGenerator->generate($this->category, false, 1));
+    }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryHashMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryHashMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..52d65710fed69a0902f28aacb25a5183fa28e833
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryHashMapTest.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Catalog\Model\ResourceModel\CategoryFactory;
+use Magento\Catalog\Model\ResourceModel\Category;
+use Magento\Framework\DB\Select;
+use Magento\Catalog\Model\CategoryRepository;
+use Magento\Catalog\Api\Data\CategoryInterface;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+
+/**
+ * Class DataCategoryHashMapTest
+ */
+class DataCategoryHashMapTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var CategoryRepository|\PHPUnit_Framework_MockObject_MockObject */
+    private $categoryRepository;
+
+    /** @var CategoryResourceFactory|\PHPUnit_Framework_MockObject_MockObject */
+    private $categoryResourceFactory;
+
+    /** @var Category|\PHPUnit_Framework_MockObject_MockObject */
+    private $categoryResource;
+
+    /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->categoryRepository = $this->getMock(CategoryRepository::class, [], [], '', false);
+        $this->categoryResourceFactory = $this->getMock(CategoryFactory::class, ['create'], [], '', false);
+        $this->categoryResource = $this->getMock(
+            Category::class,
+            ['getConnection', 'getEntityTable'],
+            [],
+            '',
+            false
+        );
+
+        $this->categoryResourceFactory->expects($this->any())
+            ->method('create')
+            ->willReturn($this->categoryResource);
+
+        $this->model = (new ObjectManager($this))->getObject(
+            DataCategoryHashMap::class,
+            [
+                'categoryRepository' => $this->categoryRepository,
+                'categoryResourceFactory' => $this->categoryResourceFactory
+            ]
+        );
+    }
+
+    /**
+     * Tests getAllData, getData and resetData functionality
+     */
+    public function testGetAllData()
+    {
+        $categoryIds = ['1' => [1, 2, 3], '2' => [2, 3], '3' => 3];
+        $categoryIdsOther = ['2' => [2, 3, 4]];
+
+        $categoryMock = $this->getMock(CategoryInterface::class, [], [], '', false);
+        $connectionAdapterMock = $this->getMock(AdapterInterface::class);
+        $selectMock = $this->getMock(Select::class, [], [], '', false);
+
+        $this->categoryRepository->expects($this->any())
+            ->method('get')
+            ->willReturn($categoryMock);
+        $categoryMock->expects($this->any())
+            ->method('getResource')
+            ->willReturn($this->categoryResource);
+        $this->categoryResource->expects($this->any())
+            ->method('getConnection')
+            ->willReturn($connectionAdapterMock);
+        $this->categoryResource->expects($this->any())
+            ->method('getEntityTable')
+            ->willReturn('category_entity');
+        $connectionAdapterMock->expects($this->any())
+            ->method('select')
+            ->willReturn($selectMock);
+        $selectMock->expects($this->any())
+            ->method('from')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('where')
+            ->willReturnSelf();
+        $connectionAdapterMock->expects($this->any())
+            ->method('fetchCol')
+            ->willReturnOnConsecutiveCalls($categoryIds, $categoryIdsOther, $categoryIds);
+
+        $this->assertEquals($categoryIds, $this->model->getAllData(1));
+        $this->assertEquals($categoryIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($categoryIdsOther, $this->model->getAllData(2));
+        $this->assertEquals($categoryIdsOther[2], $this->model->getData(2, 2));
+        $this->model->resetData(1);
+        $this->assertEquals($categoryIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($categoryIds, $this->model->getAllData(1));
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUrlRewriteDatabaseMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUrlRewriteDatabaseMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7891c56be1368b7917bfb01e33a8a84ef79eba6b
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUrlRewriteDatabaseMapTest.php
@@ -0,0 +1,125 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Framework\DB\Select;
+use Magento\CatalogUrlRewrite\Model\Map\HashMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUsedInProductsHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\TemporaryTableService;
+
+/**
+ * Class DataCategoryUrlRewriteDatabaseMapTest
+ */
+class DataCategoryUrlRewriteDatabaseMapTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $hashMapPoolMock;
+
+    /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataCategoryMapMock;
+
+    /** @var DataCategoryUsedInProductsHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataCategoryUsedInProductsMapMock;
+
+    /** @var TemporaryTableService|\PHPUnit_Framework_MockObject_MockObject */
+    private $temporaryTableServiceMock;
+
+    /** @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject */
+    private $connectionMock;
+
+    /** @var DataCategoryUrlRewriteDatabaseMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false);
+        $this->dataCategoryMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false);
+        $this->dataCategoryUsedInProductsMapMock = $this->getMock(
+            DataCategoryUsedInProductsHashMap::class,
+            [],
+            [],
+            '',
+            false
+        );
+        $this->temporaryTableServiceMock = $this->getMock(TemporaryTableService::class, [], [], '', false);
+        $this->connectionMock = $this->getMock(ResourceConnection::class, [], [], '', false);
+
+        $this->hashMapPoolMock->expects($this->any())
+            ->method('getDataMap')
+            ->willReturnOnConsecutiveCalls($this->dataCategoryUsedInProductsMapMock, $this->dataCategoryMapMock);
+
+        $this->model = (new ObjectManager($this))->getObject(
+            DataCategoryUrlRewriteDatabaseMap::class,
+            [
+                'connection' => $this->connectionMock,
+                'hashMapPool' => $this->hashMapPoolMock,
+                'temporaryTableService' => $this->temporaryTableServiceMock
+            ]
+        );
+    }
+
+    /**
+     * Tests getAllData, getData and resetData functionality
+     */
+    public function testGetAllData()
+    {
+        $productStoreIds = [
+            '1' => ['store_id' => 1, 'category_id' => 1],
+            '2' => ['store_id' => 2, 'category_id' => 1],
+            '3' => ['store_id' => 3, 'category_id' => 1],
+            '4' => ['store_id' => 1, 'category_id' => 2],
+            '5' => ['store_id' => 2, 'category_id' => 2],
+        ];
+
+        $connectionMock = $this->getMock(AdapterInterface::class);
+        $selectMock = $this->getMock(Select::class, [], [], '', false);
+
+        $this->connectionMock->expects($this->any())
+            ->method('getConnection')
+            ->willReturn($connectionMock);
+        $connectionMock->expects($this->any())
+            ->method('select')
+            ->willReturn($selectMock);
+        $connectionMock->expects($this->any())
+            ->method('fetchAll')
+            ->willReturn($productStoreIds[3]);
+        $selectMock->expects($this->any())
+            ->method('from')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('joinInner')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('where')
+            ->willReturnSelf();
+        $this->dataCategoryMapMock->expects($this->once())
+            ->method('getAllData')
+            ->willReturn([]);
+        $this->dataCategoryUsedInProductsMapMock->expects($this->once())
+            ->method('getAllData')
+            ->willReturn([]);
+        $this->temporaryTableServiceMock->expects($this->any())
+            ->method('createFromSelect')
+            ->withConsecutive(
+                $selectMock,
+                $connectionMock,
+                [
+                    'PRIMARY' => ['url_rewrite_id'],
+                    'HASHKEY_ENTITY_STORE' => ['hash_key'],
+                    'ENTITY_STORE' => ['entity_id', 'store_id']
+                ]
+            )
+            ->willReturn('tempTableName');
+
+        $this->assertEquals($productStoreIds[3], $this->model->getData(1, '3_1'));
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUsedInProductsHashMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUsedInProductsHashMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..74353d3a924f7687eb238399d38380bffcab36c5
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataCategoryUsedInProductsHashMapTest.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Framework\DB\Select;
+use Magento\CatalogUrlRewrite\Model\Map\HashMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUsedInProductsHashMap;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\App\ResourceConnection;
+
+/**
+ * Class DataCategoryUsedInProductsHashMapTest
+ */
+class DataCategoryUsedInProductsHashMapTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $hashMapPoolMock;
+
+    /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataCategoryMapMock;
+
+    /** @var DataProductHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataProductMapMock;
+
+    /** @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject */
+    private $connectionMock;
+
+    /** @var DataCategoryUsedInProductsHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false);
+        $this->dataCategoryMapMock = $this->getMock(DataCategoryHashMap::class, [], [], '', false);
+        $this->dataProductMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false);
+        $this->connectionMock = $this->getMock(ResourceConnection::class, [], [], '', false);
+
+        $this->hashMapPoolMock->expects($this->any())
+            ->method('getDataMap')
+            ->willReturnOnConsecutiveCalls(
+                $this->dataProductMapMock,
+                $this->dataCategoryMapMock,
+                $this->dataProductMapMock,
+                $this->dataCategoryMapMock,
+                $this->dataProductMapMock,
+                $this->dataCategoryMapMock
+            );
+
+        $this->model = (new ObjectManager($this))->getObject(
+            DataCategoryUsedInProductsHashMap::class,
+            [
+                'connection' => $this->connectionMock,
+                'hashMapPool' => $this->hashMapPoolMock
+            ]
+        );
+    }
+
+    /**
+     * Tests getAllData, getData and resetData functionality
+     */
+    public function testGetAllData()
+    {
+        $categoryIds = ['1' => [1, 2, 3], '2' => [2, 3], '3' => 3];
+        $categoryIdsOther = ['2' => [2, 3, 4]];
+
+        $connectionMock = $this->getMock(AdapterInterface::class);
+        $selectMock = $this->getMock(Select::class, [], [], '', false);
+
+        $this->connectionMock->expects($this->any())
+            ->method('getConnection')
+            ->willReturn($connectionMock);
+        $connectionMock->expects($this->any())
+            ->method('select')
+            ->willReturn($selectMock);
+        $connectionMock->expects($this->any())
+            ->method('fetchCol')
+            ->willReturnOnConsecutiveCalls($categoryIds, $categoryIdsOther, $categoryIds);
+        $selectMock->expects($this->any())
+            ->method('from')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('joinInner')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('where')
+            ->willReturnSelf();
+        $this->hashMapPoolMock->expects($this->at(4))
+            ->method('resetMap')
+            ->with(DataProductHashMap::class, 1);
+        $this->hashMapPoolMock->expects($this->at(5))
+            ->method('resetMap')
+            ->with(DataCategoryHashMap::class, 1);
+
+        $this->assertEquals($categoryIds, $this->model->getAllData(1));
+        $this->assertEquals($categoryIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($categoryIdsOther, $this->model->getAllData(2));
+        $this->assertEquals($categoryIdsOther[2], $this->model->getData(2, 2));
+        $this->model->resetData(1);
+        $this->assertEquals($categoryIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($categoryIds, $this->model->getAllData(1));
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductHashMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductHashMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..53e4d225977698c0a3b3bd4afe7809a58f3623bd
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductHashMapTest.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Catalog\Model\ResourceModel\Category\Collection;
+use Magento\Catalog\Model\ResourceModel\Category as CategoryResource;
+use Magento\Framework\DB\Select;
+use Magento\Catalog\Model\ProductRepository;
+use Magento\CatalogUrlRewrite\Model\Map\HashMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
+use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
+
+/**
+ * Class DataProductHashMapTest
+ */
+class DataProductHashMapTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $hashMapPoolMock;
+
+    /** @var DataCategoryHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataCategoryMapMock;
+
+    /**
+     * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $collectionFactoryMock;
+
+    /**
+     * @var ProductCollection|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $productCollectionMock;
+
+    /** @var DataProductHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false);
+        $this->dataCategoryMapMock = $this->getMock(DataCategoryHashMap::class, [], [], '', false);
+        $this->collectionFactoryMock = $this->getMock(CollectionFactory::class, ['create'], [], '', false);
+        $this->productCollectionMock = $this->getMock(
+            ProductCollection::class,
+            ['getSelect', 'getConnection', 'getAllIds'],
+            [],
+            '',
+            false
+        );
+
+        $this->collectionFactoryMock->expects($this->any())
+            ->method('create')
+            ->willReturn($this->productCollectionMock);
+
+        $this->hashMapPoolMock->expects($this->any())
+            ->method('getDataMap')
+            ->willReturn($this->dataCategoryMapMock);
+
+        $this->model = (new ObjectManager($this))->getObject(
+            DataProductHashMap::class,
+            [
+                'collectionFactory' => $this->collectionFactoryMock,
+                'hashMapPool' => $this->hashMapPoolMock
+            ]
+        );
+    }
+
+    /**
+     * Tests getAllData, getData and resetData functionality
+     */
+    public function testGetAllData()
+    {
+        $productIds = ['1' => [1, 2, 3], '2' => [2, 3], '3' => 3];
+        $productIdsOther = ['2' => [2, 3, 4]];
+
+        $connectionMock = $this->getMock(AdapterInterface::class);
+        $selectMock = $this->getMock(Select::class, [], [], '', false);
+
+        $this->productCollectionMock->expects($this->exactly(3))
+            ->method('getAllIds')
+            ->willReturnOnConsecutiveCalls($productIds, $productIdsOther, $productIds);
+        $this->productCollectionMock->expects($this->any())
+            ->method('getConnection')
+            ->willReturn($connectionMock);
+        $connectionMock->expects($this->any())
+            ->method('getTableName')
+            ->willReturn($this->returnValue($this->returnArgument(0)));
+        $this->productCollectionMock->expects($this->any())
+            ->method('getSelect')
+            ->willReturn($selectMock);
+        $selectMock->expects($this->any())
+            ->method('from')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('joinInner')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('where')
+            ->willReturnSelf();
+        $this->dataCategoryMapMock->expects($this->any())
+            ->method('getAllData')
+            ->willReturn([]);
+        $this->hashMapPoolMock->expects($this->any())
+            ->method('resetMap')
+            ->with(DataCategoryHashMap::class, 1);
+        $this->assertEquals($productIds, $this->model->getAllData(1));
+        $this->assertEquals($productIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($productIdsOther, $this->model->getAllData(2));
+        $this->assertEquals($productIdsOther[2], $this->model->getData(2, 2));
+        $this->model->resetData(1);
+        $this->assertEquals($productIds[2], $this->model->getData(1, 2));
+        $this->assertEquals($productIds, $this->model->getAllData(1));
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductUrlRewriteDatabaseMapTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductUrlRewriteDatabaseMapTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d283c6d3f0410f9df87036be147f47c17ca7f212
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/DataProductUrlRewriteDatabaseMapTest.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Framework\DB\Select;
+use Magento\CatalogUrlRewrite\Model\Map\HashMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\TemporaryTableService;
+
+/**
+ * Class DataProductUrlRewriteDatabaseMapTest
+ */
+class DataProductUrlRewriteDatabaseMapTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $hashMapPoolMock;
+
+    /** @var DataProductHashMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $dataProductMapMock;
+
+    /** @var TemporaryTableService|\PHPUnit_Framework_MockObject_MockObject */
+    private $temporaryTableServiceMock;
+
+    /** @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject */
+    private $connectionMock;
+
+    /** @var DataProductUrlRewriteDatabaseMap|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->hashMapPoolMock = $this->getMock(HashMapPool::class, [], [], '', false);
+        $this->dataProductMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false);
+        $this->temporaryTableServiceMock = $this->getMock(TemporaryTableService::class, [], [], '', false);
+        $this->connectionMock = $this->getMock(ResourceConnection::class, [], [], '', false);
+
+        $this->hashMapPoolMock->expects($this->any())
+            ->method('getDataMap')
+            ->willReturn($this->dataProductMapMock);
+
+        $this->model = (new ObjectManager($this))->getObject(
+            DataProductUrlRewriteDatabaseMap::class,
+            [
+                'connection' => $this->connectionMock,
+                'hashMapPool' => $this->hashMapPoolMock,
+                'temporaryTableService' => $this->temporaryTableServiceMock
+            ]
+        );
+    }
+
+    /**
+     * Tests getAllData, getData and resetData functionality
+     */
+    public function testGetAllData()
+    {
+        $productStoreIds = [
+            '1' => ['store_id' => 1, 'product_id' => 1],
+            '2' => ['store_id' => 2, 'product_id' => 1],
+            '3' => ['store_id' => 3, 'product_id' => 1],
+            '4' => ['store_id' => 1, 'product_id' => 2],
+            '5' => ['store_id' => 2, 'product_id' => 2],
+        ];
+
+        $connectionMock = $this->getMock(AdapterInterface::class);
+        $selectMock = $this->getMock(Select::class, [], [], '', false);
+
+        $this->connectionMock->expects($this->any())
+            ->method('getConnection')
+            ->willReturn($connectionMock);
+        $connectionMock->expects($this->any())
+            ->method('select')
+            ->willReturn($selectMock);
+        $connectionMock->expects($this->any())
+            ->method('fetchAll')
+            ->willReturn($productStoreIds[3]);
+        $selectMock->expects($this->any())
+            ->method('from')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('joinInner')
+            ->willReturnSelf();
+        $selectMock->expects($this->any())
+            ->method('where')
+            ->willReturnSelf();
+
+        $this->dataProductMapMock->expects($this->any())
+            ->method('getAllData')
+            ->willReturn([]);
+
+        $this->temporaryTableServiceMock->expects($this->any())
+            ->method('createFromSelect')
+            ->withConsecutive(
+                $selectMock,
+                $connectionMock,
+                [
+                    'PRIMARY' => ['url_rewrite_id'],
+                    'HASHKEY_ENTITY_STORE' => ['hash_key'],
+                    'ENTITY_STORE' => ['entity_id', 'store_id']
+                ]
+            )
+            ->willReturn('tempTableName');
+
+        $this->assertEquals($productStoreIds[3], $this->model->getData(1, '3_1'));
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/HashMapPoolTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/HashMapPoolTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b9efdb01f2e1dba3d8b0cc3385181fa382baa52e
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/HashMapPoolTest.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\CatalogUrlRewrite\Model\Map\HashMapPool;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductHashMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUsedInProductsHashMap;
+use Magento\Framework\ObjectManagerInterface;
+
+/**
+ * Class HashMapPoolTest
+ */
+class HashMapPoolTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject */
+    private $objectManagerMock;
+
+    /** @var HashMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->objectManagerMock = $this->getMock(ObjectManagerInterface::class);
+
+        $this->model = (new ObjectManager($this))->getObject(
+            HashMapPool::class,
+            [
+                'objectManager' => $this->objectManagerMock,
+            ]
+        );
+    }
+
+    /**
+     * Tests getDataMap
+     */
+    public function testGetDataMap()
+    {
+        $dataCategoryMapMock = $this->getMock(DataCategoryHashMap::class, [], [], '', false);
+        $dataProductMapMock = $this->getMock(DataProductHashMap::class, [], [], '', false);
+        $dataProductMapMockOtherCategory = $this->getMock(DataCategoryUsedInProductsHashMap::class, [], [], '', false);
+
+        $this->objectManagerMock->expects($this->any())
+            ->method('create')
+            ->willReturnMap(
+                [
+                    [
+                        DataCategoryHashMap::class,
+                        ['category' => 1],
+                        $dataCategoryMapMock
+                    ],
+                    [
+                        DataProductHashMap::class,
+                        ['category' => 1],
+                        $dataProductMapMock
+                    ],
+                    [
+                        DataCategoryUsedInProductsHashMap::class,
+                        ['category' => 2],
+                        $dataProductMapMockOtherCategory
+                    ]
+               ]
+            );
+        $this->assertSame($dataCategoryMapMock, $this->model->getDataMap(DataCategoryHashMap::class, 1));
+        $this->assertSame($dataProductMapMock, $this->model->getDataMap(DataProductHashMap::class, 1));
+        $this->assertSame(
+            $dataProductMapMockOtherCategory,
+            $this->model->getDataMap(DataCategoryUsedInProductsHashMap::class, 2)
+        );
+    }
+
+    /**
+     * Tests getDataMap with exception
+     */
+    public function testGetDataMapException()
+    {
+        $nonInterface = $this->getMock(HashMapPool::class, [], [], '', false);
+
+        $this->objectManagerMock->expects($this->any())
+            ->method('create')
+            ->willReturn($nonInterface);
+        $this->setExpectedException(\InvalidArgumentException::class);
+        $this->model->getDataMap(HashMapPool::class, 1);
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/UrlRewriteFinderTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/UrlRewriteFinderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b0c4500dab3e02f4d35aed41eb57b54a3e54cfd4
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Map/UrlRewriteFinderTest.php
@@ -0,0 +1,161 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Map;
+
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder;
+use Magento\CatalogUrlRewrite\Model\Map\DatabaseMapPool;
+use Magento\UrlRewrite\Model\UrlFinderInterface;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap;
+use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap;
+
+/**
+ * Class UrlRewriteFinderTest
+ */
+class UrlRewriteFinderTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var DatabaseMapPool|\PHPUnit_Framework_MockObject_MockObject */
+    private $databaseMapPoolMock;
+
+    /** @var UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */
+    private $urlRewriteFactoryMock;
+
+    /** @var UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */
+    private $urlRewritePrototypeMock;
+
+    /** @var UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */
+    private $urlFinderMock;
+
+    /** @var UrlRewriteFinder|\PHPUnit_Framework_MockObject_MockObject */
+    private $model;
+
+    protected function setUp()
+    {
+        $this->databaseMapPoolMock = $this->getMock(DatabaseMapPool::class, [], [], '', false);
+        $this->urlFinderMock = $this->getMock(UrlFinderInterface::class);
+        $this->urlRewriteFactoryMock = $this->getMock(UrlRewriteFactory::class, ['create'], [], '', false);
+        $this->urlRewritePrototypeMock = new UrlRewrite();
+
+        $this->urlRewriteFactoryMock->expects($this->any())
+            ->method('create')
+            ->willReturn($this->urlRewritePrototypeMock);
+
+        $urlRewriteClassesNamesArray = [
+            UrlRewriteFinder::ENTITY_TYPE_PRODUCT => DataProductUrlRewriteDatabaseMap::class,
+            UrlRewriteFinder::ENTITY_TYPE_CATEGORY => DataCategoryUrlRewriteDatabaseMap::class
+        ];
+
+        $this->model = (new ObjectManager($this))->getObject(
+            UrlRewriteFinder::class,
+            [
+                'databaseMapPool' => $this->databaseMapPoolMock,
+                'urlFinder' => $this->urlFinderMock,
+                'urlRewriteFactory' => $this->urlRewriteFactoryMock,
+                'urlRewriteClassNames' => $urlRewriteClassesNamesArray
+            ]
+        );
+    }
+
+    /**
+     * test findAllByData using urlFinder
+     */
+    public function testGetByIdentifiersFallback()
+    {
+        $expected = [1, 2, 3];
+        $this->databaseMapPoolMock->expects($this->never())
+            ->method('getDataMap');
+
+        $this->urlFinderMock->expects($this->exactly(7))
+            ->method('findAllByData')
+            ->willReturn($expected);
+
+        $this->assertEquals($expected, $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_CATEGORY));
+        $this->assertEquals($expected, $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_PRODUCT));
+        $this->assertEquals($expected, $this->model->findAllByData('a', 1, UrlRewriteFinder::ENTITY_TYPE_PRODUCT), 1);
+        $this->assertEquals($expected, $this->model->findAllByData('a', 'a', UrlRewriteFinder::ENTITY_TYPE_PRODUCT), 1);
+        $this->assertEquals($expected, $this->model->findAllByData(1, 'a', UrlRewriteFinder::ENTITY_TYPE_PRODUCT), 1);
+        $this->assertEquals($expected, $this->model->findAllByData(1, 1, 'cms', 1));
+        $this->assertEquals($expected, $this->model->findAllByData(1, 1, 'cms'));
+    }
+
+    /**
+     * test findAllByData Product URL rewrites
+     */
+    public function testGetByIdentifiersProduct()
+    {
+        $data =[
+            [
+                'url_rewrite_id' => '1',
+                'entity_type' => 'product',
+                'entity_id' => '3',
+                'request_path' => 'request_path',
+                'target_path' => 'target_path',
+                'redirect_type' => 'redirect_type',
+                'store_id' => '4',
+                'description' => 'description',
+                'is_autogenerated' => '1',
+                'metadata' => '{}'
+            ]
+        ];
+
+        $dataProductMapMock = $this->getMock(DataProductUrlRewriteDatabaseMap::class, [], [], '', false);
+        $this->databaseMapPoolMock->expects($this->once())
+            ->method('getDataMap')
+            ->with(DataProductUrlRewriteDatabaseMap::class, 1)
+            ->willReturn($dataProductMapMock);
+
+        $this->urlFinderMock->expects($this->never())
+            ->method('findAllByData')
+            ->willReturn([]);
+
+        $dataProductMapMock->expects($this->once())
+            ->method('getData')
+            ->willReturn($data);
+
+        $urlRewriteResultArray = $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_PRODUCT, 1);
+        $this->assertEquals($data[0], $urlRewriteResultArray[0]->toArray());
+    }
+
+    /**
+     * test findAllByData Category URL rewrites
+     */
+    public function testGetByIdentifiersCategory()
+    {
+        $data =[
+            [
+                'url_rewrite_id' => '1',
+                'entity_type' => 'category',
+                'entity_id' => '3',
+                'request_path' => 'request_path',
+                'target_path' => 'target_path',
+                'redirect_type' => 'redirect_type',
+                'store_id' => '4',
+                'description' => 'description',
+                'is_autogenerated' => '1',
+                'metadata' => '{}'
+            ]
+        ];
+
+        $dataCategoryMapMock = $this->getMock(DataCategoryUrlRewriteDatabaseMap::class, [], [], '', false);
+        $this->databaseMapPoolMock->expects($this->once())
+            ->method('getDataMap')
+            ->with(DataCategoryUrlRewriteDatabaseMap::class, 1)
+            ->willReturn($dataCategoryMapMock);
+
+        $this->urlFinderMock->expects($this->never())
+            ->method('findAllByData')
+            ->willReturn([]);
+
+        $dataCategoryMapMock->expects($this->once())
+            ->method('getData')
+            ->willReturn($data);
+
+        $urlRewriteResultArray = $this->model->findAllByData(1, 1, UrlRewriteFinder::ENTITY_TYPE_CATEGORY, 1);
+        $this->assertEquals($data[0], $urlRewriteResultArray[0]->toArray());
+    }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php
index 28aee3c7c39f463a6740a3cc32cf227207a0d827..b5515c2c254593bd5d7f015584b9b7e62f182bd3 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php
@@ -16,31 +16,31 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
 class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 {
     /** @var \Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator */
-    protected $currentUrlRewritesRegenerator;
-
-    /** @var \PHPUnit_Framework_MockObject_MockObject */
-    protected $filter;
-
-    /** @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlFinder;
+    private $currentUrlRewritesRegenerator;
 
     /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */
-    protected $productUrlPathGenerator;
+    private $productUrlPathGenerator;
 
     /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */
-    protected $product;
+    private $product;
 
     /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */
-    protected $category;
+    private $category;
 
     /** @var \Magento\CatalogUrlRewrite\Model\ObjectRegistry|\PHPUnit_Framework_MockObject_MockObject */
-    protected $objectRegistry;
+    private $objectRegistry;
 
     /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlRewriteFactory;
+    private $urlRewriteFactory;
 
     /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */
-    protected $urlRewrite;
+    private $urlRewrite;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
+
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $urlRewriteFinder;
 
     protected function setUp()
     {
@@ -55,28 +55,36 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             ->disableOriginalConstructor()->getMock();
         $this->objectRegistry = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\ObjectRegistry::class)
             ->disableOriginalConstructor()->getMock();
-        $this->filter = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\Filter::class)
-            ->disableOriginalConstructor()->getMock();
-        $this->filter->expects($this->any())->method('setStoreId')->will($this->returnSelf());
-        $this->filter->expects($this->any())->method('setEntityId')->will($this->returnSelf());
-        $this->urlFinder = $this->getMockBuilder(\Magento\UrlRewrite\Model\UrlFinderInterface::class)
+        $this->urlRewriteFinder = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder::class)
             ->disableOriginalConstructor()->getMock();
+        $this->urlRewriteFactory->expects($this->once())->method('create')
+            ->willReturn($this->urlRewrite);
         $this->productUrlPathGenerator = $this->getMockBuilder(
             \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::class
         )->disableOriginalConstructor()->getMock();
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
         $this->currentUrlRewritesRegenerator = (new ObjectManager($this))->getObject(
             \Magento\CatalogUrlRewrite\Model\Product\CurrentUrlRewritesRegenerator::class,
             [
-                'urlFinder' => $this->urlFinder,
                 'productUrlPathGenerator' => $this->productUrlPathGenerator,
-                'urlRewriteFactory' => $this->urlRewriteFactory
+                'urlRewriteFactory' => $this->urlRewriteFactory,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory,
+                'urlRewriteFinder' => $this->urlRewriteFinder
             ]
         );
     }
 
     public function testIsAutogeneratedWithoutSaveRewriteHistory()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([[UrlRewrite::IS_AUTOGENERATED => 1]])));
         $this->product->expects($this->once())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue(false));
@@ -89,7 +97,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
     public function testSkipGenerationForAutogenerated()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [UrlRewrite::IS_AUTOGENERATED => 1, UrlRewrite::REQUEST_PATH => 'same-path'],
             ])));
@@ -100,7 +108,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             [],
-            $this->currentUrlRewritesRegenerator->generate('store_id', $this->product, $this->objectRegistry)
+            $this->currentUrlRewritesRegenerator->generate('store_id', $this->product, $this->objectRegistry, 1)
         );
     }
 
@@ -108,21 +116,22 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
     {
         $requestPath = 'autogenerated.html';
         $targetPath = 'some-path.html';
+        $autoGenerated = 1;
         $storeId = 2;
         $productId = 12;
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [
                     UrlRewrite::REQUEST_PATH => $requestPath,
                     UrlRewrite::TARGET_PATH => 'custom-target-path',
                     UrlRewrite::STORE_ID => $storeId,
-                    UrlRewrite::IS_AUTOGENERATED => 1,
+                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                     UrlRewrite::METADATA => [],
                     UrlRewrite::DESCRIPTION => $description,
                 ],
             ])));
-        $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId));
+        $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId));
         $this->product->expects($this->once())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue(true));
         $this->productUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix')
@@ -133,6 +142,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             $productId,
             $requestPath,
             $targetPath,
+            0,
             OptionProvider::PERMANENT,
             [],
             $description
@@ -140,7 +150,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             [$this->urlRewrite],
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry)
+            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry, 1)
         );
     }
 
@@ -149,21 +159,22 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $productId = 12;
         $requestPath = 'autogenerated.html';
         $targetPath = 'simple-product.html';
+        $autoGenerated = 1;
         $storeId = 2;
         $metadata = ['category_id' => 2, 'some_another_data' => 1];
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [
                     UrlRewrite::REQUEST_PATH => $requestPath,
                     UrlRewrite::TARGET_PATH => 'some-path.html',
                     UrlRewrite::STORE_ID => $storeId,
-                    UrlRewrite::IS_AUTOGENERATED => 1,
+                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                     UrlRewrite::METADATA => $metadata,
                     UrlRewrite::DESCRIPTION => $description,
                 ],
             ])));
-        $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId));
+        $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId));
         $this->product->expects($this->once())->method('getData')->with('save_rewrites_history')
             ->will($this->returnValue(true));
         $this->productUrlPathGenerator->expects($this->once())->method('getUrlPathWithSuffix')
@@ -174,6 +185,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             $productId,
             $requestPath,
             $targetPath,
+            0,
             OptionProvider::PERMANENT,
             $metadata,
             $description
@@ -181,13 +193,13 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
 
         $this->assertEquals(
             [$this->urlRewrite],
-            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry)
+            $this->currentUrlRewritesRegenerator->generate($storeId, $this->product, $this->objectRegistry, 2)
         );
     }
 
     public function testSkipGenerationForCustom()
     {
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [
                     UrlRewrite::IS_AUTOGENERATED => 0,
@@ -210,21 +222,31 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $productId = 123;
         $requestPath = 'generate-for-custom-without-redirect-type.html';
         $targetPath = 'custom-target-path.html';
+        $autoGenerated = 0;
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [
                     UrlRewrite::REQUEST_PATH => $requestPath,
                     UrlRewrite::TARGET_PATH => $targetPath,
                     UrlRewrite::REDIRECT_TYPE => 0,
-                    UrlRewrite::IS_AUTOGENERATED => 0,
+                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                     UrlRewrite::DESCRIPTION => $description,
                     UrlRewrite::METADATA => [],
                 ],
             ])));
         $this->productUrlPathGenerator->expects($this->never())->method('getUrlPathWithSuffix');
-        $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId));
-        $this->prepareUrlRewriteMock($storeId, $productId, $requestPath, $targetPath, 0, [], $description);
+        $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId));
+        $this->prepareUrlRewriteMock(
+            $storeId,
+            $productId,
+            $requestPath,
+            $targetPath,
+            $autoGenerated,
+            0,
+            [],
+            $description
+        );
 
         $this->assertEquals(
             [$this->urlRewrite],
@@ -238,22 +260,23 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $productId = 123;
         $requestPath = 'generate-for-custom-without-redirect-type.html';
         $targetPath = 'generated-target-path.html';
+        $autoGenerated = 0;
         $description = 'description';
-        $this->urlFinder->expects($this->once())->method('findAllByData')
+        $this->urlRewriteFinder->expects($this->once())->method('findAllByData')
             ->will($this->returnValue($this->getCurrentRewritesMocks([
                 [
                     UrlRewrite::REQUEST_PATH => $requestPath,
                     UrlRewrite::TARGET_PATH => 'custom-target-path.html',
                     UrlRewrite::REDIRECT_TYPE => 'code',
-                    UrlRewrite::IS_AUTOGENERATED => 0,
+                    UrlRewrite::IS_AUTOGENERATED => $autoGenerated,
                     UrlRewrite::DESCRIPTION => $description,
                     UrlRewrite::METADATA => [],
                 ],
             ])));
         $this->productUrlPathGenerator->expects($this->any())->method('getUrlPathWithSuffix')
             ->will($this->returnValue($targetPath));
-        $this->product->expects($this->any())->method('getId')->will($this->returnValue($productId));
-        $this->prepareUrlRewriteMock($storeId, $productId, $requestPath, $targetPath, 'code', [], $description);
+        $this->product->expects($this->any())->method('getEntityId')->will($this->returnValue($productId));
+        $this->prepareUrlRewriteMock($storeId, $productId, $requestPath, $targetPath, 0, 'code', [], $description);
 
         $this->assertEquals(
             [$this->urlRewrite],
@@ -287,6 +310,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
      * @param mixed $productId
      * @param mixed $requestPath
      * @param mixed $targetPath
+     * @param mixed $autoGenerated
      * @param mixed $redirectType
      * @param mixed $metadata
      * @param mixed $description
@@ -296,6 +320,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
         $productId,
         $requestPath,
         $targetPath,
+        $autoGenerated,
         $redirectType,
         $metadata,
         $description
@@ -310,7 +335,7 @@ class CurrentUrlRewritesRegeneratorTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setTargetPath')->with($targetPath)
             ->will($this->returnSelf());
-        $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with(0)
+        $this->urlRewrite->expects($this->any())->method('setIsAutogenerated')->with($autoGenerated)
             ->will($this->returnSelf());
         $this->urlRewrite->expects($this->any())->method('setRedirectType')->with($redirectType)
             ->will($this->returnSelf());
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php
index 0e6c501c4981b6dc6e7660ee02824c7c091d4294..15d93a5026f32a825007a865627231861681b591 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php
@@ -41,6 +41,9 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
     /** @var  ProductScopeRewriteGenerator */
     private $productScopeGenerator;
 
+    /** @var \PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
+
     public function setUp()
     {
         $this->currentUrlRewritesRegenerator = $this->getMockBuilder(
@@ -61,6 +64,15 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->storeViewService = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Service\V1\StoreViewService::class)
             ->disableOriginalConstructor()->getMock();
         $this->storeManager = $this->getMock(StoreManagerInterface::class);
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
 
         $this->productScopeGenerator = (new ObjectManager($this))->getObject(
             \Magento\CatalogUrlRewrite\Model\ProductScopeRewriteGenerator::class,
@@ -72,6 +84,7 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
                 'objectRegistryFactory' => $this->objectRegistryFactory,
                 'storeViewService' => $this->storeViewService,
                 'storeManager' => $this->storeManager,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory
             ]
         );
     }
@@ -91,34 +104,34 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
             ->willReturn(1);
         $this->initObjectRegistryFactory([]);
         $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $canonical->setTargetPath('category-1')
+        $canonical->setRequestPath('category-1')
             ->setStoreId(1);
         $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$canonical]));
         $categories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $categories->setTargetPath('category-2')
+        $categories->setRequestPath('category-2')
             ->setStoreId(2);
         $this->categoriesUrlRewriteGenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$categories]));
         $current = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $current->setTargetPath('category-3')
+        $current->setRequestPath('category-3')
             ->setStoreId(3);
         $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$current]));
         $anchorCategories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $anchorCategories->setTargetPath('category-4')
+        $anchorCategories->setRequestPath('category-4')
             ->setStoreId(4);
         $this->anchorUrlRewriteGenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$anchorCategories]));
 
         $this->assertEquals(
             [
-                'category-1-1' => $canonical,
-                'category-2-2' => $categories,
-                'category-3-3' => $current,
-                'category-4-4' => $anchorCategories
+                'category-1_1' => $canonical,
+                'category-2_2' => $categories,
+                'category-3_3' => $current,
+                'category-4_4' => $anchorCategories
             ],
-            $this->productScopeGenerator->generateForGlobalScope([$categoryMock], $product)
+            $this->productScopeGenerator->generateForGlobalScope([$categoryMock], $product, 1)
         );
     }
 
@@ -138,7 +151,7 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($store));
         $this->initObjectRegistryFactory([$category]);
         $canonical = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite();
-        $canonical->setTargetPath('category-1')
+        $canonical->setRequestPath('category-1')
             ->setStoreId(1);
         $this->canonicalUrlRewriteGenerator->expects($this->any())->method('generate')
             ->will($this->returnValue([$canonical]));
@@ -150,8 +163,8 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
             ->will($this->returnValue([]));
 
         $this->assertEquals(
-            ['category-1-1' => $canonical],
-            $this->productScopeGenerator->generateForSpecificStoreView(1, [$category], $product)
+            ['category-1_1' => $canonical],
+            $this->productScopeGenerator->generateForSpecificStoreView(1, [$category], $product, 1)
         );
     }
 
@@ -165,7 +178,7 @@ class ProductScopeRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->storeViewService->expects($this->exactly(2))->method('doesEntityHaveOverriddenUrlKeyForStore')
             ->will($this->returnValue(true));
 
-        $this->assertEquals([], $this->productScopeGenerator->generateForGlobalScope([], $product));
+        $this->assertEquals([], $this->productScopeGenerator->generateForGlobalScope([], $product, 1));
     }
 
     /**
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php
index a392b5231b22bb87d2ad31692870ddb3fe60f7de..81eb66551007d581acaf885a9f684ca0a88451d5 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlRewriteGeneratorTest.php
@@ -129,6 +129,6 @@ class ProductUrlRewriteGeneratorTest extends \PHPUnit_Framework_TestCase
         $this->productScopeRewriteGenerator->expects($this->once())
             ->method('generateForSpecificStoreView')
             ->willReturn($urls);
-        $this->assertEquals($urls, $this->productUrlRewriteGenerator->generate($productMock));
+        $this->assertEquals($urls, $this->productUrlRewriteGenerator->generate($productMock, 1));
     }
 }
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
index eb9977fa1f64fc5c8bbd216f972fc8a886efe66b..069fa1a859a7913f1eb5d1cdd610900fbaff39cb 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
@@ -23,12 +23,12 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
     /**
      * @var string
      */
-    protected $categoryId = 10;
+    private $categoryId = 10;
 
     /**
      * @var \Magento\UrlRewrite\Model\UrlPersistInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $urlPersist;
+    private $urlPersist;
 
     /**
      * @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject
@@ -38,12 +38,12 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
     /**
      * @var \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $productUrlRewriteGenerator;
+    private $productUrlRewriteGenerator;
 
     /**
      * @var \Magento\Catalog\Api\ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $productRepository;
+    private $productRepository;
 
     /**
      * @var \Magento\CatalogImportExport\Model\Import\Product|\PHPUnit_Framework_MockObject_MockObject
@@ -53,67 +53,65 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
     /**
      * @var \Magento\Framework\Event\Observer|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $observer;
+    private $observer;
 
     /**
      * @var \Magento\Framework\Event|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $event;
+    private $event;
 
     /**
      * @var \Magento\Catalog\Model\ProductFactory|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $catalogProductFactory;
+    private $catalogProductFactory;
 
     /**
      * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $storeManager;
+    private $storeManager;
 
     /**
      * @var \Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $objectRegistryFactory;
+    private $objectRegistryFactory;
 
     /**
      * @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $productUrlPathGenerator;
+    private $productUrlPathGenerator;
 
     /**
      * @var \Magento\CatalogUrlRewrite\Service\V1\StoreViewService|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $storeViewService;
+    private $storeViewService;
 
     /**
      * @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $urlRewriteFactory;
+    private $urlRewriteFactory;
 
     /**
      * @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $urlRewrite;
+    private $urlRewrite;
 
     /**
      * @var \Magento\CatalogUrlRewrite\Model\ObjectRegistry|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $objectRegistry;
-
-    /**
-     * @var \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver|\PHPUnit_Framework_MockObject_MockObject
-     */
-    protected $importMock;
+    private $objectRegistry;
 
     /**
      * @var \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver
      */
-    protected $import;
+    private $import;
 
     /**
      * @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject
      */
-    protected $product;
+    private $product;
+
+    /** @var \Magento\UrlRewrite\Model\MergeDataProvider|\PHPUnit_Framework_MockObject_MockObject */
+    private $mergeDataProvider;
 
     /**
      * Test products returned by getBunch method of event object.
@@ -278,6 +276,15 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
             ->expects($this->any())
             ->method('getCategoryProcessor')
             ->willReturn($categoryProcessor);
+        $mergeDataProviderFactory = $this->getMock(
+            \Magento\UrlRewrite\Model\MergeDataProviderFactory::class,
+            ['create'],
+            [],
+            '',
+            false
+        );
+        $this->mergeDataProvider = new \Magento\UrlRewrite\Model\MergeDataProvider;
+        $mergeDataProviderFactory->expects($this->once())->method('create')->willReturn($this->mergeDataProvider);
 
         $this->objectManager = new ObjectManager($this);
         $this->import = $this->objectManager->getObject(
@@ -291,6 +298,7 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
                 'urlPersist' => $this->urlPersist,
                 'urlRewriteFactory' => $this->urlRewriteFactory,
                 'urlFinder' => $this->urlFinder,
+                'mergeDataProviderFactory' => $mergeDataProviderFactory
             ]
         );
     }
@@ -433,14 +441,15 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
         $this->urlRewrite->expects($this->any())->method('setRequestPath')->willReturnSelf();
         $this->urlRewrite->expects($this->any())->method('setTargetPath')->willReturnSelf();
         $this->urlRewrite->expects($this->any())->method('getTargetPath')->willReturn('targetPath');
+        $this->urlRewrite->expects($this->any())->method('getRequestPath')->willReturn('requestPath');
         $this->urlRewrite->expects($this->any())->method('getStoreId')
             ->willReturnOnConsecutiveCalls(0, 'not global');
 
         $this->urlRewriteFactory->expects($this->any())->method('create')->willReturn($this->urlRewrite);
 
         $productUrls = [
-            'targetPath-0' => $this->urlRewrite,
-            'targetPath-not global' => $this->urlRewrite
+            'requestPath_0' => $this->urlRewrite,
+            'requestPath_not global' => $this->urlRewrite
         ];
 
         $this->urlPersist
diff --git a/app/code/Magento/CatalogUrlRewrite/etc/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/di.xml
index 5a8974fc52da4b619b73dc627ea8d35159d5ac71..7f0fc718083ecab1df47dd659bbbcac78e14a77f 100644
--- a/app/code/Magento/CatalogUrlRewrite/etc/di.xml
+++ b/app/code/Magento/CatalogUrlRewrite/etc/di.xml
@@ -23,4 +23,12 @@
     <type name="Magento\UrlRewrite\Model\StorageInterface">
         <plugin name="storage_plugin" type="Magento\CatalogUrlRewrite\Model\Category\Plugin\Storage"/>
     </type>
+    <type name="Magento\CatalogUrlRewrite\Model\Map\UrlRewriteFinder">
+        <arguments>
+            <argument name="urlRewriteClassNames" xsi:type="array">
+                <item name="product" xsi:type="string">Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap</item>
+                <item name="category" xsi:type="string">Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap</item>
+            </argument>
+        </arguments>
+    </type>
 </config>
diff --git a/app/code/Magento/UrlRewrite/Model/MergeDataProvider.php b/app/code/Magento/UrlRewrite/Model/MergeDataProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..20ecf0f52c05781d8acaf076cca745e04481347e
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Model/MergeDataProvider.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\UrlRewrite\Model;
+
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite as UrlRewriteService;
+
+/**
+ * This class is to be used as a container for new generated url rewrites by adding new ones using merge method
+ * Removes duplicates for a set/array of Url Rewrites based on the unique key of the url_rewrites table
+ *
+ */
+class MergeDataProvider
+{
+    const SEPARATOR = '_';
+
+    /**
+     * @var $rewritesArray[]
+     */
+    private $data = [];
+
+    /**
+     * Adds url rewrites to class data container by removing duplicates by a unique key
+     *
+     * @param UrlRewriteService[] $urlRewritesArray
+     * @return void
+     */
+    public function merge(array $urlRewritesArray)
+    {
+        foreach ($urlRewritesArray as $urlRewrite) {
+            $key = $urlRewrite->getRequestPath() . self::SEPARATOR . $urlRewrite->getStoreId();
+            if ($key !== self::SEPARATOR) {
+                $this->data[$key] = $urlRewrite;
+            } else {
+                $this->data[] = $urlRewrite;
+            }
+        }
+    }
+
+    /**
+     * Returns the data added to container
+     *
+     * @return UrlRewriteService[]
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+}
diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Model/MergeDataProviderTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Model/MergeDataProviderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..76aa5d368be4c805155b374245799197e4b538dd
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Unit/Model/MergeDataProviderTest.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\UrlRewrite\Test\Unit\Model;
+
+use Magento\UrlRewrite\Model\MergeDataProvider;
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+
+class MergeDataProviderTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var MergeDataProvider|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $urlRewritesSet;
+
+    /**
+     * Set up
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->urlRewritesSet = (new ObjectManager($this))->getObject(
+            MergeDataProvider::class,
+            []
+        );
+    }
+
+    /**
+     * Run test merge method
+     *
+     * @param array $urlRewriteMockArray
+     * @param String $expectedData
+     * @param int $arrayCount
+     * @dataProvider mergeDataProvider
+     * @return void
+     */
+    public function testMerge($urlRewriteMockArray, $expectedData, $arrayCount)
+    {
+        $this->urlRewritesSet->merge($urlRewriteMockArray);
+        $this->assertEquals($expectedData, $this->urlRewritesSet->getData());
+        $this->assertCount($arrayCount, $this->urlRewritesSet->getData());
+    }
+
+    /**
+     * Run test getData method when data is Empty
+     *
+     * @return void
+     */
+    public function testGetDataWhenEmpty()
+    {
+        $this->assertEmpty($this->urlRewritesSet->getData());
+    }
+
+    /**
+     * Data provider for testMerge
+     *
+     * @return array
+     */
+    public function mergeDataProvider()
+    {
+        $urlRewriteMock1 = $this->getMock(UrlRewrite::class, [], [], '', false);
+
+        $requestPathForMock2 = 'magento.tst/products/simpleproduct2';
+        $storeIdForMock2 = 'testStore2';
+        $urlRewriteMock2 = $this->getMock(UrlRewrite::class, [], [], '', false);
+
+        $urlRewriteMock2->expects($this->atLeastOnce())
+            ->method('getRequestPath')
+            ->willReturn($requestPathForMock2);
+
+        $urlRewriteMock2->expects($this->atLeastOnce())
+            ->method('getStoreId')
+            ->willReturn($storeIdForMock2);
+
+        $requestPathForMock3 = 'magento.tst/products/simpleproduct3';
+        $storeIdForMock3 = 'testStore3';
+        $urlRewriteMock3 = $this->getMock(UrlRewrite::class, [], [], '', false);
+
+        $urlRewriteMock3->expects($this->atLeastOnce())
+            ->method('getRequestPath')
+            ->willReturn($requestPathForMock3);
+
+        $urlRewriteMock3->expects($this->atLeastOnce())
+            ->method('getStoreId')
+            ->willReturn($storeIdForMock3);
+
+        return [
+            [
+                [],
+                [],
+                0
+            ],
+            [
+                [$urlRewriteMock1],
+                [$urlRewriteMock1],
+                1
+            ],
+            [
+                [
+                    $urlRewriteMock1,
+                    $urlRewriteMock2,
+                    $urlRewriteMock2
+                ],
+                [
+                    $urlRewriteMock1,
+                    $requestPathForMock2 . '_' . $storeIdForMock2 => $urlRewriteMock2
+                ],
+                2
+            ],
+            [
+                [
+                    $urlRewriteMock1,
+                    $urlRewriteMock2,
+                    $urlRewriteMock3
+                ],
+                [
+                    $urlRewriteMock1,
+                    $requestPathForMock2 . '_' . $storeIdForMock2 => $urlRewriteMock2,
+                    $requestPathForMock3 . '_' . $storeIdForMock3 => $urlRewriteMock3
+                ],
+                3
+            ],
+        ];
+    }
+}
diff --git a/app/etc/di.xml b/app/etc/di.xml
index 3a5533fd825ce677d5df3f6ff2dded69af010693..a7c38ae1246b602a105c5e1a588fc5e7b9561b2e 100755
--- a/app/etc/di.xml
+++ b/app/etc/di.xml
@@ -1221,4 +1221,17 @@
             </argument>
         </arguments>
     </type>
+    <type name="Magento\Framework\DB\TemporaryTableService">
+        <arguments>
+            <argument name="allowedIndexMethods" xsi:type="array">
+                <item name="HASH" xsi:type="string">HASH</item>
+                <item name="BTREE" xsi:type="string">BTREE</item>
+            </argument>
+            <argument name="allowedEngines" xsi:type="array">
+                <item name="INNODB" xsi:type="string">INNODB</item>
+                <item name="MEMORY" xsi:type="string">MEMORY</item>
+                <item name="MYISAM" xsi:type="string">MYISAM</item>
+            </argument>
+        </arguments>
+    </type>
 </config>
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserverTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserverTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..101ce61c779dcfd930a7816bcafc37c84c4d3fdc
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserverTest.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogUrlRewrite\Observer;
+
+use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
+
+/**
+ * Test Cases:
+ *
+ * - has changes for url_key, is_anchor, changed product list
+ * - in global scope
+ * - in store
+ * - generate canonical
+ * - generate children
+ * - has children
+ * - generate current
+ * - has rewrites history
+ * @magentoAppArea adminhtml
+ */
+class CategoryProcessUrlRewriteSavingObserverTest extends \PHPUnit_Framework_TestCase
+{
+    /** @var \Magento\Framework\ObjectManagerInterface */
+    protected $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+    }
+
+    /**
+     * @param array $filter
+     * @return array
+     */
+    private function getActualResults(array $filter)
+    {
+        /** @var \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder */
+        $urlFinder = $this->objectManager->get(\Magento\UrlRewrite\Model\UrlFinderInterface::class);
+        $actualResults = [];
+        foreach ($urlFinder->findAllByData($filter) as $url) {
+            $actualResults[] = [
+                'request_path' => $url->getRequestPath(),
+                'target_path' => $url->getTargetPath(),
+                'is_auto_generated' => (int)$url->getIsAutogenerated(),
+                'redirect_type' => $url->getRedirectType()
+            ];
+        }
+        return $actualResults;
+    }
+
+    public function tearDown()
+    {
+        $category = $this->objectManager->create(\Magento\Catalog\Model\Category::class);
+        $category->load(3);
+        $category->delete();
+    }
+
+    /**
+     * @magentoDataFixture Magento/CatalogUrlRewrite/_files/categories.php
+     * @magentoDbIsolation enabled
+     * @magentoAppIsolation enabled
+     */
+    public function testUrlKeyHasChanged()
+    {
+        $categoryFilter = [
+            UrlRewrite::ENTITY_TYPE => 'category',
+        ];
+        $expected = [
+            [
+                'request_path' => "category-1.html",
+                'target_path' => "catalog/category/view/id/3",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-1/category-1-1.html",
+                'target_path' => "catalog/category/view/id/4",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-1/category-1-1/category-1-1-1.html",
+                'target_path' => "catalog/category/view/id/5",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-2.html",
+                'target_path' =>  "catalog/category/view/id/6",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ]
+
+        ];
+        $actual = $this->getActualResults($categoryFilter);
+        foreach ($expected as $row) {
+            $this->assertContains($row, $actual);
+        }
+        /** @var \Magento\Catalog\Model\Category $category */
+        $category = $this->objectManager->create(\Magento\Catalog\Model\Category::class);
+        $category->load(3);
+        $category->setData('save_rewrites_history', false);
+        $category->setUrlKey('new-url');
+        $category->save();
+        $expected = [
+            [
+                'request_path' => "new-url.html",
+                'target_path' => "catalog/category/view/id/3",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "new-url/category-1-1.html",
+                'target_path' => "catalog/category/view/id/4",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "new-url/category-1-1/category-1-1-1.html",
+                'target_path' => "catalog/category/view/id/5",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-2.html",
+                'target_path' =>  "catalog/category/view/id/6",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ]
+
+        ];
+        $actual = $this->getActualResults($categoryFilter);
+        foreach ($expected as $row) {
+            $this->assertContains($row, $actual);
+        }
+    }
+
+    /**
+     * @magentoDataFixture Magento/CatalogUrlRewrite/_files/categories_with_products.php
+     * @magentoDbIsolation enabled
+     * @magentoAppIsolation enabled
+     */
+    public function testIsAnchorHasChanged()
+    {
+        $categoryFilter = [
+            UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE,
+        ];
+        /** @var \Magento\Catalog\Model\Category $category */
+        $category = $this->objectManager->create(\Magento\Catalog\Model\Category::class);
+        $category->load(3);
+        $category->setData('is_anchor', false);
+        $category->save();
+        $expected = [
+            [
+                'request_path' => "category-1.html",
+                'target_path' => "catalog/category/view/id/3",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-1/category-1-1.html",
+                'target_path' => "catalog/category/view/id/4",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-1/category-1-1/category-1-1-1.html",
+                'target_path' => "catalog/category/view/id/5",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ],
+            [
+                'request_path' => "category-2.html",
+                'target_path' =>  "catalog/category/view/id/6",
+                'is_auto_generated' => 1,
+                'redirect_type' => 0
+            ]
+
+        ];
+        $actual = $this->getActualResults($categoryFilter);
+        foreach ($expected as $row) {
+            $this->assertContains($row, $actual);
+        }
+    }
+}
diff --git a/lib/internal/Magento/Framework/DB/TemporaryTableService.php b/lib/internal/Magento/Framework/DB/TemporaryTableService.php
new file mode 100644
index 0000000000000000000000000000000000000000..36a96f69ad67c6d46527121c377a2b2500ea309c
--- /dev/null
+++ b/lib/internal/Magento/Framework/DB/TemporaryTableService.php
@@ -0,0 +1,164 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Framework\DB;
+
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Select;
+
+/**
+ * Class TemporaryTableService creates a temporary table in mysql from a Magento\Framework\DB\Select.
+ * Use this class to create an index with that that you want to query later for quick data access
+ *
+ * @api
+ */
+class TemporaryTableService
+{
+    const INDEX_METHOD_HASH = 'HASH';
+    const DB_ENGINE_INNODB = 'INNODB';
+
+    /**
+     * @var string[]
+     */
+    private $allowedIndexMethods;
+
+    /**
+     * @var string[]
+     */
+    private $allowedEngines;
+
+    /**
+     * @var \Magento\Framework\Math\Random
+     */
+    private $random;
+
+    /**
+     * @var AdapterInterface[]
+     */
+    private $createdTableAdapters = [];
+
+    /**
+     * @param \Magento\Framework\Math\Random $random
+     * @param string[] $allowedIndexMethods
+     * @param string[] $allowedEngines
+     */
+    public function __construct(
+        \Magento\Framework\Math\Random $random,
+        $allowedIndexMethods = [],
+        $allowedEngines = []
+    ) {
+        $this->random = $random;
+        $this->allowedIndexMethods = $allowedIndexMethods;
+        $this->allowedEngines = $allowedEngines;
+    }
+
+    /**
+     * Creates a temporary table from select removing duplicate rows if you have a union in your select
+     * This method should always be paired with dropTable to ensure cleanup
+     * Make sure you index your data so you can query it fast
+     * You can choose from memory or file table and provide indexes to ensure fast data query
+     *
+     * Example: createFromSelect(
+     *           $selectObject,
+     *           $this->resourceConnection->getConnection(),
+     *           [
+     *              'PRIMARY' => ['primary_id'],
+     *              'some_single_field_index' => ['field'],
+     *              'UNQ_some_multiple_field_index' => ['field1', 'field2'],
+     *           ]
+     *          )
+     * Note that indexes names with UNQ_ prefix, will be created as unique
+     *
+     * @param Select $select
+     * @param AdapterInterface $adapter
+     * @param array $indexes
+     * @param string $indexMethod
+     * @param string $dbEngine
+     * @return string
+     * @throws \InvalidArgumentException
+     */
+    public function createFromSelect(
+        Select $select,
+        AdapterInterface $adapter,
+        array $indexes = [],
+        $indexMethod = self::INDEX_METHOD_HASH,
+        $dbEngine = self::DB_ENGINE_INNODB
+    ) {
+        if (!in_array($indexMethod, $this->allowedIndexMethods)) {
+            throw new \InvalidArgumentException(
+                sprintf('indexMethod must be one of %s', implode(',', $this->allowedIndexMethods))
+            );
+        }
+
+        if (!in_array($dbEngine, $this->allowedEngines)) {
+            throw new \InvalidArgumentException(
+                sprintf('dbEngine must be one of %s', implode(',', $this->allowedEngines))
+            );
+        }
+
+        $name = $this->random->getUniqueHash('tmp_select_');
+
+        $indexStatements = [];
+        foreach ($indexes as $indexName => $columns) {
+            $renderedColumns = implode(',', array_map([$adapter, 'quoteIdentifier'], $columns));
+
+            $indexType = sprintf(
+                'INDEX %s USING %s',
+                $adapter->quoteIdentifier($indexName),
+                $indexMethod
+            );
+
+            if ($indexName === 'PRIMARY') {
+                $indexType = 'PRIMARY KEY';
+            } elseif (strpos($indexName, 'UNQ_') === 0) {
+                $indexType = sprintf('UNIQUE %s', $adapter->quoteIdentifier($indexName));
+            }
+
+            $indexStatements[] = sprintf('%s(%s)', $indexType, $renderedColumns);
+        }
+
+        $statement = sprintf(
+            'CREATE TEMPORARY TABLE %s %s ENGINE=%s IGNORE (%s)',
+            $adapter->quoteIdentifier($name),
+            $indexStatements ? '(' . implode(',', $indexStatements) . ')' : '',
+            $adapter->quoteIdentifier($dbEngine),
+            "{$select}"
+        );
+
+        $adapter->query(
+            $statement,
+            $select->getBind()
+        );
+
+        $this->createdTableAdapters[$name] = $adapter;
+
+        return $name;
+    }
+
+    /**
+     * Method used to drop a table by name
+     * This class will hold all temporary table names in createdTableAdapters array
+     * so we can dispose them once we're finished
+     *
+     * Example: dropTable($name)
+     * where $name is a variable that holds the name for a previously created temporary  table
+     * by using "createFromSelect" method
+     *
+     * @param string $name
+     * @return bool
+     */
+    public function dropTable($name)
+    {
+        if (!empty($this->createdTableAdapters)) {
+            if (isset($this->createdTableAdapters[$name]) && !empty($name)) {
+                $adapter = $this->createdTableAdapters[$name];
+                $adapter->dropTemporaryTable($name);
+                unset($this->createdTableAdapters[$name]);
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/lib/internal/Magento/Framework/DB/Test/Unit/TemporaryTableServiceTest.php b/lib/internal/Magento/Framework/DB/Test/Unit/TemporaryTableServiceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..620a197386fde6bcfa923fff03a3919511ce3cb5
--- /dev/null
+++ b/lib/internal/Magento/Framework/DB/Test/Unit/TemporaryTableServiceTest.php
@@ -0,0 +1,209 @@
+<?php
+/**
+ * Copyright © 2013-2017 Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Framework\DB\Test\Unit;
+
+use Magento\Framework\DB\TemporaryTableService;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Select;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\Math\Random;
+
+class TemporaryTableServiceTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var TemporaryTableService|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $temporaryTableService;
+
+    /**
+     * @var AdapterInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $adapterMock;
+
+    /**
+     * @var Random|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $randomMock;
+
+    /**
+     * @var Select|\PHPUnit_Framework_MockObject_MockObject
+     */
+    private $selectMock;
+
+    /**
+     * Set up
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->adapterMock = $this->getMock(AdapterInterface::class);
+        $this->selectMock = $this->getMock(Select::class, [], [], '', false);
+        $this->randomMock = $this->getMock(Random::class, [], [], '', false);
+        $this->temporaryTableService = (new ObjectManager($this))->getObject(
+            TemporaryTableService::class,
+            [
+                'random' => $this->randomMock,
+                'allowedIndexMethods' => ['HASH'],
+                'allowedEngines' => ['INNODB']
+            ]
+        );
+    }
+
+    /**
+     * Run test createFromSelect method
+     *
+     * @return void
+     */
+    public function testCreateFromSelectWithException()
+    {
+        $this->setExpectedException(\InvalidArgumentException::class);
+        $random = 'random_table';
+        $indexes = [
+            ['PRIMARY' => ['primary_column_name']],
+            'CREATE TEMPORARY TABLE random_table (PRIMARY KEY(primary_column_name)) ENGINE=INNODB IGNORE '
+            . '(select * from sometable)'
+        ];
+
+        $this->assertEquals(
+            $random,
+            $this->temporaryTableService->createFromSelect(
+                $this->selectMock,
+                $this->adapterMock,
+                $indexes,
+                TemporaryTableService::INDEX_METHOD_HASH . "Other",
+                TemporaryTableService::DB_ENGINE_INNODB . "Other"
+            )
+        );
+    }
+
+    /**
+     * Run test createFromSelect method
+     *
+     * @param array $indexes
+     * @param string $expectedSelect
+     * @dataProvider createFromSelectDataProvider
+     * @return void
+     */
+    public function testCreateFromSelect($indexes, $expectedSelect)
+    {
+        $selectString = 'select * from sometable';
+        $random = 'random_table';
+
+        $this->randomMock->expects($this->once())
+            ->method('getUniqueHash')
+            ->willReturn($random);
+
+        $this->adapterMock->expects($this->once())
+            ->method('query')
+            ->with($expectedSelect)
+            ->willReturnSelf();
+
+        $this->adapterMock->expects($this->once())
+            ->method('query')
+            ->willReturnSelf();
+
+        $this->adapterMock->expects($this->any())
+            ->method('quoteIdentifier')
+            ->willReturnArgument(0);
+
+        $this->selectMock->expects($this->once())
+            ->method('getBind')
+            ->willReturn(['bind']);
+
+        $this->selectMock->expects($this->any())
+            ->method('__toString')
+            ->willReturn($selectString);
+
+        $this->assertEquals(
+            $random,
+            $this->temporaryTableService->createFromSelect(
+                $this->selectMock,
+                $this->adapterMock,
+                $indexes
+            )
+        );
+    }
+
+    /**
+     * Run test dropTable method when createdTables array of TemporaryTableService is empty
+     *
+     * @return void
+     */
+    public function testDropTableWhenCreatedTablesArrayIsEmpty()
+    {
+        $this->assertFalse($this->temporaryTableService->dropTable('tmp_select_table'));
+    }
+
+    /**
+     * Run test dropTable method when data exists in createdTables array of TemporaryTableService
+     *
+     * @param string $tableName
+     * @param bool $assertion
+     *
+     * @dataProvider dropTableWhenCreatedTablesArrayNotEmptyDataProvider
+     * @return void
+     */
+    public function testDropTableWhenCreatedTablesArrayNotEmpty($tableName, $assertion)
+    {
+        $createdTableAdapters = new \ReflectionProperty($this->temporaryTableService, 'createdTableAdapters');
+        $createdTableAdapters->setAccessible(true);
+        $createdTableAdapters->setValue($this->temporaryTableService, ['tmp_select_table' => $this->adapterMock]);
+        $createdTableAdapters->setAccessible(false);
+
+        $this->adapterMock->expects($this->any())
+            ->method('dropTemporaryTable')
+            ->willReturn(true);
+
+        $this->assertEquals($this->temporaryTableService->dropTable($tableName), $assertion);
+    }
+
+    /**
+     * @return array
+     */
+    public function createFromSelectDataProvider()
+    {
+        return [
+            [
+                ['PRIMARY' => ['primary_column_name']],
+                'CREATE TEMPORARY TABLE random_table (PRIMARY KEY(primary_column_name)) ENGINE=INNODB IGNORE '
+                . '(select * from sometable)'
+            ],
+            [
+                ['UNQ_INDX' => ['column1', 'column2']],
+                'CREATE TEMPORARY TABLE random_table (UNIQUE UNQ_INDX(column1,column2)) ENGINE=INNODB IGNORE '
+                . '(select * from sometable)'
+            ],
+            [
+                ['OTH_INDX' => ['column3', 'column4']],
+                'CREATE TEMPORARY TABLE random_table (INDEX OTH_INDX USING HASH(column3,column4)) ENGINE=INNODB IGNORE '
+                . '(select * from sometable)'
+            ],
+            [
+                [
+                    'PRIMARY' => ['primary_column_name'],
+                    'OTH_INDX' => ['column3', 'column4'],
+                    'UNQ_INDX' => ['column1', 'column2']
+                ],
+                'CREATE TEMPORARY TABLE random_table '
+                . '(PRIMARY KEY(primary_column_name),'
+                . 'INDEX OTH_INDX USING HASH(column3,column4),UNIQUE UNQ_INDX(column1,column2)) ENGINE=INNODB IGNORE '
+                . '(select * from sometable)'
+            ],
+        ];
+    }
+
+    /**
+     * @return array
+     */
+    public function dropTableWhenCreatedTablesArrayNotEmptyDataProvider()
+    {
+        return [
+            ['tmp_select_table_1', false],
+            ['tmp_select_table', true],
+        ];
+    }
+}