-
Notifications
You must be signed in to change notification settings - Fork 63
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
Track Postgres buffer cache usage #633
Conversation
00eef44
to
47d6489
Compare
select { | ||
case <-ctx.Done(): | ||
case ts.BufferCache = <-bufferCacheReady: | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since querying pg_buffercache
can be slow (~12 seconds for 200 GB of shared buffers), the query is performed in a separate goroutine so full snapshot collection can proceed without blocking on the buffer cache data, until it's needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that there can be num_of_servers extra connections happening concurrently? Could this be a problem with the pganalyze user connection limit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this will run an additional database connection concurrently when generating a full snapshot, so we could run into the connection limit. But I don't think that's likely to happen because by default we have a limit of 10 connections per server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I don't think that's likely to happen because by default we have a limit of 10 connections per server.
Ah true, it's per server, not per collector - yeah in that case, it's at most one additional connection per server, so it's gonna be okay 👍
I had another question around here: you say "until it's needed", and it's true - if the full snapshot finishes running (outputting buffer cache) before this ts.BufferCache = <-bufferCacheReady
, it'll output the data of 10 mins ago, correct? IOW, full snapshot with a timely buffer cache info can be obtained only when this goroutine finishes before the "outputting buffer cache" part is done.
I think it's better than making a full snapshot runs longer (so I'm not opposed to it), but just wanted to double check if my understanding is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the full snapshot finishes running (outputting buffer cache) before this
ts.BufferCache = <-bufferCacheReady
, it'll output the data of 10 mins ago, correct?
No, the code is blocking on the message from that channel. It will wait for the new buffer cache stats to be available. For our production server that has 198 GB of shared buffers, querying pg_buffercache
takes 12 seconds. I think that's short enough to not be an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we log the collection duration at Verbose level? Users could check pg_stat_statements instead, but it might be convenient to have this here for debugging.
FROM pg_buffercache | ||
GROUP BY 1, 2` | ||
|
||
func GetBufferCache(ctx context.Context, server *state.Server, globalCollectionOpts state.CollectionOpts, logger *util.Logger, postgresVersion state.PostgresVersion, channel chan state.BufferCache) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently this doesn't perform a Postgres version check or include a way to disable the functionality. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could technically disable by setting MaxBufferCacheMonitoringGB
to zero. Re: version check, what's in your mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe Lukas had mentioned that we won't want to run this for some older Postgres versions, though I don't know the details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking the release notes for all the X.0 major versions we support (I don't think a performance improvement like this would be released in a minor version), I don't see anything about pg_buffercache
except in 16, with the summary function. Going through the Postgres commit history, I see
commit 6e654546fb61f62cc982d0c8f62241b3b30e7ef8
Author: Heikki Linnakangas <[email protected]>
Date: Thu Sep 29 13:16:30 2016 +0300
Don't bother to lock bufmgr partitions in pg_buffercache.
That makes the view a lot less disruptive to use on a production system.
Without the locks, you don't get a consistent snapshot across all buffers,
but that's OK. It wasn't a very useful guarantee in practice.
Ivan Kartyshov, reviewed by Tomas Vondra and Robert Haas.
Discusssion: <[email protected]>
which I think might be what Lukas was talking about (I recall locking being mentioned). Running git tag --contains 6e654546fb61f62cc982d0c8f62241b3b30e7ef8
confirms this, showing all versions back to 10.0 including this. Going back one more version in the release notes and checking the release notes for 10 backs that up, noting the reduced locking requirement. So I don't think we need a version check, since we no longer support any versions that would have performance problems.
I agree with Keiko that setting the max to zero is probably sufficient to disable the feature.
untrackedBytes += bytes | ||
continue | ||
} | ||
s.DatabaseStatictics[databaseIdx].UntrackedCacheBytes = untrackedBytes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows us to track buffer cache usage from untracked databases and tables.
Note: input/postgres/relations.go
calls bufferCache[dataFilenode] = 0
to zero out buffer cache entries, so that the code here only tracks buffer usage not associated with a known table/index.
47d6489
to
0ff0356
Compare
@@ -212,6 +212,10 @@ type ServerConfig struct { | |||
// once the server is promoted | |||
SkipIfReplica bool `ini:"skip_if_replica"` | |||
|
|||
// The maximum shared_buffers size in gigabytes that the collector will monitor | |||
// pg_buffercache. Defaults to 200 GB. | |||
MaxBufferCacheMonitoringGB int `ini:"max_buffer_cache_monitoring_gb"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on the naming of this setting? It's difficult to find a name that's descriptive without being too long.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It reads okay to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, seems okay.
@@ -212,6 +212,10 @@ type ServerConfig struct { | |||
// once the server is promoted | |||
SkipIfReplica bool `ini:"skip_if_replica"` | |||
|
|||
// The maximum shared_buffers size in gigabytes that the collector will monitor | |||
// pg_buffercache. Defaults to 200 GB. | |||
MaxBufferCacheMonitoringGB int `ini:"max_buffer_cache_monitoring_gb"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It reads okay to me
FROM pg_buffercache | ||
GROUP BY 1, 2` | ||
|
||
func GetBufferCache(ctx context.Context, server *state.Server, globalCollectionOpts state.CollectionOpts, logger *util.Logger, postgresVersion state.PostgresVersion, channel chan state.BufferCache) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could technically disable by setting MaxBufferCacheMonitoringGB
to zero. Re: version check, what's in your mind?
select { | ||
case <-ctx.Done(): | ||
case ts.BufferCache = <-bufferCacheReady: | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean that there can be num_of_servers extra connections happening concurrently? Could this be a problem with the pganalyze user connection limit?
row.CachedDataBytes = bufferCache[dataFilenode] | ||
row.CachedToastBytes = bufferCache[toastFilenode] | ||
bufferCache[dataFilenode] = 0 | ||
bufferCache[toastFilenode] = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not understanding the logic here well for why you're setting zero here (and below). Can you please explain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah nvm, you explained it in https://github.com/pganalyze/collector/pull/633/files#r1841309323
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe that comment should be here instead? And a code comment rather than just a PR comment? I like the approach, but I, too, was confused on first reading this.
…nitoring for very large servers
0ff0356
to
0ffac89
Compare
@@ -212,6 +212,10 @@ type ServerConfig struct { | |||
// once the server is promoted | |||
SkipIfReplica bool `ini:"skip_if_replica"` | |||
|
|||
// The maximum shared_buffers size in gigabytes that the collector will monitor | |||
// pg_buffercache. Defaults to 200 GB. | |||
MaxBufferCacheMonitoringGB int `ini:"max_buffer_cache_monitoring_gb"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, seems okay.
select { | ||
case <-ctx.Done(): | ||
case ts.BufferCache = <-bufferCacheReady: | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we log the collection duration at Verbose level? Users could check pg_stat_statements instead, but it might be convenient to have this here for debugging.
input/postgres/buffer_cache.go
Outdated
sizeGB := 0 | ||
db.QueryRowContext(ctx, QueryMarkerSQL+bufferCacheSizeSQL).Scan(&sizeGB) | ||
if sizeGB > server.Config.MaxBufferCacheMonitoringGB { | ||
logger.PrintWarning("GetBufferCache: skipping collection. To enable, set max_buffer_cache_monitoring_gb to a value over %d", sizeGB) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want this printed for every full snapshot? I think there are legitimate cases where you want the extension to be present (for manual checks), but don't want the performance hit. Maybe we should only log this for a test run?
FROM pg_buffercache | ||
GROUP BY 1, 2` | ||
|
||
func GetBufferCache(ctx context.Context, server *state.Server, globalCollectionOpts state.CollectionOpts, logger *util.Logger, postgresVersion state.PostgresVersion, channel chan state.BufferCache) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking the release notes for all the X.0 major versions we support (I don't think a performance improvement like this would be released in a minor version), I don't see anything about pg_buffercache
except in 16, with the summary function. Going through the Postgres commit history, I see
commit 6e654546fb61f62cc982d0c8f62241b3b30e7ef8
Author: Heikki Linnakangas <[email protected]>
Date: Thu Sep 29 13:16:30 2016 +0300
Don't bother to lock bufmgr partitions in pg_buffercache.
That makes the view a lot less disruptive to use on a production system.
Without the locks, you don't get a consistent snapshot across all buffers,
but that's OK. It wasn't a very useful guarantee in practice.
Ivan Kartyshov, reviewed by Tomas Vondra and Robert Haas.
Discusssion: <[email protected]>
which I think might be what Lukas was talking about (I recall locking being mentioned). Running git tag --contains 6e654546fb61f62cc982d0c8f62241b3b30e7ef8
confirms this, showing all versions back to 10.0 including this. Going back one more version in the release notes and checking the release notes for 10 backs that up, noting the reduced locking requirement. So I don't think we need a version check, since we no longer support any versions that would have performance problems.
I agree with Keiko that setting the max to zero is probably sufficient to disable the feature.
input/postgres/buffer_cache.go
Outdated
// See also https://www.postgresql.org/docs/current/pgbuffercache.html | ||
const bufferCacheSQL string = ` | ||
SELECT reldatabase, relfilenode, count(*) * current_setting('block_size')::int | ||
FROM pg_buffercache |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be installed in a schema other than public, or a schema that's not in the search path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separately, here and in other SQL in this PR, we should schema-qualify references to system tables and functions with pg_catalog
, like we do elsewhere.
input/postgres/relations.go
Outdated
COALESCE(toast.relname, '') AS toast_table, | ||
coalesce(pg_relation_filenode(c.oid), 0) AS data_filenode, | ||
coalesce(pg_relation_filenode(c.reltoastrelid), 0) AS toast_filenode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
COALESCE(toast.relname, '') AS toast_table, | |
coalesce(pg_relation_filenode(c.oid), 0) AS data_filenode, | |
coalesce(pg_relation_filenode(c.reltoastrelid), 0) AS toast_filenode | |
COALESCE(toast.relname, '') AS toast_table, | |
COALESCE(pg_relation_filenode(c.oid), 0) AS data_filenode, | |
COALESCE(pg_relation_filenode(c.reltoastrelid), 0) AS toast_filenode |
Or the other way around (assuming you change other occurrences here). I don't especially care, but the inconsistency hurts readability.
row.CachedDataBytes = bufferCache[dataFilenode] | ||
row.CachedToastBytes = bufferCache[toastFilenode] | ||
bufferCache[dataFilenode] = 0 | ||
bufferCache[toastFilenode] = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe that comment should be here instead? And a code comment rather than just a PR comment? I like the approach, but I, too, was confused on first reading this.
GROUP BY 1, 2` | ||
const bufferCacheExtensionSQL string = ` | ||
SELECT COALESCE(( | ||
SELECT nspname |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably be calling quote_literal(nspname)
here, to make sure we support extensions that are installed in schemas that require quoting (e.g., "this schema has spaces"), but I noticed that our handling of the pg_stat_statements schema doesn't do this either, and I don't think we've heard complaints, so we can leave this and fix both in a follow-up patch instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, looks good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like Maciek dropped a few more feedback but otherwise, looks good to me too!
(also nice reviews, Maciek. +1 on all points you made)
This PR adds Postgres buffer cache tracking on a per-table/index basis.
Since querying
pg_buffercache
is slow and grows slower with larger buffer cache sizes, by default we will not collect buffer cache stats for a database with more than 200 GB ofshared_buffers
. This can be configured with themax_buffer_cache_monitoring_gb
setting, which accepts an integer number of gigabytes to use as the threshold.