diff --git a/message/cache.go b/message/cache.go index 710d5f1e..2542ebcf 100644 --- a/message/cache.go +++ b/message/cache.go @@ -300,7 +300,8 @@ func (c *Cache) MarkPublished(m *model.Message) error { return err } -// MessagesCount returns the total number of messages in the cache +// MessagesCount returns the total number of messages in the cache. On Postgres, this is the +// planner's estimate once the table has been analyzed, not an exact count. func (c *Cache) MessagesCount() (int, error) { rows, err := c.db.ReadOnly().Query(c.queries.selectMessagesCount) if err != nil { diff --git a/message/cache_postgres.go b/message/cache_postgres.go index efecb8de..b921ae7e 100644 --- a/message/cache_postgres.go +++ b/message/cache_postgres.go @@ -62,8 +62,13 @@ const ( ORDER BY time, id ` postgresUpdateMessagePublishedQuery = `UPDATE message SET published = TRUE WHERE mid = $1` - postgresSelectMessagesCountQuery = `SELECT COUNT(*) FROM message` - postgresSelectTopicsQuery = `SELECT topic FROM message GROUP BY topic` + // Planner estimate, since a COUNT(*) scans the whole table; reltuples is -1 if never analyzed + postgresSelectMessagesCountQuery = ` + SELECT CASE WHEN reltuples < 0 THEN (SELECT COUNT(*) FROM message) ELSE reltuples::BIGINT END + FROM pg_class + WHERE oid = 'message'::regclass + ` + postgresSelectTopicsQuery = `SELECT topic FROM message GROUP BY topic` postgresDeleteExpiredMessagesQuery = `DELETE FROM message WHERE mid IN (SELECT mid FROM message WHERE expires <= $1 AND published = TRUE LIMIT $2)` postgresMarkExpiredAttachmentsDeletedQuery = `UPDATE message SET attachment_deleted = TRUE WHERE mid IN (SELECT mid FROM message WHERE attachment_expires > 0 AND attachment_expires <= $1 AND attachment_deleted = FALSE LIMIT $2)` diff --git a/message/cache_postgres_test.go b/message/cache_postgres_test.go index c8535862..6a3e6c39 100644 --- a/message/cache_postgres_test.go +++ b/message/cache_postgres_test.go @@ -74,3 +74,28 @@ func TestPostgresStore_Migration_From14(t *testing.T) { require.Nil(t, err) require.Equal(t, dbtest.PostgresSchema(t, freshDB), dbtest.PostgresSchema(t, testDB)) } + +func TestPostgresStore_MessagesCount_UsesPlannerEstimate(t *testing.T) { + // The manager calls MessagesCount every minute for a metric; a COUNT(*) scans the whole + // table on every call, so once the table has been analyzed, the planner's estimate is used + testDB := dbtest.CreateTestPostgres(t) + store, err := message.NewPostgresStore(testDB, 0, 0) + require.Nil(t, err) + for i := 0; i < 10; i++ { + require.Nil(t, store.AddMessage(model.NewDefaultMessage("mytopic", "some message"))) + } + + // Never analyzed: falls back to an exact count + count, err := store.MessagesCount() + require.Nil(t, err) + require.Equal(t, 10, count) + + // Analyzed, then rows deleted: the estimate lags until the next (auto)analyze + _, err = testDB.Exec(`ANALYZE message`) + require.Nil(t, err) + _, err = testDB.Exec(`DELETE FROM message WHERE id IN (SELECT id FROM message LIMIT 4)`) + require.Nil(t, err) + count, err = store.MessagesCount() + require.Nil(t, err) + require.Equal(t, 10, count) +}