grafana/pkg/infra/db/db.go
Kristin Laemmert 50244ed4a1
Experimental Feature Toggle: databaseReadReplica (#89232)
This adds a version of the SQLStore that includes a ReadReplica. The primary DB can be accessed directly - from the caller's standpoint, there is no difference between the SQLStore and ReplStore unless they wish to explicitly call the ReadReplica() and use that for the DB sessions.

Currently only the stats service GetSystemStats and GetAdminStats are using the ReadReplica(); if it's misconfigured or if the databaseReadReplica feature flag is not turned on, it will fall back to the usual (SQLStore) behavior.

Testing requires a database and read replica - the replication should already be configured. I have been testing this locally with a docker mysql setup (https://medium.com/@vbabak/docker-mysql-master-slave-replication-setup-2ff553fceef2) and the following config:

[feature_toggles]
databaseReadReplica = true

[database]
type = mysql
name = grafana
user = grafana
password = password
host = 127.0.0.1:3306

[database_replica]
type = mysql
name = grafana
user = grafana
password = password
host = 127.0.0.1:3307
2024-06-18 11:07:15 -04:00

109 lines
3.8 KiB
Go

package db
import (
"context"
"os"
"xorm.io/core"
"xorm.io/xorm"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/session"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/grafana/grafana/pkg/setting"
)
type DB interface {
// WithTransactionalDbSession creates a new SQL transaction to ensure consistency
// for the database operations done within the [sqlstore.DBTransactionFunc].
// It's better to combine InTransaction and WithDbSession instead, as the context
// variable is not updated when using this method.
WithTransactionalDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error
// WithDbSession runs database operations either in an existing transaction available
// through [context.Context] or if that's not present, as non-transactional database
// operations.
WithDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error
// WithNewDbSession behaves like [DB.WithDbSession] without picking up a transaction
// from the context.
WithNewDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error
// GetDialect returns an object that contains information about the peculiarities of
// the particular database type available to the runtime.
GetDialect() migrator.Dialect
// GetDBType returns the name of the database type available to the runtime.
GetDBType() core.DbType
// GetEngine returns the underlying xorm engine.
GetEngine() *xorm.Engine
// GetSqlxSession is an experimental extension to use sqlx instead of xorm to
// communicate with the database.
// NOTE: when using this session with mysql, the connection will *not* have:
// the expected parameters: "&sql_mode='ANSI_QUOTES" and "&parseTime=true"
// The sqlx session is useful, but be careful not to expect automagic date parsing
GetSqlxSession() *session.SessionDB
// InTransaction creates a new SQL transaction that is placed on the context.
// Use together with [DB.WithDbSession] to run database operations.
InTransaction(ctx context.Context, fn func(ctx context.Context) error) error
// Quote wraps an identifier so that it cannot be mistaken for an SQL keyword.
Quote(value string) string
// RecursiveQueriesAreSupported runs a dummy recursive query and it returns true
// if the query runs successfully or false if it fails with mysqlerr.ER_PARSE_ERROR error or any other error
RecursiveQueriesAreSupported() (bool, error)
}
type Session = sqlstore.DBSession
type InitTestDBOpt = sqlstore.InitTestDBOpt
var SetupTestDB = sqlstore.SetupTestDB
var CleanupTestDB = sqlstore.CleanupTestDB
var ProvideService = sqlstore.ProvideService
func InitTestDB(t sqlutil.ITestDB, opts ...InitTestDBOpt) *sqlstore.SQLStore {
db, _ := InitTestDBWithCfg(t, opts...)
return db
}
func InitTestReplDBWithCfg(t sqlutil.ITestDB, opts ...InitTestDBOpt) (*sqlstore.ReplStore, *setting.Cfg) {
return sqlstore.InitTestReplDB(t, opts...)
}
func InitTestReplDB(t sqlutil.ITestDB, opts ...InitTestDBOpt) *sqlstore.ReplStore {
db, _ := InitTestReplDBWithCfg(t, opts...)
return db
}
func InitTestDBWithCfg(t sqlutil.ITestDB, opts ...InitTestDBOpt) (*sqlstore.SQLStore, *setting.Cfg) {
return sqlstore.InitTestDB(t, opts...)
}
func IsTestDbSQLite() bool {
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); !present || db == "sqlite" {
return true
}
return !IsTestDbMySQL() && !IsTestDbPostgres()
}
func IsTestDbMySQL() bool {
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
return db == migrator.MySQL
}
return false
}
func IsTestDbPostgres() bool {
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
return db == migrator.Postgres
}
return false
}
func IsTestDBMSSQL() bool {
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
return db == migrator.MSSQL
}
return false
}