diff --git a/core/modules/node/src/Tests/NodeLoadMultipleTest.php b/core/modules/node/src/Tests/NodeLoadMultipleTest.php
index 4add3bd..c758b3d 100644
--- a/core/modules/node/src/Tests/NodeLoadMultipleTest.php
+++ b/core/modules/node/src/Tests/NodeLoadMultipleTest.php
@@ -1,7 +1,6 @@
 <?php
 
 namespace Drupal\node\Tests;
-
 use Drupal\node\Entity\Node;
 
 /**
diff --git a/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php b/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
old mode 100644
new mode 100755
index 8f4384e..34e8c00
--- a/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
+++ b/core/modules/statistics/src/Plugin/Block/StatisticsPopularBlock.php
@@ -4,8 +4,15 @@
 
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Entity\EntityRepositoryInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\node\Entity\Node;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\statistics\StatisticsStorageInterface;
 
 /**
  * Provides a 'Popular content' block.
@@ -15,17 +22,82 @@
  *   admin_label = @Translation("Popular content")
  * )
  */
-class StatisticsPopularBlock extends BlockBase {
+class StatisticsPopularBlock extends BlockBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The entity repository service.
+   *
+   * @var \Drupal\Core\Entity\EntityRepositoryInterface
+   */
+  protected $entityRepository;
+
+  /**
+   * The storage for statistics.
+   *
+   * @var \Drupal\statistics\StatisticsStorageInterface
+   */
+  protected $statisticsStorage;
+
+  /**
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs an StatisticsPopularBlock object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
+   *   The entity repository service
+   * @param \Drupal\statistics\StatisticsStorageInterface $statistics_storage
+   *   The storage for statistics.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository, StatisticsStorageInterface $statistics_storage, RendererInterface $renderer) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->entityTypeManager = $entity_type_manager;
+    $this->entityRepository = $entity_repository;
+    $this->statisticsStorage = $statistics_storage;
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager'),
+      $container->get('entity.repository'),
+      $container->get('statistics.storage'),
+      $container->get('renderer')
+    );
+  }
 
   /**
    * {@inheritdoc}
    */
   public function defaultConfiguration() {
-    return array(
+    return [
       'top_day_num' => 0,
       'top_all_num' => 0,
       'top_last_num' => 0
-    );
+    ];
   }
 
   /**
@@ -40,29 +112,29 @@ protected function blockAccess(AccountInterface $account) {
    */
   public function blockForm($form, FormStateInterface $form_state) {
     // Popular content block settings.
-    $numbers = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40);
-    $numbers = array('0' => $this->t('Disabled')) + array_combine($numbers, $numbers);
-    $form['statistics_block_top_day_num'] = array(
+    $numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 40];
+    $numbers = ['0' => $this->t('Disabled')] + array_combine($numbers, $numbers);
+    $form['statistics_block_top_day_num'] = [
      '#type' => 'select',
      '#title' => $this->t("Number of day's top views to display"),
      '#default_value' => $this->configuration['top_day_num'],
      '#options' => $numbers,
      '#description' => $this->t('How many content items to display in "day" list.'),
-    );
-    $form['statistics_block_top_all_num'] = array(
+    ];
+    $form['statistics_block_top_all_num'] = [
       '#type' => 'select',
       '#title' => $this->t('Number of all time views to display'),
       '#default_value' => $this->configuration['top_all_num'],
       '#options' => $numbers,
       '#description' => $this->t('How many content items to display in "all time" list.'),
-    );
-    $form['statistics_block_top_last_num'] = array(
+    ];
+    $form['statistics_block_top_last_num'] = [
       '#type' => 'select',
       '#title' => $this->t('Number of most recent views to display'),
       '#default_value' => $this->configuration['top_last_num'],
       '#options' => $numbers,
       '#description' => $this->t('How many content items to display in "recently viewed" list.'),
-    );
+    ];
     return $form;
   }
 
@@ -79,31 +151,67 @@ public function blockSubmit($form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function build() {
-    $content = array();
+    $content = [];
 
     if ($this->configuration['top_day_num'] > 0) {
-      $result = statistics_title_list('daycount', $this->configuration['top_day_num']);
+      $result = $this->statisticsStorage->fetchAll('daycount', $this->configuration['top_day_num']);
       if ($result) {
-        $content['top_day'] = node_title_list($result, $this->t("Today's:"));
+        $content['top_day'] = $this->nodeTitleList($result, $this->t("Today's:"));
         $content['top_day']['#suffix'] = '<br />';
       }
     }
 
     if ($this->configuration['top_all_num'] > 0) {
-      $result = statistics_title_list('totalcount', $this->configuration['top_all_num']);
+      $result = $this->statisticsStorage->fetchAll('totalcount', $this->configuration['top_all_num']);
       if ($result) {
-        $content['top_all'] = node_title_list($result, $this->t('All time:'));
+        $content['top_all'] = $this->nodeTitleList($result, $this->t('All time:'));
         $content['top_all']['#suffix'] = '<br />';
       }
     }
 
     if ($this->configuration['top_last_num'] > 0) {
-      $result = statistics_title_list('timestamp', $this->configuration['top_last_num']);
-      $content['top_last'] = node_title_list($result, $this->t('Last viewed:'));
+      $result = $this->statisticsStorage->fetchAll('timestamp', $this->configuration['top_last_num']);
+      $content['top_last'] = $this->nodeTitleList($result, $this->t('Last viewed:'));
       $content['top_last']['#suffix'] = '<br />';
     }
 
     return $content;
   }
 
+  /**
+   * Generates the ordered array of node links for build().
+   *
+   * @param int[] $nids
+   *   An ordered array of node ids.
+   * @param string $title
+   *   The title for the list.
+   *
+   * @return array
+   *   A render array for the list.
+   */
+  protected function nodeTitleList(array $nids, $title) {
+    $nodes = Node::loadMultiple($nids);
+
+    $items = [];
+    foreach ($nids as $nid) {
+      $node = $this->entityRepository->getTranslationFromContext($nodes[$nid]);
+      $item = [
+        '#type' => 'link',
+        '#title' => $node->getTitle(),
+        '#url' => $node->urlInfo('canonical'),
+      ];
+      $this->renderer->addCacheableDependency($item, $node);
+      $items[] = $item;
+    }
+
+    return [
+      '#theme' => 'item_list__node',
+      '#items' => $items,
+      '#title' => $title,
+      '#cache' => [
+        'tags' => $this->entityTypeManager->getDefinition('node')->getListCacheTags(),
+      ],
+    ];
+  }
+
 }
diff --git a/core/modules/statistics/src/StatisticsDatabaseStorage.php b/core/modules/statistics/src/StatisticsDatabaseStorage.php
new file mode 100644
index 0000000..32d0871
--- /dev/null
+++ b/core/modules/statistics/src/StatisticsDatabaseStorage.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\statistics\StatisticsDatabaseStorage.
+ */
+
+namespace Drupal\statistics;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\State\StateInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Provides the default database storage backend for statistics.
+ */
+class StatisticsDatabaseStorage implements StatisticsStorageInterface {
+
+  /**
+  * The database connection used.
+  *
+  * @var \Drupal\Core\Database\Connection
+  */
+  protected $connection;
+
+  /**
+   * The state service.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * The request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
+   * Constructs the statistics storage.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   The database connection for the node view storage.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service.
+   */
+  public function __construct(Connection $connection, StateInterface $state, RequestStack $request_stack) {
+    $this->connection = $connection;
+    $this->state = $state;
+    $this->requestStack = $request_stack;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function recordHit($id) {
+    return (bool) $this->connection
+      ->merge('node_counter')
+      ->key('nid', $id)
+      ->fields([
+        'daycount' => 1,
+        'totalcount' => 1,
+        'timestamp' => $this->getRequestTime(),
+      ])
+      ->expression('daycount', 'daycount + 1')
+      ->expression('totalcount', 'totalcount + 1')
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchViews($id) {
+    $views =  $this->connection
+      ->select('node_counter', 'nc')
+      ->fields('nc', ['totalcount', 'daycount', 'timestamp'])
+      ->condition('nid', $id, '=')
+      ->execute()
+      ->fetchAssoc();
+    return new StatisticsViews($views['totalcount'], $views['daycount'], $views['timestamp']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fetchAll($order = 'totalcount', $limit = 5) {
+    assert(in_array($order, ['totalcount', 'daycount', 'timestamp']), "Invalid order argument.");
+
+    return $this->connection
+      ->select('node_counter', 'nc')
+      ->fields('nc', ['nid'])
+      ->orderBy($order, 'DESC')
+      ->range(0, $limit)
+      ->execute()
+      ->fetchCol();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clean($id) {
+    return (bool) $this->connection
+      ->delete('node_counter')
+      ->condition('nid', $id)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetDayCount() {
+    $statistics_timestamp = $this->state->get('statistics.day_timestamp') ?: 0;
+    if (($this->getRequestTime() - $statistics_timestamp) >= 86400) {
+      $this->state->set('statistics.day_timestamp', $this->getRequestTime());
+      return (bool) $this->connection->update('node_counter')
+        ->fields(['daycount' => 0])
+        ->execute();
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function maxTotalCount() {
+    $query = $this->connection->select('node_counter', 'nc');
+    $query->addExpression('MAX(totalcount)');
+    $max_total_count = (int)$query->execute()->fetchField();
+    $this->state->set('statistics.node_counter_scale', 1.0 / max(1.0, $max_total_count));
+    return $max_total_count;
+  }
+
+  /**
+   * Get current request time.
+   *
+   * @return int
+   *   Unix timestamp for current server request time.
+   */
+  protected function getRequestTime() {
+    return $this->requestStack->getCurrentRequest()->server->get('REQUEST_TIME');
+  }
+
+}
diff --git a/core/modules/statistics/src/StatisticsStorageInterface.php b/core/modules/statistics/src/StatisticsStorageInterface.php
new file mode 100644
index 0000000..b12165b
--- /dev/null
+++ b/core/modules/statistics/src/StatisticsStorageInterface.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\statistics\StatisticsStorageInterface.
+ */
+
+namespace Drupal\statistics;
+
+/**
+ * Provides an interface defining Statistics Storage.
+ *
+ * Stores the views per day, total views and timestamp of last view
+ * for entities.
+ */
+interface StatisticsStorageInterface {
+
+  /**
+   * Count a entity view.
+   *
+   * @param int $id
+   *   The ID of the entity to count.
+   *
+   * @return bool
+   *   TRUE if the entity view has been counted.
+   */
+  public function recordHit($id);
+
+  /**
+   * Returns the number of times a entity has been viewed.
+   *
+   * @param int $id
+   *   The ID of the entity to fetch the views for.
+   *
+   * @return \Drupal\statistics\StatisticsViewsInterface
+   */
+  public function fetchViews($id);
+
+  /**
+   * Returns the number of times a entity has been viewed.
+   *
+   * @param string $order
+   *   The counter name to order by:
+   *   - 'totalcount' The total number of views.
+   *   - 'daycount' The number of views today.
+   *   - 'timestamp' The unix timestamp of the last view.
+   *
+   * @param int $limit
+   *   The number of entity IDs to return.
+   *
+   * @return array
+   *   An ordered array of entity IDs.
+   */
+  public function fetchAll($order = 'totalcount', $limit = 5);
+
+  /**
+   * Delete counts for a specific entity.
+   *
+   * @param int $id
+   *   The ID of the entity which views to delete.
+   *
+   * @return bool
+   *   TRUE if the entity views have been deleted.
+   */
+  public function clean($id);
+
+  /**
+   * Resets the day count for all entities.
+   *
+   * @return bool
+   *  TRUE if the day count is reset.
+   */
+  public function resetDayCount();
+
+  /**
+   * Returns the highest 'totalcount' value.
+   *
+   * @return int
+   *   The highest 'totalcount' value.
+   */
+  public function maxTotalCount();
+
+}
diff --git a/core/modules/statistics/src/StatisticsViews.php b/core/modules/statistics/src/StatisticsViews.php
new file mode 100644
index 0000000..e107955
--- /dev/null
+++ b/core/modules/statistics/src/StatisticsViews.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\statistics;
+
+class StatisticsViews implements StatisticsViewsInterface {
+
+  /**
+   * @var int
+   */
+  protected $totalCount;
+
+  /**
+   * @var int
+   */
+  protected $dayCount;
+
+  /**
+   * @var int
+   */
+  protected $timestamp;
+
+  public function __construct($total_count, $day_count, $timestamp) {
+    $this->totalCount = $total_count;
+    $this->dayCount = $day_count;
+    $this->timestamp = $timestamp;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTotalCount() {
+    return $this->totalCount;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDayCount() {
+    return $this->dayCount;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTimestamp() {
+    return $this->timestamp;
+  }
+}
diff --git a/core/modules/statistics/src/StatisticsViewsInterface.php b/core/modules/statistics/src/StatisticsViewsInterface.php
new file mode 100644
index 0000000..2c72184
--- /dev/null
+++ b/core/modules/statistics/src/StatisticsViewsInterface.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\statistics;
+
+interface StatisticsViewsInterface {
+
+  /**
+   * Total number of times the entity has been viewed.
+   *
+   * @return int
+   */
+  public function getTotalCount();
+
+  /**
+   * Total number of times the entity has been viewed "today".
+   *
+   * @return int
+   */
+  public function getDayCount();
+
+  /**
+   * Timestamp of when the entity was last viewed.
+   *
+   * @return int
+   */
+  public function getTimestamp();
+
+}
diff --git a/core/modules/statistics/src/Tests/StatisticsAdminTest.php b/core/modules/statistics/src/Tests/StatisticsAdminTest.php
index 565925c..a8bdba7 100644
--- a/core/modules/statistics/src/Tests/StatisticsAdminTest.php
+++ b/core/modules/statistics/src/Tests/StatisticsAdminTest.php
@@ -73,7 +73,7 @@ function testStatisticsSettings() {
     $this->drupalGet('node/' . $this->testNode->id());
     // Manually calling statistics.php, simulating ajax behavior.
     $nid = $this->testNode->id();
-    $post = array('nid' => $nid);
+    $post = array('id' => $nid);
     global $base_url;
     $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics') . '/statistics.php';
     $this->client->post($stats_path, array('form_params' => $post));
@@ -108,7 +108,7 @@ function testDeleteNode() {
     $this->drupalGet('node/' . $this->testNode->id());
     // Manually calling statistics.php, simulating ajax behavior.
     $nid = $this->testNode->id();
-    $post = array('nid' => $nid);
+    $post = array('id' => $nid);
     global $base_url;
     $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics') . '/statistics.php';
     $this->client->post($stats_path, array('form_params' => $post));
@@ -142,7 +142,7 @@ function testExpiredLogs() {
     $this->drupalGet('node/' . $this->testNode->id());
     // Manually calling statistics.php, simulating ajax behavior.
     $nid = $this->testNode->id();
-    $post = array('nid' => $nid);
+    $post = array('id' => $nid);
     global $base_url;
     $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics') . '/statistics.php';
     $this->client->post($stats_path, array('form_params' => $post));
diff --git a/core/modules/statistics/src/Tests/StatisticsLoggingTest.php b/core/modules/statistics/src/Tests/StatisticsLoggingTest.php
index 7cfec7c..81c917f 100644
--- a/core/modules/statistics/src/Tests/StatisticsLoggingTest.php
+++ b/core/modules/statistics/src/Tests/StatisticsLoggingTest.php
@@ -113,17 +113,17 @@ function testLogging() {
     $this->drupalGet($path);
     $settings = $this->getDrupalSettings();
     $this->assertPattern($expected_library, 'Found statistics library JS on node page.');
-    $this->assertIdentical($this->node->id(), $settings['statistics']['data']['nid'], 'Found statistics settings on node page.');
+    $this->assertIdentical($this->node->id(), $settings['statistics']['data']['id'], 'Found statistics settings on node page.');
 
     // Verify the same when loading the site in a non-default language.
     $this->drupalGet($this->language['langcode'] . '/' . $path);
     $settings = $this->getDrupalSettings();
     $this->assertPattern($expected_library, 'Found statistics library JS on a valid node page in a non-default language.');
-    $this->assertIdentical($this->node->id(), $settings['statistics']['data']['nid'], 'Found statistics settings on valid node page in a non-default language.');
+    $this->assertIdentical($this->node->id(), $settings['statistics']['data']['id'], 'Found statistics settings on valid node page in a non-default language.');
 
     // Manually call statistics.php to simulate ajax data collection behavior.
     global $base_root;
-    $post = array('nid' => $this->node->id());
+    $post = array('id' => $this->node->id());
     $this->client->post($base_root . $stats_path, array('form_params' => $post));
     $node_counter = statistics_get($this->node->id());
     $this->assertIdentical($node_counter['totalcount'], '1');
diff --git a/core/modules/statistics/src/Tests/StatisticsReportsTest.php b/core/modules/statistics/src/Tests/StatisticsReportsTest.php
index 9c0d26c..ada5e10 100644
--- a/core/modules/statistics/src/Tests/StatisticsReportsTest.php
+++ b/core/modules/statistics/src/Tests/StatisticsReportsTest.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\statistics\Tests;
 
+use Drupal\Core\Cache\Cache;
+use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
+
 /**
  * Tests display of statistics report blocks.
  *
@@ -9,6 +12,8 @@
  */
 class StatisticsReportsTest extends StatisticsTestBase {
 
+  use AssertPageCacheContextsAndTagsTrait;
+
   /**
    * Tests the "popular content" block.
    */
@@ -21,7 +26,7 @@ function testPopularContentBlock() {
     $this->drupalGet('node/' . $node->id());
     // Manually calling statistics.php, simulating ajax behavior.
     $nid = $node->id();
-    $post = http_build_query(array('nid' => $nid));
+    $post = http_build_query(array('id' => $nid));
     $headers = array('Content-Type' => 'application/x-www-form-urlencoded');
     global $base_url;
     $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics') . '/statistics.php';
@@ -30,7 +35,7 @@ function testPopularContentBlock() {
     $client->post($stats_path, array('headers' => $headers, 'body' => $post));
 
     // Configure and save the block.
-    $this->drupalPlaceBlock('statistics_popular_block', array(
+    $block = $this->drupalPlaceBlock('statistics_popular_block', array(
       'label' => 'Popular content',
       'top_day_num' => 3,
       'top_all_num' => 3,
@@ -44,9 +49,16 @@ function testPopularContentBlock() {
     $this->assertText('All time', 'Found the all time popular content.');
     $this->assertText('Last viewed', 'Found the last viewed popular content.');
 
-    // statistics.module doesn't use node entities, prevent the node language
-    // from being added to the options.
-    $this->assertRaw(\Drupal::l($node->label(), $node->urlInfo('canonical', ['language' => NULL])), 'Found link to visited node.');
+    $tags = Cache::mergeTags($node->getCacheTags(), $block->getCacheTags());
+    $tags = Cache::mergeTags($tags, $this->blockingUser->getCacheTags());
+    $tags = Cache::mergeTags($tags, ['block_view', 'config:block_list', 'node_list', 'rendered', 'user_view']);
+    $this->assertCacheTags($tags);
+    $contexts = Cache::mergeContexts($node->getCacheContexts(), $block->getCacheContexts());
+    $contexts = Cache::mergeContexts($contexts, ['url.query_args:_wrapper_format']);
+    $this->assertCacheContexts($contexts);
+
+    // Check if the node link is displayed.
+    $this->assertRaw(\Drupal::l($node->label(), $node->urlInfo('canonical')), 'Found link to visited node.');
   }
 
 }
diff --git a/core/modules/statistics/src/Tests/StatisticsTokenReplaceTest.php b/core/modules/statistics/src/Tests/StatisticsTokenReplaceTest.php
index b7d22b8..2a0ce86 100644
--- a/core/modules/statistics/src/Tests/StatisticsTokenReplaceTest.php
+++ b/core/modules/statistics/src/Tests/StatisticsTokenReplaceTest.php
@@ -24,7 +24,7 @@ function testStatisticsTokenReplacement() {
     $this->drupalGet('node/' . $node->id());
     // Manually calling statistics.php, simulating ajax behavior.
     $nid = $node->id();
-    $post = http_build_query(array('nid' => $nid));
+    $post = http_build_query(array('id' => $nid));
     $headers = array('Content-Type' => 'application/x-www-form-urlencoded');
     global $base_url;
     $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics') . '/statistics.php';
diff --git a/core/modules/statistics/src/Tests/Views/IntegrationTest.php b/core/modules/statistics/src/Tests/Views/IntegrationTest.php
index 4882fd2..26b2b68 100644
--- a/core/modules/statistics/src/Tests/Views/IntegrationTest.php
+++ b/core/modules/statistics/src/Tests/Views/IntegrationTest.php
@@ -72,7 +72,7 @@ public function testNodeCounterIntegration() {
     global $base_url;
     $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics') . '/statistics.php';
     $client = \Drupal::service('http_client_factory')->fromOptions(['config/curl', array(CURLOPT_TIMEOUT => 10)]);
-    $client->post($stats_path, array('form_params' => array('nid' => $this->node->id())));
+    $client->post($stats_path, array('form_params' => array('id' => $this->node->id())));
     $this->drupalGet('test_statistics_integration');
 
     $expected = statistics_get($this->node->id());
diff --git a/core/modules/statistics/statistics.module b/core/modules/statistics/statistics.module
index 5079e43..fe55853 100644
--- a/core/modules/statistics/statistics.module
+++ b/core/modules/statistics/statistics.module
@@ -40,7 +40,7 @@ function statistics_help($route_name, RouteMatchInterface $route_match) {
 function statistics_node_view(array &$build, EntityInterface $node, EntityViewDisplayInterface $display, $view_mode) {
   if (!$node->isNew() && $view_mode == 'full' && node_is_page($node) && empty($node->in_preview)) {
     $build['#attached']['library'][] = 'statistics/drupal.statistics';
-    $settings = array('data' => array('nid' => $node->id()), 'url' => Url::fromUri('base:' . drupal_get_path('module', 'statistics') . '/statistics.php')->toString());
+    $settings = ['data' => ['id' => $node->id()], 'url' => Url::fromUri('base:' . drupal_get_path('module', 'statistics') . '/statistics.php')->toString()];
     $build['#attached']['drupalSettings']['statistics'] = $settings;
   }
 }
@@ -52,14 +52,14 @@ function statistics_node_links_alter(array &$links, NodeInterface $entity, array
   if ($context['view_mode'] != 'rss') {
     $links['#cache']['contexts'][] = 'user.permissions';
     if (\Drupal::currentUser()->hasPermission('view post access counter')) {
-      $statistics = statistics_get($entity->id());
-      if ($statistics) {
-        $statistics_links['statistics_counter']['title'] = \Drupal::translation()->formatPlural($statistics['totalcount'], '1 view', '@count views');
-        $links['statistics'] = array(
+      $statistics = \Drupal::service('statistics.storage')->fetchViews($entity->id());
+      if ($statistics instanceof \Drupal\statistics\StatisticsViewsInterface) {
+        $statistics_links['statistics_counter']['title'] = \Drupal::translation()->formatPlural($statistics->getTotalCount(), '1 view', '@count views');
+        $links['statistics'] = [
           '#theme' => 'links__node__statistics',
           '#links' => $statistics_links,
           '#attributes' => array('class' => array('links', 'inline')),
-        );
+        ];
       }
       $links['#cache']['max-age'] = \Drupal::config('statistics.settings')->get('display_max_age');
     }
@@ -70,79 +70,26 @@ function statistics_node_links_alter(array &$links, NodeInterface $entity, array
  * Implements hook_cron().
  */
 function statistics_cron() {
-  $statistics_timestamp = \Drupal::state()->get('statistics.day_timestamp') ?: 0;
-
-  if ((REQUEST_TIME - $statistics_timestamp) >= 86400) {
-    // Reset day counts.
-    db_update('node_counter')
-      ->fields(array('daycount' => 0))
-      ->execute();
-    \Drupal::state()->set('statistics.day_timestamp', REQUEST_TIME);
-  }
-
-  // Calculate the maximum of node views, for node search ranking.
-  \Drupal::state()->set('statistics.node_counter_scale', 1.0 / max(1.0, db_query('SELECT MAX(totalcount) FROM {node_counter}')->fetchField()));
-}
-
-/**
- * Returns the most viewed content of all time, today, or the last-viewed node.
- *
- * @param string $dbfield
- *   The database field to use, one of:
- *   - 'totalcount': Integer that shows the top viewed content of all time.
- *   - 'daycount': Integer that shows the top viewed content for today.
- *   - 'timestamp': Integer that shows only the last viewed node.
- * @param int $dbrows
- *   The number of rows to be returned.
- *
- * @return SelectQuery|FALSE
- *   A query result containing the node ID, title, user ID that owns the node,
- *   and the username for the selected node(s), or FALSE if the query could not
- *   be executed correctly.
- */
-function statistics_title_list($dbfield, $dbrows) {
-  if (in_array($dbfield, array('totalcount', 'daycount', 'timestamp'))) {
-    $query = db_select('node_field_data', 'n');
-    $query->addTag('node_access');
-    $query->join('node_counter', 's', 'n.nid = s.nid');
-    $query->join('users_field_data', 'u', 'n.uid = u.uid');
-
-    return $query
-      ->fields('n', array('nid', 'title'))
-      ->fields('u', array('uid', 'name'))
-      ->condition($dbfield, 0, '<>')
-      ->condition('n.status', 1)
-      // @todo This should be actually filtering on the desired node status
-      //   field language and just fall back to the default language.
-      ->condition('n.default_langcode', 1)
-      ->condition('u.default_langcode', 1)
-      ->orderBy($dbfield, 'DESC')
-      ->range(0, $dbrows)
-      ->execute();
-  }
-  return FALSE;
+  $storage = \Drupal::service('statistics.storage');
+  $storage->resetDayCount();
+  $storage->maxTotalCount();
 }
 
-
 /**
  * Retrieves a node's "view statistics".
  *
- * @param int $nid
- *   The node ID.
- *
- * @return array
- *   An associative array containing:
- *   - totalcount: Integer for the total number of times the node has been
- *     viewed.
- *   - daycount: Integer for the total number of times the node has been viewed
- *     "today". For the daycount to be reset, cron must be enabled.
- *   - timestamp: Integer for the timestamp of when the node was last viewed.
+ * @deprecated in Drupal 8.2.x, will be removed before Drupal 9.0.0.
+ *   Use \Drupal::service('statistics.storage')->fetchViews($id).
  */
-function statistics_get($nid) {
-
-  if ($nid > 0) {
-    // Retrieve an array with both totalcount and daycount.
-    return db_query('SELECT totalcount, daycount, timestamp FROM {node_counter} WHERE nid = :nid', array(':nid' => $nid), array('target' => 'replica'))->fetchAssoc();
+function statistics_get($id) {
+  if ($id > 0) {
+    /** @var \Drupal\statistics\StatisticsViewsInterface $statistics */
+    $statistics = \Drupal::service('statistics.storage')->fetchViews($id);
+    return [
+      'totalcount' => $statistics->getTotalCount(),
+      'daycount' => $statistics->getDayCount(),
+      'timestamp' => $statistics->getTimestamp(),
+    ];
   }
 }
 
@@ -151,9 +98,8 @@ function statistics_get($nid) {
  */
 function statistics_node_predelete(EntityInterface $node) {
   // Clean up statistics table when node is deleted.
-  db_delete('node_counter')
-    ->condition('nid', $node->id())
-    ->execute();
+  $id = $node->id();
+  return \Drupal::service('statistics.storage')->clean($id);
 }
 
 /**
@@ -161,15 +107,15 @@ function statistics_node_predelete(EntityInterface $node) {
  */
 function statistics_ranking() {
   if (\Drupal::config('statistics.settings')->get('count_content_views')) {
-    return array(
-      'views' => array(
+    return [
+      'views' => [
         'title' => t('Number of views'),
-        'join' => array(
+        'join' => [
           'type' => 'LEFT',
           'table' => 'node_counter',
           'alias' => 'node_counter',
           'on' => 'node_counter.nid = i.sid',
-        ),
+        ],
         // Inverse law that maps the highest view count on the site to 1 and 0
         // to 0. Note that the ROUND here is necessary for PostgreSQL and SQLite
         // in order to ensure that the :statistics_scale argument is treated as
@@ -177,9 +123,9 @@ function statistics_ranking() {
         // values in as strings instead of numbers in complex expressions like
         // this.
         'score' => '2.0 - 2.0 / (1.0 + node_counter.totalcount * (ROUND(:statistics_scale, 4)))',
-        'arguments' => array(':statistics_scale' => \Drupal::state()->get('statistics.node_counter_scale') ?: 0),
-      ),
-    );
+        'arguments' => [':statistics_scale' => \Drupal::state()->get('statistics.node_counter_scale') ?: 0],
+      ],
+    ];
   }
 }
 
diff --git a/core/modules/statistics/statistics.php b/core/modules/statistics/statistics.php
index a79af5f..9b861e8 100644
--- a/core/modules/statistics/statistics.php
+++ b/core/modules/statistics/statistics.php
@@ -14,24 +14,17 @@
 
 $kernel = DrupalKernel::createFromRequest(Request::createFromGlobals(), $autoloader, 'prod');
 $kernel->boot();
+$container = $kernel->getContainer();
 
-$views = $kernel->getContainer()
+$views = $container
   ->get('config.factory')
   ->get('statistics.settings')
   ->get('count_content_views');
 
 if ($views) {
-  $nid = filter_input(INPUT_POST, 'nid', FILTER_VALIDATE_INT);
-  if ($nid) {
-    \Drupal::database()->merge('node_counter')
-      ->key('nid', $nid)
-      ->fields(array(
-        'daycount' => 1,
-        'totalcount' => 1,
-        'timestamp' => REQUEST_TIME,
-      ))
-      ->expression('daycount', 'daycount + 1')
-      ->expression('totalcount', 'totalcount + 1')
-      ->execute();
+  $id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT);
+  if ($id) {
+    $container->get('request_stack')->push(Request::createFromGlobals());
+    $container->get('statistics.storage')->recordHit($id);
   }
 }
diff --git a/core/modules/statistics/statistics.services.yml b/core/modules/statistics/statistics.services.yml
new file mode 100644
index 0000000..b034780
--- /dev/null
+++ b/core/modules/statistics/statistics.services.yml
@@ -0,0 +1,6 @@
+services:
+  statistics.storage:
+    class: Drupal\statistics\StatisticsDatabaseStorage
+    arguments: ['@database', '@state', '@request_stack']
+    tags:
+      - { name: backend_overridable }
