From c8c42d8a384f226598a4327eb51a45073b3f81bb Mon Sep 17 00:00:00 2001
From: Jared Hancock <gravydish@gmail.com>
Date: Wed, 15 Aug 2018 03:11:14 +0000
Subject: [PATCH] queues: Add "rough" counts

This changes the queue counts shown at the bottom of the page to no longer be
calculated using the SQL_CALC_FOUND_ROWS method of MySQL. Such is very slow for
large recordsets. Instead, a rough count is computed based on the total number of
tickets in the queue without respect for staff access. This is the fastest way to
get a maximum number of possible tickets to be shown. The pagenation interface
should be changed to show only NEXT and PREVIOUS pages where the rough estimate can
be used to provide a rough idea of whether or not another page of data would be
available.

Furthermore, if APCu is available, the rough count is stashed and kept between
requests so that the rough counts do not need to be re-tallied until they would
change from a ticket state change.

Another optimization might be to increment and decrement the queue rough counts when
tickets are created or change states. In such a case, it could be identified which
queues the old ticket would have been (and decrement the count) and which queues the
updated ticket would be in (and increment the count).
---
 include/class.pagenate.php                    | 12 +++--
 include/class.queue.php                       | 51 +++++++++++++++++++
 include/class.search.php                      |  2 +
 .../staff/templates/queue-tickets.tmpl.php    |  4 +-
 4 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/include/class.pagenate.php b/include/class.pagenate.php
index b20ad52a5..70d1ca126 100644
--- a/include/class.pagenate.php
+++ b/include/class.pagenate.php
@@ -22,6 +22,7 @@ class PageNate {
     var $total;
     var $page;
     var $pages;
+    var $approx=false;
 
 
     function __construct($total,$page,$limit=20,$url='') {
@@ -32,7 +33,7 @@ class PageNate {
         $this->setTotal($total);
     }
 
-    function setTotal($total) {
+    function setTotal($total, $approx=false) {
         $this->total = intval($total);
         $this->pages = ceil( $this->total / $this->limit );
 
@@ -42,6 +43,7 @@ class PageNate {
         if (($this->limit-1)*$this->start > $this->total) {
             $this->start -= $this->start % $this->limit;
         }
+        $this->approx = $approx;
     }
 
     function setURL($url='',$vars='') {
@@ -97,8 +99,12 @@ class PageNate {
         }
         $html=__('Showing')."&nbsp;";
         if ($this->total > 0) {
-            $html .= sprintf(__('%1$d - %2$d of %3$d' /* Used in pagination output */),
-               $start, $end, $this->total);
+            if ($this->approx)
+                $html .= sprintf(__('%1$d - %2$d of about %3$d' /* Used in pagination output */),
+                   $start, $end, $this->total);
+            else
+                $html .= sprintf(__('%1$d - %2$d of %3$d' /* Used in pagination output */),
+                   $start, $end, $this->total);
         }else{
             $html .= " 0 ";
         }
diff --git a/include/class.queue.php b/include/class.queue.php
index e62300017..643fdb712 100644
--- a/include/class.queue.php
+++ b/include/class.queue.php
@@ -1085,6 +1085,57 @@ class CustomQueue extends VerySimpleModel {
         $this->clearFlag(self::FLAG_DISABLED);
     }
 
+    function getRoughCount() {
+        if (($count = $this->getRoughCountAPC()) !== false)
+            return $count;
+
+        $query = Ticket::objects();
+        $Q = $this->getBasicQuery();
+        $expr = SqlCase::N()->when(new SqlExpr(new Q($Q->constraints)),
+            new SqlField('ticket_id'));
+        $query = $query->aggregate(array(
+            "ticket_count" => SqlAggregate::COUNT($expr)
+        ));
+
+        $row = $query->values()->one();
+        return $row['ticket_count'];
+    }
+
+    function getRoughCountAPC() {
+        if (!function_exists('apcu_store'))
+            return false;
+
+        $key = "rough.counts.".SECRET_SALT;
+        $cached = false;
+        $counts = apcu_fetch($key, $cached);
+        if ($cached === true && isset($counts["q{$this->id}"]))
+            return $counts["q{$this->id}"];
+
+        // Fetch rough counts of all queues. That is, fetch a total of the
+        // counts based on the queue criteria alone. Do no consider agent
+        // access. This should be fast and "rought"
+        $queues = static::objects()
+            ->filter(['flags__hasbit' => CustomQueue::FLAG_PUBLIC])
+            ->exclude(['flags__hasbit' => CustomQueue::FLAG_DISABLED]);
+
+        $query = Ticket::objects();
+        $prefix = "";
+
+        foreach ($queues as $queue) {
+            $Q = $queue->getBasicQuery();
+            $expr = SqlCase::N()->when(new SqlExpr(new Q($Q->constraints)),
+                new SqlField('ticket_id'));
+            $query = $query->aggregate(array(
+                "q{$queue->id}" => SqlAggregate::COUNT($expr)
+            ));
+        }
+
+        $counts = $query->values()->one();
+
+        apcu_store($key, $counts, 900);
+        return @$counts["q{$this->id}"];
+    }
+
     function updateExports($fields, $save=true) {
 
         if (!$fields)
diff --git a/include/class.search.php b/include/class.search.php
index 6dad61416..aa2919910 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -899,6 +899,8 @@ class SavedQueue extends CustomQueue {
             foreach (new APCUIterator($regex, APC_ITER_KEY) as $key) {
                 apcu_delete($key);
             }
+            // Also clear rough counts
+            apcu_delete("rough.counts.".SECRET_SALT);
         }
     }
 
diff --git a/include/staff/templates/queue-tickets.tmpl.php b/include/staff/templates/queue-tickets.tmpl.php
index 37d7cced7..205ac43c3 100644
--- a/include/staff/templates/queue-tickets.tmpl.php
+++ b/include/staff/templates/queue-tickets.tmpl.php
@@ -76,8 +76,8 @@ if (!$sorted && isset($sort['queuesort'])) {
 $page = ($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
 $pageNav = new Pagenate(PHP_INT_MAX, $page, PAGE_LIMIT);
 $tickets = $pageNav->paginateSimple($tickets);
-$count = $tickets->total();
-$pageNav->setTotal($count);
+$count = $queue->getRoughCount();
+$pageNav->setTotal($count, true);
 $pageNav->setURL('tickets.php', $args);
 ?>
 
-- 
GitLab