Previously we tried using a sub-query which has some terrible performance
implications, by adding 1 extra query we can eliminate the PG issue
A join to user_stats also is prone to the same slowdown
This optimisation avoids large scans joining the topics table with the
topic_users table.
Previously when a user carried a lot of read state we would have to join
the entire read state with the topics table. This operation would slow down
home page and every topic page. The more read state you accumulated the
larger the impact.
The optimisation helps people who clean up unread, however if you carry
unread from years ago it will only have minimal impact.
Historically due to https://meta.discourse.org/t/why-is-discourse-so-slow-on-android/8823
we decreased page sizes of both home page and topic page on android by half.
This was done on the server side and as a side effect and caused page sizes on android
to mismatch between Android and non Android.
Unfortunately about a year ago googlebot started pretending it is Android,
this cause Google to start indexing pages as what android would see. So
it saw double the amount of pages in the index as what exists on desktop.
This in turn caused double the amount of indexing work and a large amount
of broken links on long topics.
This fix removes all special behavior which is no longer needed due to
other performance work in Discourse including raw handlebars on home page
and virtual dom on topic pages.
I tested we do not need this on Blu Advance 5.0 it has 1.3 GHZ mediatec mt6580
This phone retails for around $50 USD.
If we decide long term that we want any hacks like this we will shift them
to the client side. It can just hold data in memory without rendering.
Previously the related PMs were last meaning you would have to work through
all unread to see them.
Also amends it so it either asks for related by group OR user not both.
- By default, behaviour is not changed: tags are made lowercase upon creation and edit.
- If force_lowercase_tags is disabled, then mixed case tags are allowed.
- Tags must remain case-insensitively unique. This is enforced by ActiveRecord and Postgres.
- A migration is added to provide a `UNIQUE` index on `lower(name)`. Migration includes a safety to correct any current tags that do not meet the criteria.
- A `where_name` scope is added to `models/tag.rb`, to allow easy case-insensitive lookups. This is used instead of `Tag.where(name: "blah")`.
- URLs remain lowercase. Mixed case URLs are functional, but have the lowercase equivalent as the canonical.
This feature can be enabled by choosing a destination for the
`shared drafts category` site setting.
* Staff members can create shared drafts, choosing a destination
category for the topic when it is published.
* Shared Drafts can be viewed in their category, or above the
topic list for the destination category where it will end up.
* When the shared draft is ready, it can be published to the
appropriate category by clicking a button on the topic view.
* When published, Drafts change their timestamps to the current
time, and any edits to the original post are removed.
```
EXPLAIN ANALYZE SELECT "topics".* FROM "topics"
LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND tu.user_id =
13455
WHERE ("topics"."deleted_at" IS NULL)
AND (topics.archetype = 'private_message')
AND (
topics.id IN (
SELECT topic_id
FROM topic_allowed_groups tg
JOIN group_users gu ON gu.user_id = 13455 AND gu.group_id =
tg.group_id
WHERE gu.group_id IN (47)
)
)
AND (
topics.id IN (
SELECT ta.topic_id
FROM topic_allowed_users ta
WHERE ta.user_id IN (32852,-10)
)
OR
topics.id IN (
SELECT tg.topic_id
FROM topic_allowed_groups tg
WHERE tg.group_id IN (-10)
)
)
AND (topics.id NOT IN (69933,69995,69988,69984,69968,69973,69971,69952))
AND "topics"."visible" = 't'
ORDER BY topics.bumped_at DESC
LIMIT 3;
```
Planning time: 1.277 ms
Execution time: 71.577 ms
```
EXPLAIN ANALYZE SELECT "topics".* FROM "topics"
LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND tu.user_id =
13455
LEFT JOIN (
SELECT * FROM topic_allowed_groups _tg
LEFT JOIN group_users gu
ON gu.user_id = 13455
AND gu.group_id = _tg.group_id
AND gu.group_id IN (47)
) tg ON topics.id = tg.topic_id
LEFT JOIN topic_allowed_users ta2 ON topics.id = ta2.topic_id AND
ta2.user_id IN (32852)
WHERE ("topics"."deleted_at" IS NULL)
AND (topics.archetype = 'private_message')
AND (tg.topic_id IS NOT NULL)
AND (ta2.topic_id IS NOT NULL)
AND (topics.id NOT IN (69933,69995,69988,69984,69968,69973,69971,69952))
AND "topics"."visible" = 't'
ORDER BY topics.bumped_at DESC
LIMIT 3;
```
Planning time: 1.191 ms
Execution time: 0.129 ms
```
EXPLAIN ANALYZE SELECT "topics".*
FROM "topics" LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND
tu.user_id = 13455
WHERE ("topics"."deleted_at" IS NULL)
AND (topics.archetype = 'private_message')
AND (
topics.id IN (
SELECT topic_id
FROM topic_allowed_users
WHERE user_id = 13455
)
)
AND (
topics.id IN (
SELECT ta.topic_id
FROM topic_allowed_users ta
WHERE ta.user_id IN (2,1995,8307,17621,22980,-10)
)
OR
topics.id IN (
SELECT tg.topic_id
FROM topic_allowed_groups tg
WHERE tg.group_id IN (-10)
)
)
AND (topics.id NOT IN (68559,60069,42145))
AND "topics"."visible" = 't'
ORDER BY topics.bumped_at
DESC LIMIT 5;
```
Planning time: 1.196 ms
Execution time: 21.176 ms
```
EXPLAIN ANALYZE SELECT "topics".*
FROM "topics"
LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND tu.user_id =
13455
LEFT JOIN topic_allowed_users ta ON topics.id = ta.topic_id AND
ta.user_id = 13455
LEFT JOIN topic_allowed_users ta2 ON topics.id = ta2.topic_id AND
ta2.user_id IN (2,1995,8307,17621,22980,-10)
LEFT JOIN topic_allowed_groups tg ON topics.id = tg.topic_id AND
tg.group_id IN (-10)
WHERE ("topics"."deleted_at" IS NULL)
AND (topics.archetype = 'private_message')
AND (ta.topic_id IS NOT NULL)
AND (ta2.topic_id IS NOT NULL OR tg.topic_id IS NOT NULL)
AND (topics.id NOT IN (68559,60069,42145))
AND "topics"."visible" = 't'
ORDER BY topics.bumped_at DESC
LIMIT 5;
```
Planning time: 1.792 ms
Execution time: 2.546 ms
Figuring out what unread topics a user has is a very expensive
operation over time.
Users can easily accumulate 10s of thousands of tracking state rows
(1 for every topic they ever visit)
When figuring out what a user has that is unread we need to join
the tracking state records to the topic table. This can very quickly
lead to cases where you need to scan through the entire topic table.
This commit optimises it so we always keep track of the "first" date
a user has unread topics. Then we can easily filter out all earlier
topics from the join.
We use pg functions, instead of nested queries here to assist the
planner.
- Regular users are not notified of whispers
- Regular users no longer have "stuck" topics in unread
- Additional tracking for staff highest post number
- Remove a bunch of unused columns in topics table
FEATURE: clicking envelope takes you to inbox
Suggested messages works somewhat like suggested topics.
- New show up first (in either group inbox or inbox)
- Then unread (in either group inbox or inbox)
- Finally "related" which are messages with same participants as the current pm.
Messages are now in 3 buckets
- Inbox for all new messages
- Sent for all sent messages
- Archive for all messages you are done with
You can select messages from your Inbox or Sent and move them to your Archive,
you can move messages out of your Archive similarly
Similar concept applied to group messages, except that archiving and unarchiving
will apply to all group members
Old query used to scan the full topics table, on home page
Instead we now perform 2 queries, one for pinned and one for unpinned and merge
results in a 10x improvement on a 1 million topic DB
With this change, the EXPLAIN on the topic query is reduced in cost, and
script/bench.rb results are improved.
Explain topic query, cost at root level:
cost=641.84..641.92 --> cost=570.03..570.11
script/bench.rb result, 50th percentile:
BEFORE
---
categories_admin: 95
home_admin: 80
topic_admin: 33
categories: 71
home: 51
topic: 15
AFTER
---
categories_admin: 78
home_admin: 58
topic_admin: 49
categories: 40
home: 34
topic: 30
Changed internals so trust levels are referred to with
TrustLevel[1], TrustLevel[2] etc.
This gives us much better flexibility naming trust levels, these names
are meant to be controlled by various communities.