mirror of
https://github.com/itflow-org/itflow
synced 2026-11-15 17:07:22 +00:00
Speed up the dashboard and the menu counts on installs with years of history: database 3.6.4 adds seven indexes, each only where it is missing and also in db.sql for new installs - tickets on ticket_resolved_at, ticket_closed_at and ticket_created_at, a five-column one led by ticket_billable that answers the unbilled count from the index alone, ticket_replies on ticket_reply_by with ticket_reply_created_at, invoices on invoice_status and expenses on expense_date - so open tickets, unpaid invoices, an agent time this week and unbilled tickets are found directly, where each was a read of the whole table, a dozen times over tickets on the dashboard and twice in the menu on every page; dashboardPeriodOptions finds the years with data with one index step a year per table in place of a UNION of YEAR over every row of five tables, and dashboardLatestIncome joins from payments with STRAIGHT_JOIN so the last five are read off payment_date, where every payment was sorted; measured on 280,000 tickets, 380,000 replies and 70,000 invoices the dashboard went from 5.1s to 0.7s and other pages lost about half a second, with every figure unchanged; also fix lists past 10,000 pages, where includes/filter_footer.php left the pager step unset and a division by zero ended the page at its page numbers; add a 26.11.7 changelog section
This commit is contained in:
78
admin/database_updates/3.6.4.php
Normal file
78
admin/database_updates/3.6.4.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* ITFlow - Database update to version 3.6.4 (from 3.6.3)
|
||||
* Included by admin/database_updates.php - do not access directly
|
||||
*/
|
||||
|
||||
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
|
||||
|
||||
// Third indexing pass - the dashboard, and the counts in the menu on every page.
|
||||
//
|
||||
// 2.7.5 and 2.7.6 indexed "this client's records" and "this record's children". What
|
||||
// was still reading whole tables is "the ones that are still open": on an install with
|
||||
// years behind it nearly every ticket is closed and nearly every invoice paid, and the
|
||||
// dashboard and the menu ask for the few that aren't - a dozen times over tickets on
|
||||
// the dashboard, twice in the menu on every page. Measured on 280,000 tickets, 380,000
|
||||
// replies and 70,000 invoices: the dashboard from 5.1s to 0.7s, and about half a
|
||||
// second off every other page.
|
||||
//
|
||||
// tickets.ticket_resolved_at, tickets.ticket_closed_at
|
||||
// IS NULL is the open tickets - a sliver of the table, where *_archived_at IS NULL
|
||||
// is nearly all of it (why 2.7.5 only ever put that one last). The same indexes
|
||||
// serve "resolved since" for the dashboard's opened and resolved chart.
|
||||
// tickets.ticket_created_at
|
||||
// the other half of that chart, and the years the period picker offers.
|
||||
// tickets (ticket_billable, ticket_invoice_id, ticket_status, ticket_client_id, ticket_archived_at)
|
||||
// Unbilled Time's count, in the menu on every page for anyone who can see billing.
|
||||
// It can match a large share of the table, so it carries every column the count
|
||||
// reads - the count is answered from the index and never touches a ticket's row.
|
||||
// ticket_replies (ticket_reply_by, ticket_reply_created_at)
|
||||
// one agent's time this week and last - two passes over every reply ever written.
|
||||
// invoices.invoice_status
|
||||
// 2.7.6 left this out as too few distinct values, which is right for Paid. Sent,
|
||||
// Viewed and Partial together are a sliver, the optimizer does take the index for
|
||||
// them (and still scans for Paid), and a visit to the dashboard asks eight times.
|
||||
// expenses.expense_date
|
||||
// payment_date and revenue_date have theirs; every expense figure for a period
|
||||
// read every expense.
|
||||
//
|
||||
// Still not included: payments/expenses/revenues .*_account_id. Account balances add up
|
||||
// every row there is, so an index on the account would not spare them one.
|
||||
//
|
||||
// Re-runnable, as the two before it: each index is added only where it isn't.
|
||||
|
||||
$itflow_indexes = [
|
||||
['tickets', 'ticket_resolved_at', ['ticket_resolved_at']],
|
||||
['tickets', 'ticket_closed_at', ['ticket_closed_at']],
|
||||
['tickets', 'ticket_created_at', ['ticket_created_at']],
|
||||
['tickets', 'ticket_billable', ['ticket_billable', 'ticket_invoice_id', 'ticket_status', 'ticket_client_id', 'ticket_archived_at']],
|
||||
['ticket_replies', 'ticket_reply_by', ['ticket_reply_by', 'ticket_reply_created_at']],
|
||||
['invoices', 'invoice_status', ['invoice_status']],
|
||||
['expenses', 'expense_date', ['expense_date']],
|
||||
];
|
||||
|
||||
foreach ($itflow_indexes as $itflow_index) {
|
||||
list($itflow_index_table, $itflow_index_name, $itflow_index_columns) = $itflow_index;
|
||||
|
||||
// MySQL has no ADD INDEX IF NOT EXISTS, and re-adding one is an error
|
||||
// rather than a no-op, so check first. This also makes the migration
|
||||
// safe on an instance where someone added the index by hand.
|
||||
$itflow_index_exists = mysqli_query($mysqli, "SELECT 1 FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = '$itflow_index_table'
|
||||
AND INDEX_NAME = '$itflow_index_name'
|
||||
LIMIT 1");
|
||||
|
||||
if ($itflow_index_exists && mysqli_num_rows($itflow_index_exists) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$itflow_index_column_list = '`' . implode('`, `', $itflow_index_columns) . '`';
|
||||
|
||||
mysqli_query($mysqli, "ALTER TABLE `$itflow_index_table`
|
||||
ADD KEY `$itflow_index_name` ($itflow_index_column_list)");
|
||||
}
|
||||
|
||||
unset($itflow_indexes, $itflow_index, $itflow_index_table, $itflow_index_name,
|
||||
$itflow_index_columns, $itflow_index_exists, $itflow_index_column_list);
|
||||
Reference in New Issue
Block a user