mirror of
https://github.com/mattermost/mattermost.git
synced 2026-09-21 05:54:10 +08:00
MM-67505 Add AnalyticsQueryTimeout setting and use when refreshing materialized views (#35906)
* MM-67505 Add AnalyticsQueryTimeout setting and use when refreshing materialized views * Fix last minute i18n change * Disallow 0 values for AnalyticsQueryTimeout * Fix E2E test config * Fix post store tests crashing * Update snapshot and revert accidental changes to it
This commit is contained in:
@@ -292,6 +292,7 @@
|
||||
"PublicCertificateFile": "",
|
||||
"PrivateKeyFile": "",
|
||||
"QueryTimeout": 60,
|
||||
"AnalyticsQueryTimeout": 300,
|
||||
"MaxPageSize": 500,
|
||||
"LoginFieldName": "",
|
||||
"LoginButtonColor": "#0000",
|
||||
|
||||
@@ -152,6 +152,7 @@
|
||||
"Trace": false,
|
||||
"AtRestEncryptKey": "",
|
||||
"QueryTimeout": 30,
|
||||
"AnalyticsQueryTimeout": 300,
|
||||
"DisableDatabaseSearch": false,
|
||||
"MigrationsStatementTimeoutSeconds": 100000,
|
||||
"ReplicaLagSettings": [],
|
||||
|
||||
@@ -259,6 +259,7 @@ const defaultServerConfig: AdminConfig = {
|
||||
Trace: false,
|
||||
AtRestEncryptKey: '',
|
||||
QueryTimeout: 30,
|
||||
AnalyticsQueryTimeout: 300,
|
||||
DisableDatabaseSearch: false,
|
||||
MigrationsStatementTimeoutSeconds: 100000,
|
||||
ReplicaLagSettings: [],
|
||||
|
||||
@@ -794,12 +794,15 @@ func (fs SqlFileInfoStore) RestoreForPostByIds(rctx request.CTX, postId string,
|
||||
}
|
||||
|
||||
func (fs SqlFileInfoStore) RefreshFileStats() error {
|
||||
ctx, cancel := fs.analyticsContext()
|
||||
defer cancel()
|
||||
|
||||
// CONCURRENTLY is not used deliberately because as per Postgres docs,
|
||||
// not using CONCURRENTLY takes less resources and completes faster
|
||||
// at the expense of locking the mat view. Since viewing admin console
|
||||
// is not a very frequent activity, we accept the tradeoff to let the
|
||||
// refresh happen as fast as possible.
|
||||
if _, err := fs.GetMaster().Exec("REFRESH MATERIALIZED VIEW file_stats"); err != nil {
|
||||
if _, err := fs.GetMaster().ExecContext(ctx, "REFRESH MATERIALIZED VIEW file_stats"); err != nil {
|
||||
return errors.Wrap(err, "error refreshing materialized view file_stats")
|
||||
}
|
||||
|
||||
|
||||
@@ -3267,11 +3267,18 @@ func (s *SqlPostStore) RefreshPostStats() error {
|
||||
// at the expense of locking the mat view. Since viewing admin console
|
||||
// is not a very frequent activity, we accept the tradeoff to let the
|
||||
// refresh happen as fast as possible.
|
||||
if _, err := s.GetMaster().Exec("REFRESH MATERIALIZED VIEW posts_by_team_day"); err != nil {
|
||||
|
||||
postsCtx, postsCancel := s.analyticsContext()
|
||||
defer postsCancel()
|
||||
|
||||
if _, err := s.GetMaster().ExecContext(postsCtx, "REFRESH MATERIALIZED VIEW posts_by_team_day"); err != nil {
|
||||
return errors.Wrap(err, "error refreshing materialized view posts_by_team_day")
|
||||
}
|
||||
|
||||
if _, err := s.GetMaster().Exec("REFRESH MATERIALIZED VIEW bot_posts_by_team_day"); err != nil {
|
||||
botPostsCtx, botPostsCancel := s.analyticsContext()
|
||||
defer botPostsCancel()
|
||||
|
||||
if _, err := s.GetMaster().ExecContext(botPostsCtx, "REFRESH MATERIALIZED VIEW bot_posts_by_team_day"); err != nil {
|
||||
return errors.Wrap(err, "error refreshing materialized view bot_posts_by_team_day")
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"path"
|
||||
@@ -474,6 +475,10 @@ func (ss *SqlStore) GetReplica() *sqlxDBWrapper {
|
||||
return ss.GetMaster()
|
||||
}
|
||||
|
||||
func (ss *SqlStore) analyticsContext() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), time.Duration(*ss.settings.AnalyticsQueryTimeout)*time.Second)
|
||||
}
|
||||
|
||||
func (ss *SqlStore) monitorReplicas() {
|
||||
t := time.NewTicker(time.Duration(*ss.settings.ReplicaMonitorIntervalSeconds) * time.Second)
|
||||
defer func() {
|
||||
|
||||
@@ -2374,9 +2374,13 @@ func (us SqlUserStore) GetUsersWithInvalidEmails(page int, perPage int, restrict
|
||||
}
|
||||
|
||||
func (us SqlUserStore) RefreshPostStatsForUsers() error {
|
||||
if _, err := us.GetMaster().Exec("REFRESH MATERIALIZED VIEW poststats"); err != nil {
|
||||
ctx, cancel := us.analyticsContext()
|
||||
defer cancel()
|
||||
|
||||
if _, err := us.GetMaster().ExecContext(ctx, "REFRESH MATERIALIZED VIEW poststats"); err != nil {
|
||||
return errors.Wrap(err, "users_refresh_post_stats_exec")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ func databaseSettings(driver, dataSource string) *model.SqlSettings {
|
||||
Trace: model.NewPointer(false),
|
||||
AtRestEncryptKey: model.NewPointer(model.NewRandomString(32)),
|
||||
QueryTimeout: new(int),
|
||||
AnalyticsQueryTimeout: new(int),
|
||||
MigrationsStatementTimeoutSeconds: new(int),
|
||||
}
|
||||
*settings.MaxIdleConns = 10
|
||||
@@ -97,6 +98,7 @@ func databaseSettings(driver, dataSource string) *model.SqlSettings {
|
||||
*settings.ConnMaxIdleTimeMilliseconds = 300000
|
||||
*settings.MaxOpenConns = 100
|
||||
*settings.QueryTimeout = 60
|
||||
*settings.AnalyticsQueryTimeout = 300
|
||||
*settings.MigrationsStatementTimeoutSeconds = 60
|
||||
|
||||
return settings
|
||||
|
||||
@@ -11028,6 +11028,10 @@
|
||||
"id": "model.config.is_valid.sitename_length.app_error",
|
||||
"translation": "Site name must be less than or equal to {{.MaxLength}} characters."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.sql_analytics_query_timeout.app_error",
|
||||
"translation": "Invalid analytics query timeout for SQL settings. Must be a positive number."
|
||||
},
|
||||
{
|
||||
"id": "model.config.is_valid.sql_conn_max_idle_time_milliseconds.app_error",
|
||||
"translation": "Invalid connection maximum idle time for SQL settings. Must be a non-negative number."
|
||||
|
||||
@@ -1491,6 +1491,7 @@ type SqlSettings struct {
|
||||
Trace *bool `access:"environment_database,write_restrictable,cloud_restrictable"`
|
||||
AtRestEncryptKey *string `access:"environment_database,write_restrictable,cloud_restrictable"` // telemetry: none
|
||||
QueryTimeout *int `access:"environment_database,write_restrictable,cloud_restrictable"`
|
||||
AnalyticsQueryTimeout *int `access:"environment_database,write_restrictable,cloud_restrictable"`
|
||||
DisableDatabaseSearch *bool `access:"environment_database,write_restrictable,cloud_restrictable"`
|
||||
MigrationsStatementTimeoutSeconds *int `access:"environment_database,write_restrictable,cloud_restrictable"`
|
||||
ReplicaLagSettings []*ReplicaLagSettings `access:"environment_database,write_restrictable,cloud_restrictable"` // telemetry: none
|
||||
@@ -1548,6 +1549,10 @@ func (s *SqlSettings) SetDefaults(isUpdate bool) {
|
||||
s.QueryTimeout = NewPointer(30)
|
||||
}
|
||||
|
||||
if s.AnalyticsQueryTimeout == nil {
|
||||
s.AnalyticsQueryTimeout = NewPointer(300)
|
||||
}
|
||||
|
||||
if s.DisableDatabaseSearch == nil {
|
||||
s.DisableDatabaseSearch = NewPointer(false)
|
||||
}
|
||||
@@ -4366,6 +4371,10 @@ func (s *SqlSettings) isValid() *AppError {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.sql_query_timeout.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if *s.AnalyticsQueryTimeout <= 0 {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.sql_analytics_query_timeout.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if *s.DataSource == "" {
|
||||
return NewAppError("Config.IsValid", "model.config.is_valid.sql_data_src.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
+31
@@ -168,6 +168,37 @@ exports[`components/DatabaseSettings should match snapshot 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="form-group"
|
||||
data-testid="analyticsQueryTimeout"
|
||||
>
|
||||
<label
|
||||
class="control-label col-sm-4"
|
||||
data-testid="analyticsQueryTimeoutlabel"
|
||||
for="analyticsQueryTimeout"
|
||||
>
|
||||
Analytics Query Timeout:
|
||||
</label>
|
||||
<div
|
||||
class="col-sm-8"
|
||||
>
|
||||
<input
|
||||
class="form-control"
|
||||
data-testid="analyticsQueryTimeoutinput"
|
||||
id="analyticsQueryTimeout"
|
||||
maxlength="-1"
|
||||
placeholder="E.g.: \\"300\\""
|
||||
type="text"
|
||||
value="300"
|
||||
/>
|
||||
<div
|
||||
class="help-text"
|
||||
data-testid="analyticsQueryTimeouthelp-text"
|
||||
>
|
||||
The number of seconds to wait for a response from the database after opening a connection and sending certain analytics queries. This setting only applies to long queries which are run in the background to populate some information in the Team and Site Statistics pages.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="form-group"
|
||||
data-testid="connMaxLifetimeMilliseconds"
|
||||
|
||||
@@ -35,6 +35,7 @@ describe('components/DatabaseSettings', () => {
|
||||
DisableDatabaseSearch: true,
|
||||
DataSource: 'postgres://mmuser:mostest@localhost/mattermost_test?sslmode=disable\u0026connect_timeout=10',
|
||||
QueryTimeout: 10,
|
||||
AnalyticsQueryTimeout: 300,
|
||||
ConnMaxLifetimeMilliseconds: 10,
|
||||
ConnMaxIdleTimeMilliseconds: 20,
|
||||
},
|
||||
|
||||
@@ -36,6 +36,7 @@ interface State extends BaseState {
|
||||
trace: boolean;
|
||||
disableDatabaseSearch: boolean;
|
||||
queryTimeout: number;
|
||||
analyticsQueryTimeout: number;
|
||||
connMaxLifetimeMilliseconds: number;
|
||||
connMaxIdleTimeMilliseconds: number;
|
||||
minimumHashtagLength: number;
|
||||
@@ -62,6 +63,8 @@ const messages = defineMessages({
|
||||
maxOpenDescription: {id: 'admin.sql.maxOpenDescription', defaultMessage: 'Maximum number of open connections held open to the database.'},
|
||||
queryTimeoutTitle: {id: 'admin.sql.queryTimeoutTitle', defaultMessage: 'Query Timeout:'},
|
||||
queryTimeoutDescription: {id: 'admin.sql.queryTimeoutDescription', defaultMessage: 'The number of seconds to wait for a response from the database after opening a connection and sending the query. Errors that you see in the UI or in the logs as a result of a query timeout can vary depending on the type of query.'},
|
||||
analyticsQueryTimeoutTitle: {id: 'admin.sql.analyticsQueryTimeoutTitle', defaultMessage: 'Analytics Query Timeout:'},
|
||||
analyticsQueryTimeoutDescription: {id: 'admin.sql.analyticsQueryTimeoutDescription', defaultMessage: 'The number of seconds to wait for a response from the database after opening a connection and sending certain analytics queries. This setting only applies to long queries which are run in the background to populate some information in the Team and Site Statistics pages.'},
|
||||
connMaxLifetimeTitle: {id: 'admin.sql.connMaxLifetimeTitle', defaultMessage: 'Maximum Connection Lifetime:'},
|
||||
connMaxLifetimeDescription: {id: 'admin.sql.connMaxLifetimeDescription', defaultMessage: 'Maximum lifetime for a connection to the database in milliseconds.'},
|
||||
connMaxIdleTimeTitle: {id: 'admin.sql.connMaxIdleTimeTitle', defaultMessage: 'Maximum Connection Idle Time:'},
|
||||
@@ -91,6 +94,8 @@ export const searchableStrings: Array<string|MessageDescriptor|[MessageDescripto
|
||||
messages.maxOpenDescription,
|
||||
messages.queryTimeoutTitle,
|
||||
messages.queryTimeoutDescription,
|
||||
messages.analyticsQueryTimeoutTitle,
|
||||
messages.analyticsQueryTimeoutDescription,
|
||||
messages.connMaxLifetimeTitle,
|
||||
messages.connMaxLifetimeDescription,
|
||||
messages.connMaxIdleTimeTitle,
|
||||
@@ -117,6 +122,7 @@ export default class DatabaseSettings extends OLDAdminSettings<Props, State> {
|
||||
config.SqlSettings.Trace = this.state.trace;
|
||||
config.SqlSettings.DisableDatabaseSearch = this.state.disableDatabaseSearch;
|
||||
config.SqlSettings.QueryTimeout = this.parseIntNonZero(this.state.queryTimeout);
|
||||
config.SqlSettings.AnalyticsQueryTimeout = this.parseIntNonZero(this.state.analyticsQueryTimeout);
|
||||
config.SqlSettings.ConnMaxLifetimeMilliseconds = this.parseIntNonNegative(this.state.connMaxLifetimeMilliseconds);
|
||||
config.SqlSettings.ConnMaxIdleTimeMilliseconds = this.parseIntNonNegative(this.state.connMaxIdleTimeMilliseconds);
|
||||
config.ServiceSettings.MinimumHashtagLength = this.parseIntNonZero(this.state.minimumHashtagLength, 3, 2);
|
||||
@@ -144,6 +150,7 @@ export default class DatabaseSettings extends OLDAdminSettings<Props, State> {
|
||||
trace: config.SqlSettings.Trace,
|
||||
disableDatabaseSearch: config.SqlSettings.DisableDatabaseSearch,
|
||||
queryTimeout: config.SqlSettings.QueryTimeout,
|
||||
analyticsQueryTimeout: config.SqlSettings.AnalyticsQueryTimeout,
|
||||
connMaxLifetimeMilliseconds: config.SqlSettings.ConnMaxLifetimeMilliseconds,
|
||||
connMaxIdleTimeMilliseconds: config.SqlSettings.ConnMaxIdleTimeMilliseconds,
|
||||
minimumHashtagLength: config.ServiceSettings.MinimumHashtagLength,
|
||||
@@ -284,6 +291,21 @@ export default class DatabaseSettings extends OLDAdminSettings<Props, State> {
|
||||
disabled={this.props.isDisabled}
|
||||
type='text'
|
||||
/>
|
||||
<TextSetting
|
||||
id='analyticsQueryTimeout'
|
||||
label={
|
||||
<FormattedMessage {...messages.analyticsQueryTimeoutTitle}/>
|
||||
}
|
||||
placeholder={defineMessage({id: 'admin.sql.analyticsQueryTimeoutExample', defaultMessage: 'E.g.: "300"'})}
|
||||
helpText={
|
||||
<FormattedMessage {...messages.analyticsQueryTimeoutDescription}/>
|
||||
}
|
||||
value={this.state.analyticsQueryTimeout}
|
||||
onChange={this.handleChange}
|
||||
setByEnv={this.isSetByEnv('SqlSettings.AnalyticsQueryTimeout')}
|
||||
disabled={this.props.isDisabled}
|
||||
type='text'
|
||||
/>
|
||||
<TextSetting
|
||||
id='connMaxLifetimeMilliseconds'
|
||||
label={
|
||||
|
||||
@@ -2912,6 +2912,9 @@
|
||||
"admin.site.posts": "Posts",
|
||||
"admin.site.public_links": "Public Links",
|
||||
"admin.site.usersAndTeams": "Users and Teams",
|
||||
"admin.sql.analyticsQueryTimeoutDescription": "The number of seconds to wait for a response from the database after opening a connection and sending certain analytics queries. This setting only applies to long queries which are run in the background to populate some information in the Team and Site Statistics pages.",
|
||||
"admin.sql.analyticsQueryTimeoutExample": "E.g.: \"300\"",
|
||||
"admin.sql.analyticsQueryTimeoutTitle": "Analytics Query Timeout:",
|
||||
"admin.sql.connMaxIdleTimeDescription": "Maximum idle time for a connection to the database in milliseconds.",
|
||||
"admin.sql.connMaxIdleTimeExample": "E.g.: \"300000\"",
|
||||
"admin.sql.connMaxIdleTimeTitle": "Maximum Connection Idle Time:",
|
||||
|
||||
@@ -486,6 +486,7 @@ export type SqlSettings = {
|
||||
Trace: boolean;
|
||||
AtRestEncryptKey: string;
|
||||
QueryTimeout: number;
|
||||
AnalyticsQueryTimeout: number;
|
||||
DisableDatabaseSearch: boolean;
|
||||
MigrationsStatementTimeoutSeconds: number;
|
||||
ReplicaLagSettings: ReplicaLagSetting[];
|
||||
|
||||
Reference in New Issue
Block a user