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')." "; 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