Opened 2 weeks ago
Last modified 12 days ago
#8452 new enhancement
Optimize Trac database schema
| Reported by: | timse201 | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | |
| Component: | Trac | Keywords: | dev-reviewed 2nd-opinion |
| Cc: |
Description
A review of the MySQL schema files (mysql-schema.sql and notifications-schema.sql) identified several areas where performance, storage efficiency, and data integrity can be improved.
- Remove Redundant Indexes
ticket_change: DropKEY ticket_change_ticket_idx (ticket)becauseticketis already the first column of the compositePRIMARY KEY (ticket, time, field).node_change: DropKEY node_change_repos_rev_idx (repos, rev)becausereposandrevare already covered byPRIMARY KEY (repos, rev, path, change_type).
- Add / Optimize Indexes
ticket: Currently only has indexes ontimeandstatus. Common queries likestatus != 'closed' AND reporter = $USERcause full-table scans. We should add composite indexes for common filter combinations (e.g.,(status, reporter),(component, status),(milestone, status))._notifications: ChangeKEY type_value (type, value)to a covering index(type, value, username)so queries can be satisfied directly from the index.
- Fix Foreign Key Type Mismatch
ticket.idis defined asint(10) unsigned.- Foreign key references in
ticket_change.ticket,ticket_custom.ticket, and_ticket_subs.ticketare defined as signedint(11).
- Improve
_ticket_subsTable Structure
- The table currently lacks an explicit primary key.
- The current
UNIQUE KEY username_ticket_status (username, ticket, status)allows multiple rows for the same user and ticket if the status changes. - Define
PRIMARY KEY (ticket, username)to enforce one status entry per user per ticket.
- Field Data Types
ticket.summary: Change fromtexttovarchar(255). This reduces storage overhead.auth_cookie.timeandsession.last_visit: Consider changing fromint(11)tobigint(20)to prevent Year 2038 issues and align with timestamps used in other tables.
Change History (2)
Note:
See TracTickets
for help on using tickets.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Thanks for digging into this! I think there's useful housekeeping here, particularly making the ticket ID types consistent and widening the two remaining timestamp columns.
A few thoughts after looking through the schemas and notification queries:
(ticket)secondary index can cover count queries without scanning the full clustered rows.(reporter, status): the reporter comparison is equality, whilestatus != 'closed'is a range condition. The component and milestone indexes look like reasonable candidates too, but I'd pick the final set from actual queries andEXPLAINresults._notificationsneeds the proposed covering-index change. Its existingUNIQUE (username, type, value)has all columnsNOT NULL, so InnoDB uses it as the clustered key. The(type, value)secondary index already includes the remaining clustered-key column,username, and should already cover theSELECT usernamequeries in the notification patch._ticket_subsneeds the write paths checked before changing uniqueness. The notification patch reads subscriptions (status > 0) and explicit blocks (status = 0) separately. With the current reads, an explicit block wins if both rows exist, because the block pass removes the user after subscriptions are added. A migration should preserve that behavior unless we intend to change it, and the write paths need to ensure changing a status updates the existing row. Its current unique, non-null key already gives InnoDB a clustered key, even without an explicit primary key.summaryasTEXTfor now unless we can demonstrate a benefit and establish that 255 characters is the intended application limit.VARCHAR(255)isn't automatically a meaningful storage saving with this multibyte charset, and it introduces a new limit for both existing and future tickets.A few additional things I noticed in the schemas that might be worth separate follow-ups:
ticket_change.oldvalueisTEXT, whileticket.descriptionandticket_change.newvalueareLONGTEXT. I'd check the description-edit history path and consider wideningoldvalueto match. If the previous description is recorded there, an edit to a description over 65,535 bytes can hit the smaller history-column limit even though the ticket itself supports it. A regression test that edits a large description would be useful here.attachment.filenameallows 400 characters, but its primary key only includesfilename(255). That means two filenames on the same attachment parent with identical first 255 characters collide even if their remaining characters differ. I'd check the filename limits enforced by Trac and storage, then either make the schema match that limit or enforce uniqueness on the full filename, subject to the supported InnoDB key-size limits.utf8/utf8_bin, and the main schema also sets the connection charset toutf8. I'd compare these with the live tables and connection settings and plan an explicitutf8mb4migration if they're still using three-byte UTF-8. That would support supplementary Unicode characters, including emoji. We'd need to preserve the intended case-sensitive comparisons and check composite key sizes and row formats before changing the table and connection charsets together.I'd be happy to see this move forward in small pieces, with migration SQL for the existing databases and measured results for the index changes.
Thanks for putting the list together!