Files
mostlymatter/.gitignore
Agniva De Sarker d8dbb6cc22 MM-56548: [AI assisted]Add support for incremental thread loading using UpdateAt timestamp (#30486)
Every time we load the RHS, we used to load the FULL thread always. Although
the actual ThreadViewer React component is virtualized, and the server side
API call is paginated, we still went through all the pages, to get the full
thread and passed it on to the ThreadViewer. This would be for first loads,
and subsequent loads of the same thread.

This was a bug originally, but then it was a necessity after we applied websocket event scope because
now we won't get emoji reactions of a thread if the user is not on the thread.

To fix that, we enhance the thread loading functionality by adding support for fetching
thread updates based on the UpdateAt timestamp. Now, for subsequent loads,
we only get the changed posts in a thread. The implementation:

- Adds new API parameters: fromUpdateAt and updatesOnly to the GetPostThread endpoint
- Updates database queries to support sorting and filtering by UpdateAt
- Implements thread state management to track the last update timestamp
- Adds client-side support to use incremental loading for improved performance
- Ensures proper validation for parameter combinations and error handling

This change enables more efficient thread loading, particularly for long threads
with frequent updates, by only fetching posts that have been updated since the
last view.

Caveats: For delta updates, the SQL query won't use the best index possible
because we have an index for (CreateAt, Id), but no index for (UpdateAt, Id).
However, from my tests, it is not as bad as it looks:

```
[loadtest] # EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM Posts WHERE Posts.DeleteAt = 0 AND Posts.RootId = 'qbr5gctu9iyg8c36hpcq6f3w8e' AND Posts.UpdateAt > 1623445795824 ORDER BY UpdateAt ASC, Id ASC LIMIT 61;
                                                                   QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=8.31..8.31 rows=1 width=216) (actual time=0.047..0.049 rows=0 loops=1)
   Buffers: shared hit=2
   ->  Sort  (cost=8.31..8.31 rows=1 width=216) (actual time=0.044..0.045 rows=0 loops=1)
         Sort Key: updateat, id
         Sort Method: quicksort  Memory: 25kB
         Buffers: shared hit=2
         ->  Index Scan using idx_posts_root_id_delete_at on posts  (cost=0.28..8.30 rows=1 width=216) (actual time=0.031..0.032 rows=0 loops=1)
               Index Cond: (((rootid)::text = 'qbr5gctu9iyg8c36hpcq6f3w8e'::text) AND (deleteat = 0))
               Filter: (updateat > '1623445795824'::bigint)
               Buffers: shared hit=2
 Planning:
   Buffers: shared hit=3
 Planning Time: 0.508 ms
 Execution Time: 0.106 ms
(14 rows)
```

We still get an index scan with index cond. Although there's a filter element, but atleast we get the whole thread with the index.
My thinking is that while the whole thread might be large, but after that, updates on a thread should be incremental.
Therefore, we should be okay without adding yet another index on the posts table.

This is just the first step in what could be potentially improved further.

1. We shouldn't even be loading the full thread always. But rather let the virtualized viewer
load more posts on demand.
2. If a post has been just reacted to, then we need not send the whole post down, but just the
reaction. This further saves bandwidth.

https://mattermost.atlassian.net/browse/MM-56548

TBD: Add load-test coverage to update the thread loading code

```release-note
NONE
```
---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2025-04-22 10:43:13 +05:30

164 строки
2.4 KiB
Plaintext

logs
.DS_Store
node_modules
/data
/dist
/webapp/dist
jobserver
npm-debug.log
webapp/yarn-error.log
mattermost.mattermost-license
config/mattermost.mattermost-license
config/config.json
config/*.crt
web/static/js/bundle*.js
web/static/js/bundle*.js.map
web/static/js/libs*.js
config/active.dat
config/config.json
config/logging.json
/plugins
# disable folders generated by Cypress
e2e-tests/cypress/node_modules
e2e-tests/cypress/tests/downloads
e2e-tests/cypress/tests/screenshots
e2e-tests/cypress/tests/videos
e2e-tests/cypress/tests/integration/benchmark/__benchmarks__
e2e-tests/cypress/tests/integration/performance/logs
e2e-tests/cypress/tests/fixtures/ldap_tmp
e2e-tests/cypress/tests/fixtures/mmctl
e2e-tests/cypress/results
e2e-tests/cypress/.eslintcache
# disable files/folders generated by Playwright
e2e-tests/playwright/node_modules
e2e-tests/playwright/playwright-report
e2e-tests/playwright/storage_state
e2e-tests/playwright/test-results
e2e-tests/playwright/results
e2e-tests/playwright/specs/**/*-darwin.png
e2e-tests/playwright/specs/**/*-window.png
e2e-tests/playwright/specs/accessibility/**/*-snapshots
e2e-tests/playwright/.eslintcache
# ignore temporary added configuration for pgloader
server/tests/temp.load
# Enterprise & products imports files
imports/imports.go
# go.work file
go.work
go.work.sum
#license files
*.license
*.mattermost-license
# Build Targets
.prebuild
.npminstall
.yarninstall
/prepackaged_plugins
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
testfiles
# Architecture specific extensions/prefixes
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# Log files
*.log
*.log.jsonl
*.log.gz
# Fuzz binaries and working dir
*fuzz.zip
app/workdir
.tmp
# Vim temporary files
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
*~
# VSCode project files
.vscode
*.code-workspace
# Gogland project files
mattermost-server.iml
# Build files
*bundle.js
web/sass-files/sass/.sass-cache/
*config.codekit
*.sass-cache
*styles.css
# Default local file storage
data/*
webapp/data/*
api/data/*
api4/data/*
app/data/*
/enterprise
cover.out
ecover.out
cprofile.out
*.test
webapp/coverage
/report.xml
junit.xml
.agignore
.ctags
/tags
.idea
/debug
/client
__debug_bin
report.xml
go.*.orig
config.override.mk
docker-compose.override.yaml
## Notice Folders
.notice/
.notice-work/
.aider*
.env
CLAUDE.md