|
| 1 | +// Copyright 2023 The Cockroach Authors. |
| 2 | +// |
| 3 | +// Use of this software is governed by the Business Source License |
| 4 | +// included in the file licenses/BSL.txt. |
| 5 | +// |
| 6 | +// As of the Change Date specified in that file, in accordance with |
| 7 | +// the Business Source License, use of this software will be governed |
| 8 | +// by the Apache License, Version 2.0, included in the file |
| 9 | +// licenses/APL.txt. |
| 10 | + |
| 11 | +// Package sqlstats is a subsystem that is responsible for tracking the |
| 12 | +// statistics of statements and transactions. |
| 13 | + |
| 14 | +package ssmemstorage |
| 15 | + |
| 16 | +import ( |
| 17 | + "context" |
| 18 | + "sync/atomic" |
| 19 | + "time" |
| 20 | + |
| 21 | + "github.com/cockroachdb/cockroach/pkg/settings" |
| 22 | + "github.com/cockroachdb/cockroach/pkg/settings/cluster" |
| 23 | + "github.com/cockroachdb/cockroach/pkg/util/log" |
| 24 | + "github.com/cockroachdb/cockroach/pkg/util/syncutil" |
| 25 | + "github.com/cockroachdb/cockroach/pkg/util/timeutil" |
| 26 | +) |
| 27 | + |
| 28 | +type SQLStatsAtomicCounters struct { |
| 29 | + st *cluster.Settings |
| 30 | + |
| 31 | + // uniqueStmtFingerprintLimit is the limit on number of unique statement |
| 32 | + // fingerprints we can store in memory. |
| 33 | + UniqueStmtFingerprintLimit *settings.IntSetting |
| 34 | + |
| 35 | + // uniqueTxnFingerprintLimit is the limit on number of unique transaction |
| 36 | + // fingerprints we can store in memory. |
| 37 | + UniqueTxnFingerprintLimit *settings.IntSetting |
| 38 | + |
| 39 | + // uniqueStmtFingerprintCount is the number of unique statement fingerprints |
| 40 | + // we are storing in memory. |
| 41 | + uniqueStmtFingerprintCount int64 |
| 42 | + |
| 43 | + // uniqueTxnFingerprintCount is the number of unique transaction fingerprints |
| 44 | + // we are storing in memory. |
| 45 | + uniqueTxnFingerprintCount int64 |
| 46 | + |
| 47 | + // discardUniqueStmtFingerprintCount is the number of unique statement |
| 48 | + // fingerprints that are discard because of memory limitations. |
| 49 | + discardUniqueStmtFingerprintCount int64 |
| 50 | + |
| 51 | + // discardUniqueTxnFingerprintCount is the number of unique transaction |
| 52 | + // fingerprints that are discard because of memory limitations. |
| 53 | + discardUniqueTxnFingerprintCount int64 |
| 54 | + |
| 55 | + mu struct { |
| 56 | + syncutil.Mutex |
| 57 | + |
| 58 | + // lastDiscardLogMessageSent is the last time a log message was sent for |
| 59 | + // statistics being discarded because of memory pressure. |
| 60 | + lastDiscardLogMessageSent time.Time |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +// DiscardedStatsLogInterval specifies the interval between log emissions for discarded |
| 65 | +// statement and transaction statistics due to reaching the SQL statistics memory limit. |
| 66 | +var DiscardedStatsLogInterval = settings.RegisterDurationSetting( |
| 67 | + settings.TenantWritable, |
| 68 | + "sql.metrics.discarded_stats_log.interval", |
| 69 | + "interval between log emissions for discarded statistics due to SQL statistics memory limit", |
| 70 | + 1*time.Minute, |
| 71 | + settings.NonNegativeDuration, |
| 72 | + settings.WithVisibility(settings.Reserved)) |
| 73 | + |
| 74 | +func NewSQLStatsAtomicCounters( |
| 75 | + st *cluster.Settings, |
| 76 | + uniqueStmtFingerprintLimit *settings.IntSetting, |
| 77 | + uniqueTxnFingerprintLimit *settings.IntSetting, |
| 78 | +) *SQLStatsAtomicCounters { |
| 79 | + return &SQLStatsAtomicCounters{ |
| 80 | + st: st, |
| 81 | + UniqueStmtFingerprintLimit: uniqueStmtFingerprintLimit, |
| 82 | + UniqueTxnFingerprintLimit: uniqueTxnFingerprintLimit, |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +// maybeLogDiscardMessage logs a warning if statement or transaction |
| 87 | +// fingerprints were discarded because of memory limits and enough time passed |
| 88 | +// since the last time the warning was logged. This is necessary to avoid |
| 89 | +// flooding the log with warnings once the limit is hit. |
| 90 | +func (s *SQLStatsAtomicCounters) maybeLogDiscardMessage(ctx context.Context) { |
| 91 | + discardSmtCnt := atomic.LoadInt64(&s.discardUniqueStmtFingerprintCount) |
| 92 | + discardTxnCnt := atomic.LoadInt64(&s.discardUniqueTxnFingerprintCount) |
| 93 | + if discardSmtCnt == 0 && discardTxnCnt == 0 { |
| 94 | + return |
| 95 | + } |
| 96 | + |
| 97 | + // Get the config values before the lock to reduce time in the lock. |
| 98 | + discardLogInterval := DiscardedStatsLogInterval.Get(&s.st.SV) |
| 99 | + stmtLimit := s.UniqueStmtFingerprintLimit.Get(&s.st.SV) |
| 100 | + txnLimit := s.UniqueTxnFingerprintLimit.Get(&s.st.SV) |
| 101 | + s.mu.Lock() |
| 102 | + defer s.mu.Unlock() |
| 103 | + timeNow := timeutil.Now() |
| 104 | + |
| 105 | + // Not enough time has passed since the last log message was sent. |
| 106 | + if timeNow.Sub(s.mu.lastDiscardLogMessageSent) < discardLogInterval { |
| 107 | + return |
| 108 | + } |
| 109 | + |
| 110 | + // The discard counts might be slightly off because it's possible that the |
| 111 | + // count changed after the initial load and before the log message is sent. |
| 112 | + // The count being slightly off won't impact users looking at the message. It |
| 113 | + // also avoids holding a lock on the counts which would block requests until |
| 114 | + // the log is sent. |
| 115 | + log.Warningf(ctx, "statistics discarded due to memory limit. transaction discard count: %d with limit: %d, statement discard count: %d with limit: %d, logged at interval: %s, last logged: %s", |
| 116 | + discardTxnCnt, stmtLimit, discardSmtCnt, txnLimit, discardLogInterval, s.mu.lastDiscardLogMessageSent) |
| 117 | + s.mu.lastDiscardLogMessageSent = timeNow |
| 118 | + |
| 119 | + // Reset the discard count back to 0 since the value was logged |
| 120 | + atomic.StoreInt64(&s.discardUniqueStmtFingerprintCount, int64(0)) |
| 121 | + atomic.StoreInt64(&s.discardUniqueTxnFingerprintCount, int64(0)) |
| 122 | +} |
| 123 | + |
| 124 | +// tryAddStmtFingerprint attempts to add 1 to the server level count for |
| 125 | +// statement level fingerprints and returns false if it is being throttled. |
| 126 | +func (s *SQLStatsAtomicCounters) tryAddStmtFingerprint() (ok bool) { |
| 127 | + limit := s.UniqueStmtFingerprintLimit.Get(&s.st.SV) |
| 128 | + |
| 129 | + // We check if we have reached the limit of unique fingerprints we can |
| 130 | + // store. |
| 131 | + incrementedFingerprintCount := |
| 132 | + atomic.AddInt64(&s.uniqueStmtFingerprintCount, int64(1) /* delts */) |
| 133 | + |
| 134 | + // Abort if we have exceeded limit of unique statement fingerprints. |
| 135 | + if incrementedFingerprintCount < limit { |
| 136 | + return true |
| 137 | + } |
| 138 | + |
| 139 | + atomic.AddInt64(&s.discardUniqueStmtFingerprintCount, int64(1)) |
| 140 | + atomic.AddInt64(&s.uniqueStmtFingerprintCount, -int64(1) /* delts */) |
| 141 | + return false |
| 142 | +} |
| 143 | + |
| 144 | +// tryAddTxnFingerprint attempts to add 1 to the server level count for |
| 145 | +// transaction level fingerprints and returns false if it is being throttled. |
| 146 | +func (s *SQLStatsAtomicCounters) tryAddTxnFingerprint() (ok bool) { |
| 147 | + limit := s.UniqueTxnFingerprintLimit.Get(&s.st.SV) |
| 148 | + |
| 149 | + // We check if we have reached the limit of unique fingerprints we can |
| 150 | + // store. |
| 151 | + incrementedFingerprintCount := |
| 152 | + atomic.AddInt64(&s.uniqueTxnFingerprintCount, int64(1) /* delts */) |
| 153 | + |
| 154 | + if incrementedFingerprintCount < limit { |
| 155 | + return true |
| 156 | + } |
| 157 | + |
| 158 | + atomic.AddInt64(&s.discardUniqueTxnFingerprintCount, int64(1)) |
| 159 | + atomic.AddInt64(&s.uniqueTxnFingerprintCount, -int64(1) /* delts */) |
| 160 | + return false |
| 161 | +} |
| 162 | + |
| 163 | +// freeByCnt decrements the statement and transaction count by the value |
| 164 | +// passed in. This is used in scenarios where an entire container which is |
| 165 | +// per an app name is being cleaned up. |
| 166 | +func (s *SQLStatsAtomicCounters) freeByCnt( |
| 167 | + uniqueStmtFingerprintCount, uniqueTxnFingerprintCount int64, |
| 168 | +) { |
| 169 | + atomic.AddInt64(&s.uniqueStmtFingerprintCount, uniqueStmtFingerprintCount) |
| 170 | + atomic.AddInt64(&s.uniqueTxnFingerprintCount, uniqueTxnFingerprintCount) |
| 171 | +} |
| 172 | + |
| 173 | +// GetTotalFingerprintCount returns total number of unique statement and |
| 174 | +// transaction fingerprints stored in the current SQLStats. |
| 175 | +func (s *SQLStatsAtomicCounters) GetTotalFingerprintCount() int64 { |
| 176 | + return atomic.LoadInt64(&s.uniqueStmtFingerprintCount) + atomic.LoadInt64(&s.uniqueTxnFingerprintCount) |
| 177 | +} |
0 commit comments