-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
stmtstats: investigate mutex contention in ssmemstorage.(*Container).getStatsForStmtWithKey #140590
Comments
A quick experiment with this very hacky diff on top of #139557 diff
commit b99b2d6e9e34e67044c252c566c5e5354f7fb50d
Author: Tobias Grieger <[email protected]>
Date: Thu Feb 6 15:02:48 2025 +0100
[dnm] use RLock for getStatsFor{Stmt,Txn}WithKey
diff --git a/pkg/sql/sqlstats/ssmemstorage/ss_mem_storage.go b/pkg/sql/sqlstats/ssmemstorage/ss_mem_storage.go
index a0d4455e138..464d11fced9 100644
--- a/pkg/sql/sqlstats/ssmemstorage/ss_mem_storage.go
+++ b/pkg/sql/sqlstats/ssmemstorage/ss_mem_storage.go
@@ -477,8 +477,8 @@ func (s *Container) getStatsForStmt(
func (s *Container) getStatsForStmtWithKey(
key stmtKey, stmtFingerprintID appstatspb.StmtFingerprintID, createIfNonexistent bool,
) (stats *stmtStats, created, throttled bool) {
- s.mu.Lock()
- defer s.mu.Unlock()
+ s.mu.RLock()
+ defer s.mu.RUnlock()
return s.getStatsForStmtWithKeyLocked(key, stmtFingerprintID, createIfNonexistent)
}
@@ -496,8 +496,13 @@ func (s *Container) getStatsForStmtWithKeyLocked(
}
stats = &stmtStats{}
stats.ID = stmtFingerprintID
+ // Bad hack alert!
+ s.mu.RUnlock()
+ s.mu.Lock()
s.mu.stmts[key] = stats
s.mu.sampledStatementCache[key.sampledPlanKey] = struct{}{}
+ s.mu.Unlock()
+ s.mu.RLock()
return stats, true /* created */, false /* throttled */
}
@@ -509,8 +514,8 @@ func (s *Container) getStatsForTxnWithKey(
stmtFingerprintIDs []appstatspb.StmtFingerprintID,
createIfNonexistent bool,
) (stats *txnStats, created, throttled bool) {
- s.mu.Lock()
- defer s.mu.Unlock()
+ s.mu.RLock()
+ defer s.mu.RUnlock()
return s.getStatsForTxnWithKeyLocked(key, stmtFingerprintIDs, createIfNonexistent)
}
@@ -531,7 +536,12 @@ func (s *Container) getStatsForTxnWithKeyLocked(
}
stats = &txnStats{}
stats.statementFingerprintIDs = stmtFingerprintIDs
+ // Hack time.
+ s.mu.RUnlock()
+ s.mu.Lock()
s.mu.txns[key] = stats
+ s.mu.Unlock()
+ s.mu.RLock()
return stats, true /* created */, false /* throttled */
}
return stats, false /* created */, false /* throttled */
shows promising results:
Also, predictably, the mutex disappears from the contention profile I think this kind of improvement would generally benefit all SQL gateway nodes on which certain statement fingerprints are very hot, which is common enough, and certainly is common in benchmarks. I believe it's straightforward to change this code to use RLock by default, and to re-enter only when the map entry has to be created for the first time. That's a standard pattern, for example here: cockroach/pkg/kv/kvserver/store_create_replica.go Lines 161 to 185 in b99b2d6
or here: Lines 2033 to 2053 in b99b2d6
I haven't checked, but a Either way, this seems like a small effort with a definite upside, so I would recommend taking this on. |
Thank you! This is being pursued in #140393. Extracting changes from that rn, including the lock improvements, but I would expect the biggest benefit will be from moving the recording out of the execution path which will take a bit longer for us to test thoroughly. |
See #139557 for BenchmarkParallelSysbench, whose mutex profile prompted creation of this issue.
The mutex profile there is potentially interesting because it shows a very prominent contended mutex in
ssmemstorage.(*Container).getStatsForStmtWithKey
. This accounts for >50% of contention (329.36s), on a benchmark invocation that ran for ~122s. I think this could be interpreted as losing ~2.7 (=329/122) worker threads (out of 24) to contention. Perhaps one could say that throughput could be higher by a factor of 24/21.3 ≈ 1.13x. That's 13%, so it would be pretty massive. I would take this with a large grain of salt though, since I didn't see this contention as prominently in mutex profiles from a real cluster1, and instead it may be an artifact of having too many worker goroutines (we have 24 in this benchmark, but throughput is only ~10x higher than when running with a single worker). On the other hand, the mutex being hot might highlight that this is one reason linear scaling breaks down at that point.We should take a look.
Mutex
Epic: CRDB-42584
Jira issue: CRDB-47252
Footnotes
https://github.com/cockroachdb/cockroach/issues/140235 ↩
The text was updated successfully, but these errors were encountered: