mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-07 15:13:51 +08:00
⚡️ perf(kingbase): 消除 SQL 查询周期性探活延迟
- 成功 SQL 往返后刷新缓存连接健康时间 - 避免活跃连接跨过旧时间边界时同步重复 Ping - 保留空闲连接探活、失效清理与重建机制 - 新增 Kingbase 查询健康时间回归测试
This commit is contained in:
@@ -660,6 +660,25 @@ func (a *App) databaseConnectionReturnError(cacheKey string, inst db.Database) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) markCachedDatabaseHealthy(inst db.Database, healthyAt time.Time) {
|
||||
if a == nil || inst == nil {
|
||||
return
|
||||
}
|
||||
if healthyAt.IsZero() {
|
||||
healthyAt = time.Now()
|
||||
}
|
||||
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
for key, entry := range a.dbCache {
|
||||
if entry.inst != inst || !healthyAt.After(entry.lastPing) {
|
||||
continue
|
||||
}
|
||||
entry.lastPing = healthyAt
|
||||
a.dbCache[key] = entry
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) cancelDatabaseConnectFlightsLocked(match func(*databaseConnectFlight) bool, cancelErr error, excludedFlightID uint64) []string {
|
||||
groupKeys := make([]string, 0)
|
||||
for _, flight := range a.dbConnectFlights {
|
||||
|
||||
@@ -1144,6 +1144,7 @@ func (a *App) dbQueryMulti(
|
||||
// 慢 SQL 埋点:成功执行后记录(低于阈值 500ms 自动跳过)。
|
||||
// 用 named return + defer 覆盖所有 return path,避免遗漏。
|
||||
var queryExecutionDuration time.Duration
|
||||
queryExecuted := false
|
||||
defer func() {
|
||||
if !result.Success {
|
||||
return
|
||||
@@ -1153,6 +1154,7 @@ func (a *App) dbQueryMulti(
|
||||
}()
|
||||
measureQueryExecution := func(run func()) {
|
||||
startedAt := time.Now()
|
||||
queryExecuted = true
|
||||
run()
|
||||
queryExecutionDuration += time.Since(startedAt)
|
||||
}
|
||||
@@ -1190,6 +1192,12 @@ func (a *App) dbQueryMulti(
|
||||
logger.Error(err, "DBQueryMulti 获取连接失败:%s", formatConnSummary(runConfig))
|
||||
return connection.QueryResult{Success: false, Message: err.Error(), QueryID: queryID}
|
||||
}
|
||||
defer func() {
|
||||
// A successful SQL round trip is at least as strong a health signal as Ping.
|
||||
if result.Success && queryExecuted {
|
||||
a.markCachedDatabaseHealthy(dbInst, time.Now())
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := newQueryExecutionContext(runConfig)
|
||||
defer cancel()
|
||||
@@ -1298,6 +1306,7 @@ func (a *App) dbQueryMulti(
|
||||
logger.Error(retryErr, "DBQueryMulti 重建连接失败:%s SQL片段=%q", formatConnSummary(runConfig), sqlSnippet(query))
|
||||
return connection.QueryResult{Success: false, Message: retryErr.Error(), QueryID: queryID}
|
||||
}
|
||||
dbInst = retryInst
|
||||
results, resultMessages, err = runMultiQuery(retryInst)
|
||||
}
|
||||
}
|
||||
@@ -1411,6 +1420,7 @@ func (a *App) dbQueryMulti(
|
||||
logger.Error(retryErr, "DBQueryMulti 批量写重建连接失败:%s", formatConnSummary(runConfig))
|
||||
return connection.QueryResult{Success: false, Message: retryErr.Error(), QueryID: queryID}
|
||||
}
|
||||
dbInst = retryInst
|
||||
if retryBatcher, ok2 := retryInst.(db.BatchWriteExecer); ok2 {
|
||||
measureQueryExecution(func() {
|
||||
affected, batchErr = retryBatcher.ExecBatchContext(ctx, query)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
type fakeBatchWriteDB struct {
|
||||
batchCalls int
|
||||
execCalls int
|
||||
pingCalls int
|
||||
execQueries []string
|
||||
lastQuery string
|
||||
lastCtx context.Context
|
||||
@@ -114,6 +115,7 @@ func (f *fakeBatchWriteDB) Close() error {
|
||||
}
|
||||
|
||||
func (f *fakeBatchWriteDB) Ping() error {
|
||||
f.pingCalls++
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2318,6 +2320,49 @@ func TestDBQueryMultiPrefersPlainQueryForKingbaseReadResults(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBQueryMultiSuccessfulKingbaseQueryRefreshesCachedHealthTimestamp(t *testing.T) {
|
||||
installFakeOptionalDriverRuntime(t)
|
||||
|
||||
query := "SELECT * FROM ldf_server.andon_dash_events LIMIT 101 OFFSET 0"
|
||||
fakeDB := &fakeBatchWriteDB{
|
||||
queryMap: map[string][]map[string]interface{}{
|
||||
query: {
|
||||
{"id": 1},
|
||||
},
|
||||
},
|
||||
fieldMap: map[string][]string{
|
||||
query: {"id"},
|
||||
},
|
||||
queryErr: map[string]error{},
|
||||
}
|
||||
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
||||
config := connection.ConnectionConfig{
|
||||
Type: "kingbase",
|
||||
Host: "127.0.0.1",
|
||||
Port: 54321,
|
||||
User: "system",
|
||||
Database: "ldf_server_dbs_dev",
|
||||
}
|
||||
key := getCacheKey(config)
|
||||
previousHealthyAt := time.Now().Add(-10 * time.Second)
|
||||
app.dbCache[key] = cachedDatabase{
|
||||
inst: fakeDB,
|
||||
lastPing: previousHealthyAt,
|
||||
config: normalizeCacheKeyConfig(config),
|
||||
}
|
||||
|
||||
result := app.DBQueryMulti(config, config.Database, query, "kingbase-refresh-cache-health-test")
|
||||
if !result.Success {
|
||||
t.Fatalf("expected DBQueryMulti success, got failure: %s", result.Message)
|
||||
}
|
||||
if fakeDB.pingCalls != 0 {
|
||||
t.Fatalf("expected a recently healthy cached connection to skip foreground Ping, got %d calls", fakeDB.pingCalls)
|
||||
}
|
||||
if got := app.dbCache[key].lastPing; !got.After(previousHealthyAt) {
|
||||
t.Fatalf("expected successful query to refresh cached health timestamp, before=%s after=%s", previousHealthyAt, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBQueryMultiPrefersPlainQueryForDamengReadResults(t *testing.T) {
|
||||
originalNewDatabaseFunc := newDatabaseFunc
|
||||
t.Cleanup(func() {
|
||||
|
||||
Reference in New Issue
Block a user