diff --git a/core/modules/comment/comment.admin.inc b/core/modules/comment/comment.admin.inc
new file mode 100644
index 0000000..e2dcdf8
--- /dev/null
+++ b/core/modules/comment/comment.admin.inc
@@ -0,0 +1,225 @@
+<?php
+
+/**
+ * @file
+ * Admin page callbacks for the Comment module.
+ */
+
+use Drupal\comment\CommentInterface;
+use Drupal\Core\Cache\Cache;
+
+/**
+ * Page callback: Presents an administrative comment listing.
+ *
+ * @param $type
+ *   The type of the overview form ('approval' or 'new'). See
+ *   comment_admin_overview() for details.
+ *
+ * @return mixed
+ *
+ * @see comment_menu()
+ * @see comment_multiple_delete_confirm()
+ *
+ * @deprecated Use \Drupal\comment\Controller\CommentController::adminPage()
+ */
+function comment_admin($type = 'new') {
+  $request = \Drupal::request();
+  $edit = $request->request->all();
+
+  if (isset($edit['operation']) && ($edit['operation'] == 'delete') && isset($edit['comments']) && $edit['comments']) {
+    return drupal_get_form('Drupal\comment\Form\ConfirmDeleteMultiple');
+  }
+  else {
+    return drupal_get_form('comment_admin_overview', $type);
+  }
+}
+
+/**
+ * Form constructor for the comment overview administration form.
+ *
+ * @param $arg
+ *   The type of overview form ('approval' or 'new').
+ *
+ * @return
+ * @ingroup forms
+ * @see comment_admin()
+ * @see comment_admin_overview_validate()
+ * @see comment_admin_overview_submit()
+ * @see theme_comment_admin_overview()
+ */
+function comment_admin_overview($form, &$form_state, $arg) {
+  // Build an 'Update options' form.
+  $form['options'] = array(
+    '#type' => 'details',
+    '#title' => t('Update options'),
+    '#attributes' => array('class' => array('container-inline')),
+  );
+
+  if ($arg == 'approval') {
+    $options['publish'] = t('Publish the selected comments');
+  }
+  else {
+    $options['unpublish'] = t('Unpublish the selected comments');
+  }
+  $options['delete'] = t('Delete the selected comments');
+
+  $form['options']['operation'] = array(
+    '#type' => 'select',
+    '#title' => t('Action'),
+    '#title_display' => 'invisible',
+    '#options' => $options,
+    '#default_value' => 'publish',
+  );
+  $form['options']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Update'),
+  );
+
+  // Load the comments that need to be displayed.
+  $status = ($arg == 'approval') ? CommentInterface::NOT_PUBLISHED : CommentInterface::PUBLISHED;
+  $header = array(
+    'subject' => array('data' => t('Subject'), 'field' => 'subject'),
+    'author' => array('data' => t('Author'), 'field' => 'name', 'class' => array(RESPONSIVE_PRIORITY_MEDIUM)),
+    'posted_in' => array('data' => t('Posted in'), 'field' => 'node_title', 'class' => array(RESPONSIVE_PRIORITY_LOW)),
+    'changed' => array('data' => t('Updated'), 'field' => 'c.changed', 'sort' => 'desc', 'class' => array(RESPONSIVE_PRIORITY_LOW)),
+    'operations' => t('Operations'),
+  );
+
+  $result = \Drupal::entityManager()->getStorageController('comment')->getCommentOverview($header, $status);
+
+  $cids = array();
+  $entity_ids = array();
+  $entities = array();
+
+  // We collect entities grouped by entity_type so we can load them and use
+  // their labels.
+  foreach ($result as $row) {
+    $entity_ids[$row->entity_type][] = $row->entity_id;
+    $cids[] = $row->cid;
+  }
+  // Ensure all entities are statically cached so that we do not have to load
+  // them individually when getting their labels below.
+  foreach ($entity_ids as $entity_type => $ids) {
+    $entities[$entity_type] = entity_load_multiple($entity_type, $ids);
+  }
+  $comments = entity_load_multiple('comment', $cids);
+
+  // Build a table listing the appropriate comments.
+  $options = array();
+  $destination = drupal_get_destination();
+
+  foreach ($comments as $comment) {
+    // Use the first entity label.
+    $entity = $entities[$comment->entity_type->value][$comment->entity_id->value];
+    $entity_uri = $entity->uri();
+    // Remove the first node title from the node_titles array and attach to
+    // the comment.
+    $body = '';
+    if (!empty($comment->comment_body->value)) {
+      $body = $comment->comment_body->value;
+    }
+    $options[$comment->id()] = array(
+      'title' => array('data' => array('#title' => $comment->subject->value ?: $comment->id())),
+      'subject' => array(
+        'data' => array(
+          '#type' => 'link',
+          '#title' => $comment->subject->value,
+          '#href' => 'comment/' . $comment->id(),
+          '#options' => array('attributes' => array('title' => truncate_utf8($body, 128)), 'fragment' => 'comment-' . $comment->id()),
+        ),
+      ),
+      'author' => array(
+        'data' => array(
+          '#theme' => 'username',
+          '#account' => comment_prepare_author($comment),
+        ),
+      ),
+      'posted_in' => array(
+        'data' => array(
+          '#type' => 'link',
+          '#title' => $entity->label(),
+          '#href' => $entity_uri['path'],
+          '#options' => $entity_uri['options'],
+          '#access' => $entity->access('view'),
+        ),
+      ),
+      'changed' => format_date($comment->changed->value, 'short'),
+    );
+    $links = array();
+    $links['edit'] = array(
+      'title' => t('Edit'),
+      'route_name' => 'comment.edit_page',
+      'route_parameters' => array('comment' => $comment->id()),
+      'query' => $destination,
+    );
+    if (module_invoke('content_translation', 'translate_access', $comment)) {
+      $links['translate'] = array(
+        'title' => t('Translate'),
+        'route_name' => 'content_translation.translation_overview_comment',
+        'route_parameters' => array('comment' => $comment->id()),
+        'query' => $destination,
+      );
+    }
+    $options[$comment->id()]['operations']['data'] = array(
+      '#type' => 'operations',
+      '#links' => $links,
+    );
+  }
+
+  $form['comments'] = array(
+    '#type' => 'tableselect',
+    '#header' => $header,
+    '#options' => $options,
+    '#empty' => t('No comments available.'),
+  );
+
+  $form['pager'] = array('#theme' => 'pager');
+
+  return $form;
+}
+
+/**
+ * Form validation handler for comment_admin_overview().
+ *
+ * @see comment_admin_overview_submit()
+ */
+function comment_admin_overview_validate($form, &$form_state) {
+  $form_state['values']['comments'] = array_diff($form_state['values']['comments'], array(0));
+  // We can't execute any 'Update options' if no comments were selected.
+  if (count($form_state['values']['comments']) == 0) {
+    form_set_error('', $form_state, t('Select one or more comments to perform the update on.'));
+  }
+}
+
+/**
+ * Form submission handler for comment_admin_overview().
+ *
+ * Executes the chosen 'Update option' on the selected comments, such as
+ * publishing, unpublishing or deleting.
+ *
+ * @see comment_admin_overview_validate()
+ */
+function comment_admin_overview_submit($form, &$form_state) {
+  $operation = $form_state['values']['operation'];
+  $cids = $form_state['values']['comments'];
+
+  if ($operation == 'delete') {
+    entity_delete_multiple('comment', $cids);
+  }
+  else {
+    foreach ($cids as $value) {
+      $comment = comment_load($value);
+
+      if ($operation == 'unpublish') {
+        $comment->status->value = CommentInterface::NOT_PUBLISHED;
+      }
+      elseif ($operation == 'publish') {
+        $comment->status->value = CommentInterface::PUBLISHED;
+      }
+      $comment->save();
+    }
+  }
+  drupal_set_message(t('The update has been performed.'));
+  $form_state['redirect_route']['route_name'] = 'comment.admin';
+  Cache::invalidateTags(array('content' => TRUE));
+}
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index bfdbb2b..a4c15b5 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -213,9 +213,7 @@ function comment_menu_links_defaults_alter(&$links) {
  * @todo Move to the comment manager and replace by a entity query?
  */
 function comment_count_unpublished() {
-  $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE status = :status', array(
-    ':status' => CommentInterface::NOT_PUBLISHED,
-  ))->fetchField();
+  $count = \Drupal::entityQuery('comment')->condition('status', CommentInterface::NOT_PUBLISHED)->count()->execute();
   return t('Unapproved comments (@count)', array('@count' => $count));
 }
 
@@ -279,10 +277,10 @@ function comment_field_config_delete(FieldConfigInterface $field) {
 function comment_field_instance_config_delete(FieldInstanceConfigInterface $instance) {
   if ($instance->getType() == 'comment') {
     // Delete all comments that used by the entity bundle.
-    $comments = db_query("SELECT cid FROM {comment} WHERE entity_type = :entity_type AND field_id = :field_id", array(
-      ':entity_type' => $instance->getEntityTypeId(),
-      ':field_id' => $instance->getEntityTypeId() . '__' . $instance->getName(),
-    ))->fetchCol();
+    $comments = \Drupal::entityQuery('comment')
+      ->condition('field_id', $instance->getEntityTypeId() . '__' . $instance->getName())
+      ->condition('entity_type', $instance->getEntityTypeId())
+      ->execute();
     entity_delete_multiple('comment', $comments);
     \Drupal::cache()->delete('comment_entity_info');
   }
@@ -312,6 +310,20 @@ function comment_permission() {
 }
 
 /**
+ * Finds the most recent comments that are available to the current user.
+ *
+ * @param integer $number
+ *   (optional) The maximum number of comments to find. Defaults to 10.
+ *
+ * @return
+ *   An array of comment objects or an empty array if there are no recent
+ *   comments visible to the current user.
+ */
+function comment_get_recent($number = 10) {
+  return \Drupal::entityManager()->getStorageController('comment')->loadRecent($number);
+}
+
+/**
  * Calculates the page number for the first new comment.
  *
  * @param int $num_comments
@@ -342,46 +354,8 @@ function comment_new_page_count($num_comments, $new_replies, EntityInterface $en
     $pageno = $count / $comments_per_page;
   }
   else {
-    // Threaded comments: we build a query with a subquery to find the first
-    // thread with a new comment.
-
-    // 1. Find all the threads with a new comment.
-    $unread_threads_query = db_select('comment')
-      ->fields('comment', array('thread'))
-      ->condition('entity_id', $entity->id())
-      ->condition('entity_type', $entity->getEntityTypeId())
-      ->condition('field_id', $entity->getEntityTypeId() . '__' . $field_name)
-      ->condition('status', CommentInterface::PUBLISHED)
-      ->orderBy('created', 'DESC')
-      ->orderBy('cid', 'DESC')
-      ->range(0, $new_replies);
-
-    // 2. Find the first thread.
-    $first_thread_query = db_select($unread_threads_query, 'thread');
-    $first_thread_query->addExpression('SUBSTRING(thread, 1, (LENGTH(thread) - 1))', 'torder');
-    $first_thread = $first_thread_query
-      ->fields('thread', array('thread'))
-      ->orderBy('torder')
-      ->range(0, 1)
-      ->execute()
-      ->fetchField();
-
-    // Remove the final '/'.
-    $first_thread = substr($first_thread, 0, -1);
-
-    // Find the number of the first comment of the first unread thread.
-    $count = db_query('SELECT COUNT(*) FROM {comment} WHERE entity_id = :entity_id
-                      AND entity_type = :entity_type
-                      AND field_id = :field_id
-                      AND status = :status AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread', array(
-      ':status' => CommentInterface::PUBLISHED,
-      ':entity_id' => $entity->id(),
-      ':field_id' => $entity->getEntityTypeId() . '__' . $field_name,
-      ':entity_type' => $entity->getEntityTypeId(),
-      ':thread' => $first_thread,
-    ))->fetchField();
-
-    $pageno = $count / $comments_per_page;
+    // Threaded comments.
+    $pageno = \Drupal::entityManager()->getStorageController('comment')->getThreadedNewCommentPage($entity, $new_replies, $comments_per_page, $field_name);
   }
 
   if ($pageno >= 1) {
@@ -408,6 +382,25 @@ function comment_entity_view_alter(&$build, EntityInterface $entity, EntityViewD
   }
 }
 
+function theme_comment_block($variables) {
+  $items = array();
+  $number = $variables['number'];
+  foreach (comment_get_recent($number) as $comment) {
+    $items[] = l($comment->subject->value, 'comment/' . $comment->id(), array('fragment' => 'comment-' . $comment->id())) . '&nbsp;<span>' . t('@time ago', array('@time' => format_interval(REQUEST_TIME - $comment->getChangedTime()))) . '</span>';
+  }
+
+  if ($items) {
+    $item_list = array(
+      '#theme' => 'item_list',
+      '#items' => $items,
+    );
+    return drupal_render($item_list);
+  }
+  else {
+    return t('No comments available.');
+  }
+}
+
 /**
  * Implements hook_entity_view().
  */
@@ -667,53 +660,8 @@ function comment_add(EntityInterface $entity, $field_name = 'comment', $pid = NU
  * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
  * to consider the trailing "/" so we use a substring only.
  */
-function comment_get_thread(EntityInterface $entity, $field_name, $mode, $comments_per_page, $pager_id = 0) {
-  $query = db_select('comment', 'c')
-    ->extend('Drupal\Core\Database\Query\PagerSelectExtender');
-  if ($pager_id) {
-    $query->element($pager_id);
-  }
-  $query->addField('c', 'cid');
-  $query
-    ->condition('c.entity_id', $entity->id())
-    ->condition('c.entity_type', $entity->getEntityTypeId())
-    ->condition('c.field_id', $entity->getEntityTypeId() . '__' . $field_name)
-    ->addTag('entity_access')
-    ->addTag('comment_filter')
-    ->addMetaData('base_table', 'comment')
-    ->addMetaData('entity', $entity)
-    ->addMetaData('field_name', $field_name)
-    ->limit($comments_per_page);
-
-  $count_query = db_select('comment', 'c');
-  $count_query->addExpression('COUNT(*)');
-  $count_query
-    ->condition('c.entity_id', $entity->id())
-    ->condition('c.entity_type', $entity->getEntityTypeId())
-    ->condition('c.field_id', $entity->getEntityTypeId() . '__' . $field_name)
-    ->addTag('entity_access')
-    ->addTag('comment_filter')
-    ->addMetaData('base_table', 'comment')
-    ->addMetaData('entity', $entity)
-    ->addMetaData('field_name', $field_name);
-
-  if (!user_access('administer comments')) {
-    $query->condition('c.status', CommentInterface::PUBLISHED);
-    $count_query->condition('c.status', CommentInterface::PUBLISHED);
-  }
-  if ($mode == COMMENT_MODE_FLAT) {
-    $query->orderBy('c.cid', 'ASC');
-  }
-  else {
-    // See comment above. Analysis reveals that this doesn't cost too
-    // much. It scales much much better than having the whole comment
-    // structure.
-    $query->addExpression('SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))', 'torder');
-    $query->orderBy('torder', 'ASC');
-  }
-
-  $query->setCountQuery($count_query);
-  return $query->execute()->fetchCol();
+function comment_get_thread(EntityInterface $entity, $field_name, $mode, $comments_per_page) {
+  return \Drupal::entityManager()->getStorageController('comment')->loadThread($entity, $field_name, $mode, $comments_per_page);
 }
 
 /**
@@ -880,11 +828,7 @@ function comment_entity_load($entities, $entity_type) {
   }
   // Load comment information from the database and update the entity's comment
   // statistics properties, which are defined on each CommentItem field.
-  $result = db_select('comment_entity_statistics', 'ces')
-    ->fields('ces')
-    ->condition('ces.entity_id', array_keys($entities))
-    ->condition('ces.entity_type', $entity_type)
-    ->execute();
+  $result = \Drupal::entityManager()->getStorageController('comment')->getEntityCommentStatistics(array_keys($entities), $entity_type);
   foreach ($result as $record) {
     $parts = explode('__', $record->field_id, 2);
     list(, $field_name) = $parts;
@@ -910,54 +854,7 @@ function comment_entity_insert(EntityInterface $entity) {
   // maintenance of the {comment_entity_statistics} table.
   if (\Drupal::state()->get('comment.maintain_entity_statistics') &&
     $fields = \Drupal::service('comment.manager')->getFields($entity->getEntityTypeId())) {
-    $query = db_insert('comment_entity_statistics')
-     ->fields(array(
-      'entity_id',
-      'entity_type',
-      'field_id',
-      'cid',
-      'last_comment_timestamp',
-      'last_comment_name',
-      'last_comment_uid',
-      'comment_count',
-    ));
-    $execute_query = FALSE;
-    foreach ($fields as $field_name => $detail) {
-      // Skip fields that entity does not have.
-      if (!$entity->hasField($field_name)) {
-        continue;
-      }
-      // There is at least one comment field, the query needs to be executed.
-      $execute_query = TRUE;
-      // Get the user ID from the entity if it is set, or default to the
-      // currently logged in user.
-      if ($entity instanceof EntityOwnerInterface) {
-        $last_comment_uid = $entity->getOwnerId();
-      }
-      if (!isset($last_comment_uid)) {
-        // Default to current user when entity does not implement
-        // EntityOwnerInterface or author is not set.
-        $last_comment_uid = \Drupal::currentUser()->id();
-      }
-      // Default to REQUEST_TIME when entity does not have a changed property.
-      $last_comment_timestamp = REQUEST_TIME;
-      if ($entity instanceof EntityChangedInterface) {
-        $last_comment_timestamp = $entity->getChangedTime();
-      }
-      $query->values(array(
-        'entity_id' => $entity->id(),
-        'entity_type' => $entity->getEntityTypeId(),
-        'field_id' => $entity->getEntityTypeId() . '__' . $field_name,
-        'cid' => 0,
-        'last_comment_timestamp' => $last_comment_timestamp,
-        'last_comment_name' => NULL,
-        'last_comment_uid' => $last_comment_uid,
-        'comment_count' => 0,
-      ));
-    }
-    if ($execute_query) {
-      $query->execute();
-    }
+    \Drupal::entityManager()->getStorageController('comment')->initCommentStatistics($entity, $fields);
   }
 }
 
@@ -1018,8 +915,7 @@ function comment_node_update_index(EntityInterface $node, $langcode) {
       $instance = \Drupal::service('field.info')->getInstance('node', $node->getType(), $field_name);
       $mode = $instance->getSetting('default_mode');
       $comments_per_page = $instance->getSetting('per_page');
-      if ($node->get($field_name)->status && $cids = comment_get_thread($node, $field_name, $mode, $comments_per_page)) {
-        $comments = entity_load_multiple('comment', $cids);
+      if ($node->get($field_name)->status && $comments = comment_get_thread($node, $field_name, $mode, $comments_per_page)) {
         comment_prepare_thread($comments);
         $build = comment_view_multiple($comments);
         $return .= drupal_render($build);
@@ -1078,7 +974,7 @@ function comment_user_cancel($edit, $account, $method) {
     case 'user_cancel_block_unpublish':
       $comments = entity_load_multiple_by_properties('comment', array('uid' => $account->id()));
       foreach ($comments as $comment) {
-        $comment->setPublished(COMMENT_NOT_PUBLISHED);
+        $comment->setPublished(CommentInterface::NOT_PUBLISHED);
         $comment->save();
       }
       break;
@@ -1098,7 +994,7 @@ function comment_user_cancel($edit, $account, $method) {
  * Implements hook_user_predelete().
  */
 function comment_user_predelete($account) {
-  $cids = db_query('SELECT c.cid FROM {comment} c WHERE uid = :uid', array(':uid' => $account->id()))->fetchCol();
+  $cids = \Drupal::entityManager()->getStorageController('comment')->loadUsersComments($account);
   entity_delete_multiple('comment', $cids);
 }
 
@@ -1175,19 +1071,19 @@ function comment_num_new($entity_id, $entity_type, $field_name = NULL, $timestam
     $timestamp = ($timestamp > HISTORY_READ_LIMIT ? $timestamp : HISTORY_READ_LIMIT);
 
     // Use the timestamp to retrieve the number of new comments.
-    $query = db_select('comment', 'c');
-    $query->addExpression('COUNT(cid)');
-    $query->condition('c.entity_type', $entity_type)
-      ->condition('c.entity_id', $entity_id)
-      ->condition('c.status', CommentInterface::PUBLISHED)
-      ->condition('c.created', $timestamp, '>');
+    $query = \Drupal::entityQuery('comment')
+      ->condition('entity_type', $entity_type)
+      ->condition('entity_id', $entity_id)
+      ->condition('created', $timestamp, '>')
+      ->condition('status', CommentInterface::PUBLISHED);
+
     if ($field_name) {
       // Limit to a particular field.
-      $query->condition('c.field_id', $entity_type . '__' . $field_name);
+      $query->condition('field_id', $entity_type . '__' . $field_name);
     }
 
-    return $query->execute()
-      ->fetchField();
+    return $query->count()
+      ->execute();
   }
   else {
     return FALSE;
@@ -1213,30 +1109,7 @@ function comment_num_new($entity_id, $entity_type, $field_name = NULL, $timestam
  * @see field_info_instance().
  */
 function comment_get_display_ordinal($cid, $instance) {
-  // Count how many comments (c1) are before $cid (c2) in display order. This is
-  // the 0-based display ordinal.
-  $query = db_select('comment', 'c1');
-  $query->innerJoin('comment', 'c2', 'c2.entity_id = c1.entity_id AND c2.entity_type = c1.entity_type AND c2.field_id = c1.field_id');
-  $query->addExpression('COUNT(*)', 'count');
-  $query->condition('c2.cid', $cid);
-  if (!user_access('administer comments')) {
-    $query->condition('c1.status', CommentInterface::PUBLISHED);
-  }
-
-  if ($instance->getSetting('default_mode') == COMMENT_MODE_FLAT) {
-    // For flat comments, cid is used for ordering comments due to
-    // unpredictable behavior with timestamp, so we make the same assumption
-    // here.
-    $query->condition('c1.cid', $cid, '<');
-  }
-  else {
-    // For threaded comments, the c.thread column is used for ordering. We can
-    // use the sorting code for comparison, but must remove the trailing slash.
-    // See CommentViewBuilder.
-    $query->where('SUBSTRING(c1.thread, 1, (LENGTH(c1.thread) -1)) < SUBSTRING(c2.thread, 1, (LENGTH(c2.thread) -1))');
-  }
-
-  return $query->execute()->fetchField();
+  return \Drupal::entityManager()->getStorageController('comment')->getCommentDisplayOrdinal($cid, $instance);
 }
 
 /**
diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
index 4434b6e..4a47165 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
@@ -158,4 +158,355 @@ public function getChildCids(array $comments) {
       ->fetchCol();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function loadRecent($number = 10) {
+    $query = db_select('comment', 'c');
+    $query->addMetaData('base_table', 'comment');
+    $query->fields('c', array('cid'))
+      ->condition('c.status', CommentInterface::PUBLISHED);
+    if (\Drupal::moduleHandler()->moduleExists('node')) {
+      // Special case to filter by published content.
+      $query->innerJoin('node_field_data', 'n', "n.nid = c.entity_id AND c.entity_type = 'node'");
+      $query->addTag('node_access');
+      // @todo This should be actually filtering on the desired node status field
+      //   language and just fall back to the default language.
+      $query
+        ->condition('n.status', NODE_PUBLISHED)
+        ->condition('n.default_langcode', 1);
+    }
+    $cids = $query
+      ->orderBy('c.created', 'DESC')
+      // Additionally order by cid to ensure that comments with the same timestamp
+      // are returned in the exact order posted.
+      ->orderBy('c.cid', 'DESC')
+      ->range(0, $number)
+      ->execute()
+      ->fetchCol();
+
+    $comments = array();
+    if ($cids) {
+      $comments = $this->loadMultiple($cids);
+    }
+
+    return $comments;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * To display threaded comments in the correct order we keep a 'thread' field
+   * and order by that value. This field keeps this data in
+   * a way which is easy to update and convenient to use.
+   *
+   * A "thread" value starts at "1". If we add a child (A) to this comment,
+   * we assign it a "thread" = "1.1". A child of (A) will have "1.1.1". Next
+   * brother of (A) will get "1.2". Next brother of the parent of (A) will get
+   * "2" and so on.
+   *
+   * First of all note that the thread field stores the depth of the comment:
+   * depth 0 will be "X", depth 1 "X.X", depth 2 "X.X.X", etc.
+   *
+   * Now to get the ordering right, consider this example:
+   *
+   * 1
+   * 1.1
+   * 1.1.1
+   * 1.2
+   * 2
+   *
+   * If we "ORDER BY thread ASC" we get the above result, and this is the
+   * natural order sorted by time. However, if we "ORDER BY thread DESC"
+   * we get:
+   *
+   * 2
+   * 1.2
+   * 1.1.1
+   * 1.1
+   * 1
+   *
+   * Clearly, this is not a natural way to see a thread, and users will get
+   * confused. The natural order to show a thread by time desc would be:
+   *
+   * 2
+   * 1
+   * 1.2
+   * 1.1
+   * 1.1.1
+   *
+   * which is what we already did before the standard pager patch. To achieve
+   * this we simply add a "/" at the end of each "thread" value. This way, the
+   * thread fields will look like this:
+   *
+   * 1/
+   * 1.1/
+   * 1.1.1/
+   * 1.2/
+   * 2/
+   *
+   * we add "/" since this char is, in ASCII, higher than every number, so if
+   * now we "ORDER BY thread DESC" we get the correct order. However this would
+   * spoil the reverse ordering, "ORDER BY thread ASC" -- here, we do not need
+   * to consider the trailing "/" so we use a substring only.
+   */
+  public function loadThread(EntityInterface $entity, $field_name, $mode, $comments_per_page) {
+    $query = db_select('comment', 'c')
+      ->extend('Drupal\Core\Database\Query\PagerSelectExtender');
+    $query->addField('c', 'cid');
+    $query
+      ->condition('c.entity_id', $entity->id())
+      ->condition('c.entity_type', $entity->entityType())
+      ->condition('c.field_id', $entity->entityType() . '__' . $field_name)
+      ->addTag('entity_access')
+      ->addTag('comment_filter')
+      ->addMetaData('base_table', 'comment')
+      ->addMetaData('entity', $entity)
+      ->addMetaData('field_name', $field_name)
+      ->limit($comments_per_page);
+
+    $count_query = db_select('comment', 'c');
+    $count_query->addExpression('COUNT(*)');
+    $count_query
+      ->condition('c.entity_id', $entity->id())
+      ->condition('c.entity_type', $entity->entityType())
+      ->condition('c.field_id', $entity->entityType() . '__' . $field_name)
+      ->addTag('entity_access')
+      ->addTag('comment_filter')
+      ->addMetaData('base_table', 'comment')
+      ->addMetaData('entity', $entity)
+      ->addMetaData('field_name', $field_name);
+
+    if (!user_access('administer comments')) {
+      $query->condition('c.status', CommentInterface::PUBLISHED);
+      $count_query->condition('c.status', CommentInterface::PUBLISHED);
+    }
+    if ($mode == COMMENT_MODE_FLAT) {
+      $query->orderBy('c.cid', 'ASC');
+    }
+    else {
+      // See comment above. Analysis reveals that this doesn't cost too
+      // much. It scales much much better than having the whole comment
+      // structure.
+      $query->addExpression('SUBSTRING(c.thread, 1, (LENGTH(c.thread) - 1))', 'torder');
+      $query->orderBy('torder', 'ASC');
+    }
+
+    $query->setCountQuery($count_query);
+    $cids = $query->execute()->fetchCol();
+
+    $comments = array();
+    if ($cids) {
+      $comments = $this->loadMultiple($cids);
+    }
+
+    return $comments;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityCommentStatistics($ids, $entity_type) {
+    return db_select('comment_entity_statistics', 'ces')
+      ->fields('ces')
+      ->condition('ces.entity_id', $ids)
+      ->condition('ces.entity_type', $entity_type)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function initCommentStatistics(EntityInterface $entity, $fields) {
+      $query = db_insert('comment_entity_statistics')
+        ->fields(array(
+          'entity_id',
+          'entity_type',
+          'field_id',
+          'cid',
+          'last_comment_timestamp',
+          'last_comment_name',
+          'last_comment_uid',
+          'comment_count'
+        ));
+      foreach ($fields as $field_name => $detail) {
+        // Skip fields that entity does not have.
+        if (!$entity->hasField($field_name)) {
+          continue;
+        }
+        // There is at least one comment field, the query needs to be executed.
+        // @todo Use $entity->getAuthorId() after https://drupal.org/node/2078387
+        if ($entity->hasField('uid')) {
+          $last_comment_uid = $entity->getOwnerId();
+        }
+        else {
+          // Default to current user when entity does not have a uid property.
+          $last_comment_uid = \Drupal::currentUser()->id();
+        }
+        // Default to REQUEST_TIME when entity does not have a changed property.
+        $last_comment_timestamp = REQUEST_TIME;
+        if ($entity instanceof EntityChangedInterface) {
+          $last_comment_timestamp = $entity->getChangedTime();
+        }
+        $query->values(array(
+          'entity_id' => $entity->id(),
+          'entity_type' => $entity->getEntityTypeId(),
+          'field_id' => $entity->getEntityTypeId() . '__' . $field_name,
+          'cid' => 0,
+          'last_comment_timestamp' => $last_comment_timestamp,
+          'last_comment_name' => NULL,
+          'last_comment_uid' => $last_comment_uid,
+          'comment_count' => 0,
+        ));
+      }
+      $query->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteCommentStatistics(EntityInterface $entity) {
+    db_delete('comment_entity_statistics')
+      ->condition('entity_id', $entity->id())
+      ->condition('entity_type', $entity->getEntityTypeId())
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadUsersComments(EntityInterface $account) {
+    return db_query('SELECT c.cid FROM {comment} c WHERE uid = :uid and c.status = :status', array(':uid' => $account->id(), ':status' => CommentInterface::PUBLISHED))->fetchCol();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadCommentingUsers(EntityInterface $node, $entity_type = 'node') {
+    return db_query('SELECT DISTINCT c.uid FROM {comment} c WHERE entity_id = :nid AND entity_type=:type', array(':nid' => $node->id(), ':type' => $entity_type))->fetchCol();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getHighestCommentCount() {
+    return db_query('SELECT MAX(comment_count) FROM {comment_entity_statistics}')->fetchField();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCommentDisplayOrdinal($cid, $instance) {
+    // Count how many comments (c1) are before $cid (c2) in display order. This is
+    // the 0-based display ordinal.
+    $query = db_select('comment', 'c1');
+    $query->innerJoin('comment', 'c2', 'c2.entity_id = c1.entity_id AND c2.entity_type = c1.entity_type AND c2.field_id = c1.field_id');
+    $query->addExpression('COUNT(*)', 'count');
+    $query->condition('c2.cid', $cid);
+    if (!user_access('administer comments')) {
+      $query->condition('c1.status', CommentInterface::PUBLISHED);
+    }
+
+    if ($instance->getSetting('default_mode') == COMMENT_MODE_FLAT) {
+      // For flat comments, cid is used for ordering comments due to
+      // unpredictable behavior with timestamp, so we make the same assumption
+      // here.
+      $query->condition('c1.cid', $cid, '<');
+    }
+    else {
+      // For threaded comments, the c.thread column is used for ordering. We can
+      // use the sorting code for comparison, but must remove the trailing slash.
+      // See CommentRenderController.
+      $query->where('SUBSTRING(c1.thread, 1, (LENGTH(c1.thread) -1)) < SUBSTRING(c2.thread, 1, (LENGTH(c2.thread) -1))');
+    }
+
+    return $query->execute()->fetchField();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getThreadedNewCommentPage(EntityInterface $entity, $new_replies, $comments_per_page, $field_name) {
+    // 1. Find all the threads with a new comment.
+    $unread_threads_query = db_select('comment')
+      ->fields('comment', array('thread'))
+      ->condition('entity_id', $entity->id())
+      ->condition('entity_type', $entity->getEntityTypeId())
+      ->condition('field_id', $entity->getEntityTypeId() . '__' . $field_name)
+      ->condition('status', CommentInterface::PUBLISHED)
+      ->orderBy('created', 'DESC')
+      ->orderBy('cid', 'DESC')
+      ->range(0, $new_replies);
+
+    // 2. Find the first thread.
+    $first_thread = db_select($unread_threads_query, 'thread')
+      ->fields('thread', array('thread'))
+      ->orderBy('SUBSTRING(thread, 1, (LENGTH(thread) - 1))')
+      ->range(0, 1)
+      ->execute()
+      ->fetchField();
+
+    // Remove the final '/'.
+    $first_thread = substr($first_thread, 0, -1);
+
+    // Find the number of the first comment of the first unread thread.
+    $count = db_query('SELECT COUNT(*) FROM {comment} WHERE entity_id = :entity_id
+                      AND entity_type = :entity_type
+                      AND field_id = :field_id
+                      AND status = :status AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread', array(
+      ':status' => CommentInterface::PUBLISHED,
+      ':entity_id' => $entity->id(),
+      ':field_id' => $entity->getEntityTypeId() . '__' . $field_name,
+      ':entity_type' => $entity->getEntityTypeId(),
+      ':thread' => $first_thread,
+    ))->fetchField();
+
+    return $count / $comments_per_page;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLatestNodesWithStats($nodes) {
+    // @todo This should be actually filtering on the desired language and just
+    //   fall back to the default language.
+    // @todo Find better way to send this over. Plain DB object is probably not
+    //   the best way to go.
+    return db_query("
+    SELECT
+      n.nid,
+      SUM(l.comment_count) AS comment_count
+    FROM {node_field_data} n
+      INNER JOIN {comment_entity_statistics} l
+        ON n.nid = l.entity_id AND l.entity_type = 'node'
+      INNER JOIN {users} u
+        ON n.uid = u.uid
+    WHERE n.nid IN (:nids)
+      AND n.default_langcode = 1
+    GROUP BY n.nid
+    ORDER BY n.changed DESC", array(
+      ':nids' => $nodes
+    ), array('target' => 'slave'))->fetchAllKeyed();
+  }
+
+  /**
+   * {@inheridoc}
+   */
+  public function getCommentOverview($header, $status, $limit = 50) {
+    $query = db_select('comment', 'c')
+      ->extend('Drupal\Core\Database\Query\PagerSelectExtender')
+      ->extend('Drupal\Core\Database\Query\TableSortExtender');
+    if (\Drupal::moduleHandler()->moduleExists('node')) {
+      // Special case to ensure node access works.
+      $query->leftJoin('node_field_data', 'n', "n.nid = c.entity_id AND c.entity_type = 'node'");
+      $query->addTag('node_access');
+    }
+    return $query
+      ->fields('c', array('cid', 'subject', 'name', 'changed', 'entity_id', 'entity_type', 'field_id'))
+      ->condition('c.status', $status)
+      ->limit($limit)
+      ->orderByHeader($header)
+      ->execute()
+      ->fetchAllAssoc('cid');
+  }
 }
diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php b/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php
index 4583f20..dc4a02e 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php
@@ -66,4 +66,153 @@ public function getChildCids(array $comments);
    */
   public function updateEntityStatistics(CommentInterface $comment);
 
+  /**
+   * Loads latest comments.
+   *
+   * @param int $number
+   *   Number of latest comments to load. All comments will be returned if NULL.
+   * @return array
+   *   Array of most recent comments objects.
+   *
+   */
+  public function loadRecent($number = 10);
+
+  /**
+   * Retrieves comments for a thread.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $node
+   *   The node whose comment(s) needs rendering.
+   * @param string $field_name
+   *   The field_name whose comment(s) needs rendering.
+   * @param $mode
+   *   The comment display mode; COMMENT_MODE_FLAT or COMMENT_MODE_THREADED.
+   * @param $comments_per_page
+   *   The amount of comments to display per page.
+   *
+   * @return
+   *   An array of the commment objects.
+
+   */
+  public function loadThread(EntityInterface $entity, $field_name, $mode, $comments_per_page);
+
+  /**
+   * Returns commenting stats for entities.
+   *
+   * @param array $ids
+   *   Array of entity ids to load statistics for.
+   * @param string $entity_type
+   *   Entity type.
+   *
+   * @return array
+   *   Array of comment statistics objects.
+   */
+  public function getEntityCommentStatistics($ids, $entity_type);
+
+  /**
+   * Initializes comment statistics for a node (usually used at node creation).
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   Node object being created.
+   * @param array $fields
+   *   Array on fields on $entity;
+   */
+  public function initCommentStatistics(EntityInterface $entity, $fields);
+
+  /**
+   * Deletes comment statistics from storage (usually used at node deleteion).
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   Node object being deleted.
+   */
+  public function deleteCommentStatistics(EntityInterface $entity);
+
+  /**
+   * Retrieves CIDs of comments, that were created by a user.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $account
+   *   User object.
+   *
+   * @return array
+   *   Array of user's comment ids.
+   */
+  public function loadUsersComments(EntityInterface $account);
+
+  /**
+   * Retrieves UIDs of users that commented on a given node.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $node
+   *   User object.
+   * @param string $entity_type
+   *   Entity type to query against.
+   *
+   * @return array
+   *   Array of user ids.
+   */
+  public function loadCommentingUsers(EntityInterface $node, $entity_type = 'node');
+
+  /**
+   * Retrieves globally highest comment count (across all nodes).
+   *
+   * @return int
+   *   Number of comment on a node with highest comment count.
+   */
+  public function getHighestCommentCount();
+
+  /**
+   * Gets the display ordinal for a comment, starting from 0.
+   *
+   * Count the number of comments which appear before the comment we want to
+   * display, taking into account display settings and threading.
+   *
+   * @param int $cid
+   *   The comment ID.
+   * @param array $instance
+   *   Field instance as returned from field_info_instance().
+   *
+   * @return
+   *   The display ordinal for the comment.
+   */
+  public function getCommentDisplayOrdinal($cid, $instance);
+
+  /**
+   * Get page of first unread comment in threaded list.
+   *
+   * @param EntityInterface $node
+   *   Node object.
+   * @param int $new_replies
+   *   Number of new replies.
+   * @param int $comments_per_page
+   *   Number of comments per page..
+   * @param string $field_name
+   *   The field name on the entity to which comments are attached to.
+   *
+   * @return int
+   *   "page=X" if the page number is greater than zero; empty string otherwise.
+   */
+  public function getThreadedNewCommentPage(EntityInterface $node, $new_replies, $comments_per_page, $field_name);
+
+  /**
+   * Returns nodes along with their comment count, ordered by created date.
+   *
+   * @param array $nodes
+   *   Array of node IDs that we limit to.
+   * @return array
+   *   Array of objects as loaded from DB.
+   */
+  public function getLatestNodesWithStats($nodes);
+
+  /**
+   * Returns data needed for building comment overview page.
+   *
+   * @param array $headers
+   *   Overview table headers.
+   * @param $status
+   *   Comment status to filter on (approved or unapproved).
+   * @param int $limit
+   *   Number of items per page (defaults to 50).
+   * @return array
+   *   An associative array of comments (keyed by cid), or an empty array if
+   *   there is no result set.
+   */
+  public function getCommentOverview($headers, $status, $limit = 50);
 }
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php b/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
index 3250990..7636615 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
@@ -130,8 +130,8 @@ public function viewElements(FieldItemListInterface $items) {
         $this->currentUser->hasPermission('administer comments'))) {
         $mode = $comment_settings['default_mode'];
         $comments_per_page = $comment_settings['per_page'];
-        if ($cids = comment_get_thread($entity, $field_name, $mode, $comments_per_page, $this->settings['pager_id'])) {
-          $comments = $this->storageController->loadMultiple($cids);
+
+        if ($comments = comment_get_thread($entity, $field_name, $mode, $comments_per_page)) {
           comment_prepare_thread($comments);
           $build = $this->viewBuilder->viewMultiple($comments);
           $build['pager']['#theme'] = 'pager';
diff --git a/core/modules/tracker/tracker.module b/core/modules/tracker/tracker.module
index 04420cc..140230f 100644
--- a/core/modules/tracker/tracker.module
+++ b/core/modules/tracker/tracker.module
@@ -94,24 +94,25 @@ function tracker_cron() {
         ))
         ->execute();
 
-      $query = db_select('comment', 'c', array('target' => 'slave'));
-      // Force PostgreSQL to do an implicit cast by adding 0.
-      $query->addExpression('0 + :changed', 'changed', array(':changed' => $changed));
-      $query->addField('c', 'status', 'published');
-      $query->addField('c', 'entity_id', 'nid');
-      $query
-        ->distinct()
-        ->fields('c', array('uid'))
-        ->condition('c.entity_id', $row->nid)
-        ->condition('c.entity_type', 'node')
-        ->condition('c.uid', $row->uid, '<>')
-        ->condition('c.status', CommentInterface::PUBLISHED);
+      $node = \Drupal::entityManager()->getStorageController('node')->load($row->nid);
+      $uids = \Drupal::entityManager()->getStorageController('comment')->loadCommentingUsers($node);
 
       // Insert the user-level data for the commenters (except if a commenter
       // is the node's author).
-      db_insert('tracker_user')
-        ->from($query)
-        ->execute();
+      if ($uids) {
+        $query = db_insert('tracker_user');
+        foreach ($uids as $uid) {
+          if ($uid != $row->uid) {
+            $query->fields(array(
+              'uid' => $uid,
+              'nid' => $row->nid,
+              'published' => CommentInterface::PUBLISHED,
+              'changed' => $changed,
+            ));
+          }
+        }
+        $query->execute();
+      }
 
       // Note that we have indexed at least one node.
       $last_nid = $row->nid;
@@ -296,12 +297,21 @@ function _tracker_calculate_changed($nid) {
   // @todo This should be actually filtering on the desired language and just
   //   fall back to the default language.
   $changed = db_query('SELECT changed FROM {node_field_data} WHERE nid = :nid AND default_langcode = 1 ORDER BY changed DESC', array(':nid' => $nid), array('target' => 'slave'))->fetchField();
-  $latest_comment = db_query_range("SELECT cid, changed FROM {comment} WHERE entity_type = 'node' AND entity_id = :nid AND status = :status ORDER BY changed DESC", 0, 1, array(
-    ':nid' => $nid,
-    ':status' => CommentInterface::PUBLISHED,
-  ), array('target' => 'slave'))->fetchObject();
-  if ($latest_comment && $latest_comment->changed > $changed) {
-    $changed = $latest_comment->changed;
+
+  $latest_comment_cid = \Drupal::entityQuery('comment')
+    ->condition('entity_id', $nid)
+    ->condition('entity_type', 'node')
+    ->condition('status', CommentInterface::PUBLISHED)
+    ->sort('changed', 'DESC')
+    ->range(0, 1)
+    ->execute();
+  $latest_comment_cid = reset($latest_comment_cid);
+
+  if ($latest_comment_cid) {
+    $latest_comment = \Drupal::entityManager()->getStorageController('comment')->load($latest_comment_cid);
+    if ($latest_comment && $latest_comment->getChangedTime() > $changed) {
+      $changed = $latest_comment->getChangedTime();
+    }
   }
   return $changed;
 }
@@ -329,11 +339,15 @@ function _tracker_remove($nid, $uid = NULL, $changed = NULL) {
     // Or if they have commented on the node.
     if (!$keep_subscription) {
       // Check if the user has commented at least once on the given nid.
-      $keep_subscription = db_query_range("SELECT COUNT(*) FROM {comment} WHERE entity_type = 'node' AND entity_id = :nid AND uid = :uid AND status = :status", 0, 1, array(
-        ':nid' => $nid,
-        ':uid' => $uid,
-        ':status' => CommentInterface::PUBLISHED,
-      ))->fetchField();
+
+      $keep_subscription = \Drupal::entityQuery('comment')
+        ->condition('status', CommentInterface::PUBLISHED)
+        ->condition('uid', $uid)
+        ->condition('entity_id', $nid)
+        ->condition('entity_type', 'node')
+        ->range(0, 1)
+        ->count()
+        ->execute();
     }
 
     // If we haven't found a reason to keep the user's subscription, delete it.
diff --git a/core/modules/tracker/tracker.pages.inc b/core/modules/tracker/tracker.pages.inc
index e94c163..88201d9 100644
--- a/core/modules/tracker/tracker.pages.inc
+++ b/core/modules/tracker/tracker.pages.inc
@@ -45,23 +45,7 @@ function tracker_page($account = NULL) {
   if (!empty($tracker_data)) {
     $nids = array_keys($tracker_data);
     $nodes = node_load_multiple($nids);
-    // @todo This should be actually filtering on the desired language and just
-    //   fall back to the default language.
-    $result = db_query("
-    SELECT
-      n.nid,
-      SUM(l.comment_count) AS comment_count
-    FROM {node_field_data} n
-      INNER JOIN {comment_entity_statistics} l
-        ON n.nid = l.entity_id AND l.entity_type = 'node'
-      INNER JOIN {users} u
-        ON n.uid = u.uid
-    WHERE n.nid IN (:nids)
-      AND n.default_langcode = 1
-    GROUP BY n.nid
-    ORDER BY n.changed DESC", array(
-      ':nids' => array_keys($nodes)
-    ), array('target' => 'slave'))->fetchAllKeyed();
+    $result = \Drupal::entityManager()->getStorageController('comment')->getLatestNodesWithStats(array_keys($nodes));
     foreach ($result as $nid => $comment_count) {
       $nodes[$nid]->last_activity = $tracker_data[$nid]->changed;
       $nodes[$nid]->comment_count = $comment_count;
