diff --git a/core/DataAccess/ArchiveInvalidator.php b/core/DataAccess/ArchiveInvalidator.php
index bf865e218821eea237172364e3c7a2001cd43fa4..6648c637c8d88aa5f9872defff5c9566dbe21d11 100644
--- a/core/DataAccess/ArchiveInvalidator.php
+++ b/core/DataAccess/ArchiveInvalidator.php
@@ -21,6 +21,8 @@ use Piwik\Period\Week;
  *
  * Invalidated archives can still be selected and displayed in UI and API (until they are reprocessed by core:archive)
  *
+ * The invalidated archives will be deleted by ArchivePurger
+ *
  * @package Piwik\DataAccess
  */
 class ArchiveInvalidator {
@@ -37,7 +39,7 @@ class ArchiveInvalidator {
      * @return array
      * @throws \Exception
      */
-    public function markArchivesAsInvalidated($idSites, $dates, $period)
+    public function markArchivesAsInvalidated(array $idSites, $dates, $period)
     {
         $this->findOlderDateWithLogs();
         $datesToInvalidate = $this->getDatesToInvalidateFromString($dates);
@@ -45,10 +47,10 @@ class ArchiveInvalidator {
 
         \Piwik\Plugins\SitesManager\API::getInstance()->updateSiteCreatedTime($idSites, $minDate);
 
-        $this->markArchivesInvalidatedFor($idSites, $period, $datesToInvalidate);
+        $datesByMonth = $this->getDatesByYearMonth($datesToInvalidate);
+        $this->markArchivesInvalidatedFor($idSites, $period, $datesByMonth);
 
-        $store = new InvalidatedReports();
-        $store->addInvalidatedSitesToReprocess($idSites);
+        $this->persistInvalidatedArchives($idSites, $datesByMonth);
 
         return $this->makeOutputLogs();
     }
@@ -79,15 +81,13 @@ class ArchiveInvalidator {
     /**
      * @param $idSites
      * @param $period string
-     * @param $datesToInvalidate Date[]
+     * @param $datesByMonth array
      * @throws \Exception
      */
-    private function markArchivesInvalidatedFor($idSites, $period, $datesToInvalidate)
+    private function markArchivesInvalidatedFor($idSites, $period, $datesByMonth)
     {
         $invalidateForPeriodId = $this->getPeriodId($period);
 
-        $datesByMonth = $this->getDatesByYearMonth($datesToInvalidate);
-
         // In each table, invalidate day/week/month/year containing this date
         $archiveTables = ArchiveTableCreator::getTablesArchivesInstalled();
         foreach ($archiveTables as $table) {
@@ -161,10 +161,11 @@ class ArchiveInvalidator {
                 && $date->isEarlier($this->minimumDateWithLogs)
             ) {
                 $this->warningDates[] = $date->toString();
-            } else {
-                $this->processedDates[] = $date->toString();
+                continue;
             }
 
+            $this->processedDates[] = $date->toString();
+
             $month = $date->toString('Y_m');
             // For a given date, we must invalidate in the monthly archive table
             $datesByMonth[$month][] = $date->toString();
@@ -217,8 +218,23 @@ class ArchiveInvalidator {
         return $invalidateForPeriod;
     }
 
+    /**
+     * @param array $idSites
+     * @param $datesByMonth
+     */
+    private function persistInvalidatedArchives(array $idSites, $datesByMonth)
+    {
+        $yearMonths = array_keys($datesByMonth);
+        $yearMonths = array_unique($yearMonths);
+
+        $store = new InvalidatedReports();
+        $store->addInvalidatedSitesToReprocess($idSites);
+        $store->addSitesToPurgeForYearMonths($idSites, $yearMonths);
+    }
+
     private static function getModel()
     {
         return new Model();
     }
+
 } 
\ No newline at end of file
diff --git a/core/DataAccess/ArchivePurger.php b/core/DataAccess/ArchivePurger.php
index fc9254ac3b7cdbe294fe280e3040c64b64da4228..dbdff36c6b67e482b3fc26c8c10a5a9b7fdb6e6c 100644
--- a/core/DataAccess/ArchivePurger.php
+++ b/core/DataAccess/ArchivePurger.php
@@ -16,7 +16,13 @@ use Piwik\Log;
 use Piwik\Piwik;
 
 /**
- * Cleans up outdated archives
+ *
+ * This class purges two types of archives:
+ *
+ * (1) Deletes invalidated archives (from ArchiveInvalidator)
+ *
+ * (2) Deletes outdated archives (the temporary or errored archives)
+ *
  *
  * @package Piwik\DataAccess
  */
@@ -24,35 +30,26 @@ class ArchivePurger
 {
     public static function purgeInvalidatedArchives()
     {
-        $archiveTables = ArchiveTableCreator::getTablesArchivesInstalled();
-
-        foreach ($archiveTables as $archiveTable) {
-            /**
-             * Select the archives that have already been invalidated and have been since re-processed.
-             * It purges records for each distinct { archive name (includes segment hash) , idsite, date, period } tuple.
-             */
-            $result = self::getModel()->getInvalidatedArchiveIdsSafeToDelete($archiveTable);
-
-            if (count($result) > 0) {
-                $archiveIds = array_map(
-                    function ($elm) {
-                        return $elm['idarchive'];
-                    },
-                    $result
-                );
-
-                $date = ArchiveTableCreator::getDateFromTableName($archiveTable);
-                $date = Date::factory(str_replace('_', '-', $date) . '-01');
-
-                self::deleteArchiveIds($date, $archiveIds);
+        $store = new InvalidatedReports();
+        $idSitesByYearMonth = $store->getSitesByYearMonthArchiveToPurge();
+
+        foreach ($idSitesByYearMonth as $yearMonth => $idSites) {
+            if(empty($idSites)) {
+                continue;
             }
 
-        }
-    }
+            $date = Date::factory(str_replace('_', '-', $yearMonth) . '-01');
+            $numericTable = ArchiveTableCreator::getNumericTable($date);
 
-    private static function getModel()
-    {
-        return new Model();
+            $archiveIds = self::getModel()->getInvalidatedArchiveIdsSafeToDelete($numericTable, $idSites);
+
+            if (count($archiveIds) == 0) {
+                continue;
+            }
+            self::deleteArchiveIds($date, $archiveIds);
+
+            $store->markSiteIdsHaveBeenPurged($idSites, $yearMonth);
+        }
     }
 
     /**
@@ -133,4 +130,9 @@ class ArchivePurger
         }
     }
 
+    private static function getModel()
+    {
+        return new Model();
+    }
+
 }
diff --git a/core/DataAccess/InvalidatedReports.php b/core/DataAccess/InvalidatedReports.php
index 5f389b394b4bdd2f761fe435f6350f7fca484fc1..ac5a0ebbee0dc9620715b192a63b83192dc57539 100644
--- a/core/DataAccess/InvalidatedReports.php
+++ b/core/DataAccess/InvalidatedReports.php
@@ -10,7 +10,6 @@
 namespace Piwik\DataAccess;
 
 use Piwik\Option;
-use Piwik\Piwik;
 
 /**
  * Keeps track of which reports were invalidated via CoreAdminHome.invalidateArchivedReports API.
@@ -25,19 +24,62 @@ use Piwik\Piwik;
 class InvalidatedReports
 {
     const OPTION_INVALIDATED_IDSITES_TO_REPROCESS = 'InvalidatedOldReports_WebsiteIds';
+    const OPTION_INVALIDATED_DATES_SITES_TO_PURGE = 'InvalidatedOldReports_DatesWebsiteIds';
+
+    /**
+     * Mark the sites IDs and Dates as being invalidated, so we can purge them later on.
+     *
+     * @param array $idSites
+     * @param array $yearMonths
+     */
+    public function addSitesToPurgeForYearMonths(array $idSites, $yearMonths)
+    {
+        $idSitesByYearMonth = $this->getSitesByYearMonthToPurge();
+        foreach($yearMonths as $yearMonth) {
+            $idSitesByYearMonth[$yearMonth] = $idSites;
+        }
+        $this->persistSitesByYearMonthToPurge($idSitesByYearMonth);
+    }
+
+    /**
+     * Returns the list of websites IDs for which invalidated archives can be purged.
+     */
+    public function getSitesByYearMonthArchiveToPurge()
+    {
+        $idSitesByYearMonth = $this->getSitesByYearMonthToPurge();
+
+        // From this list we remove the websites that are not yet re-processed
+        // so we don't purge them before they were re-processed
+        $idSitesNotYetReprocessed = $this->getSitesToReprocess();
+
+        foreach($idSitesByYearMonth as $yearMonth => &$idSites) {
+            $idSites = array_diff($idSites, $idSitesNotYetReprocessed);
+        }
+        return $idSitesByYearMonth;
+    }
+
+    public function markSiteIdsHaveBeenPurged(array $idSites, $yearMonth)
+    {
+        $idSitesByYearMonth = $this->getSitesByYearMonthToPurge();
+
+        if(!isset($idSitesByYearMonth[$yearMonth])) {
+            return;
+        }
+
+        $idSitesByYearMonth[$yearMonth] = array_diff($idSitesByYearMonth[$yearMonth], $idSites);
+        $this->persistSitesByYearMonthToPurge($idSitesByYearMonth);
+    }
 
     /**
      * Record those website IDs as having been invalidated
      *
      * @param $idSites
      */
-    public function addInvalidatedSitesToReprocess($idSites)
+    public function addInvalidatedSitesToReprocess(array $idSites)
     {
-        $invalidatedIdSites = $this->getSitesToReprocess();
-        $invalidatedIdSites = array_merge($invalidatedIdSites, $idSites);
-        $invalidatedIdSites = array_unique($invalidatedIdSites);
-        $invalidatedIdSites = array_values($invalidatedIdSites);
-        $this->setSitesToReprocess($invalidatedIdSites);
+        $siteIdsToReprocess = $this->getSitesToReprocess();
+        $siteIdsToReprocess = array_merge($siteIdsToReprocess, $idSites);
+        $this->setSitesToReprocess($siteIdsToReprocess);
     }
 
 
@@ -46,13 +88,13 @@ class InvalidatedReports
      */
     public function storeSiteIsReprocessed($idSite)
     {
-        $websiteIdsInvalidated = $this->getSitesToReprocess();
+        $siteIdsToReprocess = $this->getSitesToReprocess();
 
-        if (count($websiteIdsInvalidated)) {
-            $found = array_search($idSite, $websiteIdsInvalidated);
+        if (count($siteIdsToReprocess)) {
+            $found = array_search($idSite, $siteIdsToReprocess);
             if ($found !== false) {
-                unset($websiteIdsInvalidated[$found]);
-                $this->setSitesToReprocess($websiteIdsInvalidated);
+                unset($siteIdsToReprocess[$found]);
+                $this->setSitesToReprocess($siteIdsToReprocess);
             }
         }
     }
@@ -64,25 +106,56 @@ class InvalidatedReports
      */
     public function getSitesToReprocess()
     {
-        Option::clearCachedOption(self::OPTION_INVALIDATED_IDSITES_TO_REPROCESS);
-        $invalidatedIdSites = Option::get(self::OPTION_INVALIDATED_IDSITES_TO_REPROCESS);
+        return $this->getArrayValueFromOptionName(self::OPTION_INVALIDATED_IDSITES_TO_REPROCESS);
+    }
+
+    /**
+     * @return array|false|mixed|string
+     */
+    private function getSitesByYearMonthToPurge()
+    {
+        return $this->getArrayValueFromOptionName(self::OPTION_INVALIDATED_DATES_SITES_TO_PURGE);
+    }
+
+    /**
+     * @param $websiteIdsInvalidated
+     */
+    private function setSitesToReprocess($websiteIdsInvalidated)
+    {
+        $websiteIdsInvalidated = array_unique($websiteIdsInvalidated);
+        $websiteIdsInvalidated = array_values($websiteIdsInvalidated);
+        Option::set(self::OPTION_INVALIDATED_IDSITES_TO_REPROCESS, serialize($websiteIdsInvalidated));
+    }
+
+    /**
+     * @param $optionName
+     * @return array|false|mixed|string
+     */
+    private function getArrayValueFromOptionName($optionName)
+    {
+        Option::clearCachedOption($optionName);
+        $array = Option::get($optionName);
 
-        if ($invalidatedIdSites
-            && ($invalidatedIdSites = unserialize($invalidatedIdSites))
-            && count($invalidatedIdSites)
+        if ($array
+            && ($array = unserialize($array))
+            && count($array)
         ) {
-            return $invalidatedIdSites;
+            return $array;
         }
         return array();
     }
 
     /**
-     * @param $websiteIdsInvalidated
+     * @param $idSitesByYearMonth
      */
-    private function setSitesToReprocess($websiteIdsInvalidated)
+    private function persistSitesByYearMonthToPurge($idSitesByYearMonth)
     {
-        Option::set(self::OPTION_INVALIDATED_IDSITES_TO_REPROCESS, serialize($websiteIdsInvalidated));
+        // remove dates for which there are no sites to purge
+        $idSitesByYearMonth = array_filter($idSitesByYearMonth);
+
+        Option::set(self::OPTION_INVALIDATED_DATES_SITES_TO_PURGE, serialize($idSitesByYearMonth));
     }
 
 
+
 }
\ No newline at end of file
diff --git a/core/DataAccess/Model.php b/core/DataAccess/Model.php
index ee2d8702af2cca2c96ddb18ef1fc4a9ce51f9183..5cc25635af103c37e7be4c2f009ac52b44844ab9 100644
--- a/core/DataAccess/Model.php
+++ b/core/DataAccess/Model.php
@@ -27,26 +27,39 @@ class Model
      *
      * These archives { archive name (includes segment hash) , idsite, date, period } will be deleted.
      *
-     * @param $archiveTable
+     * @param string $archiveTable
+     * @param array $idSites
      * @return array
      * @throws Exception
      */
-    public function getInvalidatedArchiveIdsSafeToDelete($archiveTable)
+    public function getInvalidatedArchiveIdsSafeToDelete($archiveTable, array $idSites)
     {
         // prevent error 'The SELECT would examine more than MAX_JOIN_SIZE rows'
         Db::get()->query('SET SQL_BIG_SELECTS=1');
 
         $query = 'SELECT t1.idarchive FROM `' . $archiveTable . '` t1
                   INNER JOIN `' . $archiveTable . '` t2
-                      ON t1.name = t2.name AND t1.idsite=t2.idsite
-                      AND t1.date1=t2.date1 AND t1.date2=t2.date2 AND t1.period=t2.period
+                      ON t1.name    = t2.name
+                      AND t1.idsite = t2.idsite
+                      AND t1.date1  = t2.date1
+                      AND t1.date2  = t2.date2
+                      AND t1.period = t2.period
                   WHERE t1.value = ' . ArchiveWriter::DONE_INVALIDATED . '
+                  AND t1.idsite IN (' . implode(",", $idSites) . ')
                   AND t2.value IN(' . ArchiveWriter::DONE_OK . ', ' . ArchiveWriter::DONE_OK_TEMPORARY . ')
-                  AND t1.ts_archived < t2.ts_archived AND t1.name LIKE \'done%\'';
+                  AND t1.ts_archived < t2.ts_archived
+                  AND t1.name LIKE \'done%\'
+        ';
 
         $result = Db::fetchAll($query);
 
-        return $result;
+        $archiveIds = array_map(
+            function ($elm) {
+                return $elm['idarchive'];
+            },
+            $result
+        );
+        return $archiveIds;
     }
 
     /**
diff --git a/plugins/Annotations/AnnotationList.php b/plugins/Annotations/AnnotationList.php
index cf068e6f35e889f0d1dcd44ba62b6904a8a4f843..f51a5e86ce600f22160a90155168589608167967 100755
--- a/plugins/Annotations/AnnotationList.php
+++ b/plugins/Annotations/AnnotationList.php
@@ -440,7 +440,7 @@ class AnnotationList
     public static function canUserAddNotesFor($idSite)
     {
         return Piwik::isUserHasViewAccess($idSite)
-        && !Piwik::isUserIsAnonymous($idSite);
+        && !Piwik::isUserIsAnonymous();
     }
 
     /**