When a user disconnects from the hub, we would spawn off a goroutine
which would make a cluster request, and then update the user status
as offline in the DB.
This was another case of unbounded concurrency where the number of
goroutines spawned was user controlled. Therefore, we would see
a clear spike in DB connections on master when a lot of users
would suddenly disconnect.
To fix this, we implement concurrency control in two areas:
1. In making the cluster request. We implement a counting semaphore
per-hub to avoid making unbounded cluster requests.
2. We use a buffered channel with a periodic flusher to process
status updates.
We also add a new store method to upsert multiple statuses
in a single query. The statusUpdateThreshold is set to 32, which means
no more than 32 rows will be upserted at one time, keeping the
SQL query load reasonable.
https://mattermost.atlassian.net/browse/MM-64298
```release-note
We improve DB connection spikes on user disconnect
by processing status updates in batches.
```
* rm "No TEST_DATABASE... override" log message
Let's only log if this value is actually overridden.
* rm "(Created|Dropped) temporary database" message
* only log "Pinging SQL" on subsequent attempts
* disable morph logging from TestMain
* Fix style issues in store test files
- Add missing parameter to migrate() function calls in tests
- Remove unused log function in settings.go
- Fix formatting with go fmt
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* within sqlstore, use "enable" vs "disable" for clarity
* remove trailing newline from morph logs
---------
Co-authored-by: Claude <noreply@anthropic.com>
* MM-62378: Initialize status and session cache before loading license
ps.LoadLicense could end up calling InvalidateAllCaches. Therefore,
we need to intialize those caches before calling LoadLicense
to prevent a panic from happening.
While here, we also remove some unused code.
https://mattermost.atlassian.net/browse/MM-62378
```release-note
NONE
```
* fix lint errors
```release-note
NONE
```
* fix test
```release-note
NONE
```
And re-order the server initialization (AGAIN).
For the nth time, we found several bugs in the initialization
process.
1. filestore.NewExportFileBackend and filestore.NewFileBackend
depended on license, but the license wasn't even loaded until
later!
2. The `ps.sqlStore.UpdateLicense` call also didn't work
because the license wouldn't get loaded. It only accidentally
worked because of `ps.AddLicenseListener` which would update
the license later on. We remove that.
Ideally, we would have loaded the license first and then
checked for redis client, but it's very difficult to do that.
Reasons are explained in the code comment.
So we just wait until the license is loaded, and simply throw an
error later.
https://mattermost.atlassian.net/browse/MM-61229
```release-note
NONE
```
---------
Co-authored-by: Mattermost Build <build@mattermost.com>
This originated from https://github.com/mattermost/mattermost/issues/15249.
However, the original idea was discarded https://github.com/mattermost/mattermost/issues/15249#issuecomment-709713065
as being too complicated to implement. Then I had another
idea to implement it just for session objects.
My thinking was that since every single request allocates a new
session struct, it would be good to use a sync.Pool for that.
However, 4 years later, now we know that the primary bottleneck
in app performance comes from websocket event marshalling.
Therefore, while it would be good to do this, it is difficult
to do it correctly (as shown by the numerous racy tests).
Hence, reverting this.
```release-note
NONE
```
- Update library version.
- Added MaxFlush delay to help reduce CPU usage.
- Fall back to LRU cache for the caches which use SCAN.
- Added mattermost-redis and running for all api layer
tests in Postgres.
https://mattermost.atlassian.net/browse/MM-59934
```release-note
NONE
```
- We introduce 2 new APIs:
1. Scan: this allows incremental iteration
without blocking the Redis server and is the
recommended way to iterate over keys. With this,
we have entirely removed the need for Keys.
2. RemoveMulti: this allows deletion of multiple
keys in a single operation which optimizes
network round trips.
- While here, we make a small improvement to
GetStatusFromCache, where we remove the shallow
copy which wasn't necessary because we always
serialize the data from the cache.
- We do not use Redis for session cache because of
frequent requests to iterate the entire cache which leads
to a lot of `SCAN` calls.
- Avoid broadcasting status update messages for Redis case.
- Setting cache expiry for status cache
- Removing .Set method altogether to prevent
any chances of setting an item with no expiry.
https://mattermost.atlassian.net/browse/MM-59932
```release-note
NONE
```
We use the same name for hit/miss/invalidation metrics.
The downside is that now we have conflated a full cache purge
and a single cache key invalidation into a single label, but
the advantage is that it's easier to monitor the usage of a single
cache.
And moreover, duplicating the same string across multiple places
increases the chances of making a mistake. Centralizing the cache
name only at instantiation simplifies the code.
After this, we would be ready to use Redis.
https://mattermost.atlassian.net/browse/MM-57193
```release-note
NONE
```
We take a second stab at fixing the issue. Last time,
we were a bit too aggressive in trying to fix a lot of stuff.
I believe changing the cluster event handler had an edge case
somewhere which caused things to fail. Taking a very conservative
approach this time to only fix what is obviously wrong.
Also tested locally in a HA setup.
https://mattermost.atlassian.net/browse/MM-53879
```release-note
NONE
```
Co-authored-by: Mattermost Build <build@mattermost.com>
* Expose metrics under a source available license
* do not assume Cluster()
* allow metrics if licensed or dev
* temporary vet override
* simplify BULID_TAGS handling
* auto clean old imports.go
* fix license listener
* e2e test metrics & license semantics
* update from enterprise
* switch back to mattermost-govet/v2@new now
* update metrics from upstream
* Update license_spec.js
Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>
* Update license_spec.js
Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>
* Update e2e-tests/cypress/tests/integration/channels/enterprise/metrics/license_spec.js
Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>
* Update e2e-tests/cypress/tests/integration/channels/enterprise/metrics/license_spec.js
* split up specs
* require/delete license earlier in e2e test
* expanded expect to debug failures
* more logging
* Revert "more logging"
This reverts commit 0bc513fd926c07a0c6050f4b4c742e856d8dc435.
* e2e: try deleting license first
* update from enterprise
* toggleMetricsOn to work around license delete
* eslint
* ensure admin before deleting license
* update from enterprise
* updates from enterprise
* fix cypress logging
* temp: log at DEBUG for Cypress tests
---------
Co-authored-by: Saturnino Abril <saturnino.abril@gmail.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
* MM-54238 Initial implementation
* MM-54238 Move websocket hook into app package
* MM-54238 Add tests for mentions in posted websocket messages
* Fix styling
* Fix other styling
* Idiomatic ID naming for new code
* Fix more styles
* Separate hooks to add mentions and followers
* Improved error handling for invalid types in hooks
* Rename HasChanges to ShouldProcess
* Pass broadcast hooks through hubStart
* Add test helper for asserting json unmarshaling
* Fix missing arguments in tests
* Ensure broadcast hooks are sent across the cluster and not to users
* Ensure tests actually cover following a post
* Fix code broken by merge
* Go vet again...
* Deep copy event before processing it with hooks
* Replace RemoveBroadcastHooks with WithoutBroadcastHooks
* Address feedback
* Add helper to fix type information for hook args
* Wrap WebSocketEvent and simplify BroadcastHook
* Address feedback
* Address feedback
There were multiple problems with loading of a license.
1. It was called from inside app/server.go and app/platform/service.go. The first one wasn't really needed anymore, so we remove it.
2. To make loading of a license work across a cluster, the license load action was attached along with the `InvalidateAllCachesSkipSend` method. But the problem with that was that it would even get called in the caller node as well, putting it in a recursive loop.
```
LoadLicense -> SaveLicense -> InvalidateAllCaches -> InvalidateAllCachesSkipSend -> LoadLicense
```
To fix this, we create a dedicated loadLicense cluster event and move it away from the `InvalidateAllCachesSkipSend` method. And then from the caller side, we just trigger this action.
3. We also remove the first call to check license expiration which would load the license again. This is unnecessary because if the license is expired, server wouldn't start at all.
While here, we also make some other improvements like removing unnecessary goroutine spawning while publishing websocket events. They are already handled asynchronously, so there is no need
to create a goroutine for that.
We also remove
```
ps.ReloadConfig()
ps.InvalidateAllCaches()
```
from requestTrialLicense as they are already called from inside `*PlatformService.SaveLicense`.
And lastly, we remove the `*model.AppError` return from `*PlatformService.InvalidateAllCaches` because there was nothing to return at all.
https://mattermost.atlassian.net/browse/MM-53879
```release-note
Fix several issues with loading of a license
```
If the store fails to initialize (e.g. run a migration), it would `log.Fatal` and then `os.Exit`. Unfortunately, this trips up `TestMain`, which happily keeps running tests, now guaranteed to fail.
Avoid this by instead returning an error from the store initialization, handling appropriately at the layer above.
Having cache layer as part of the store call
Prometheus metrics can be somewhat misleading
because cache-backed methods can have very high
volume and yet not cause any load on the DB at all.
However, if the admin is not aware which calls
are cache-backed and which are not, then
it can be hard to understand why a store call
of high volume isn't causing any stress on the DB at all.
Therefore, for simplicity we reorder the layers
such that cache layer is handled outside the timer layer.
We already have cache related metrics like hit/miss
which can be used to monitor cache usage.
https://mattermost.atlassian.net/browse/MM-53058
```release-note
NONE
```
Co-authored-by: Mattermost Build <build@mattermost.com>
It was a good decision in hindsight to keep the public module as 0.x
because this would have been a breaking change again.
https://mattermost.atlassian.net/browse/MM-53032
```release-note
Changed the Go module path from github.com/mattermost/mattermost-server/server/v8 to github.com/mattermost/mattermost/server/v8.
For the public facing module, it's path is also changed from github.com/mattermost/mattermost-server/server/public to github.com/mattermost/mattermost/server/public
```
* Avoid calling the user count query in future if we get a count > 0
* re-adding mock session to avoid adding the old mitigation in future
* adjustments based on feedback