-
Notifications
You must be signed in to change notification settings - Fork 247
/
stats.go
137 lines (117 loc) · 3.76 KB
/
stats.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package crdb
import (
"context"
"fmt"
"slices"
"github.com/Masterminds/squirrel"
"github.com/jackc/pgx/v5"
"github.com/rs/zerolog/log"
pgxcommon "github.com/authzed/spicedb/internal/datastore/postgres/common"
"github.com/authzed/spicedb/pkg/datastore"
)
const (
tableMetadata = "metadata"
colUniqueID = "unique_id"
)
var (
queryReadUniqueID = psql.Select(colUniqueID).From(tableMetadata)
uniqueID string
)
func (cds *crdbDatastore) Statistics(ctx context.Context) (datastore.Stats, error) {
if len(uniqueID) == 0 {
sql, args, err := queryReadUniqueID.ToSql()
if err != nil {
return datastore.Stats{}, fmt.Errorf("unable to prepare unique ID sql: %w", err)
}
if err := cds.readPool.QueryRowFunc(ctx, func(ctx context.Context, row pgx.Row) error {
return row.Scan(&uniqueID)
}, sql, args...); err != nil {
return datastore.Stats{}, fmt.Errorf("unable to query unique ID: %w", err)
}
}
var nsDefs []datastore.RevisionedNamespace
if err := cds.readPool.BeginTxFunc(ctx, pgx.TxOptions{AccessMode: pgx.ReadOnly}, func(tx pgx.Tx) error {
_, err := tx.Exec(ctx, "SET TRANSACTION AS OF SYSTEM TIME follower_read_timestamp()")
if err != nil {
return fmt.Errorf("unable to read namespaces: %w", err)
}
nsDefs, err = loadAllNamespaces(ctx, pgxcommon.QuerierFuncsFor(tx), func(sb squirrel.SelectBuilder, fromStr string) squirrel.SelectBuilder {
return sb.From(fromStr)
})
if err != nil {
return fmt.Errorf("unable to read namespaces: %w", err)
}
return nil
}); err != nil {
return datastore.Stats{}, err
}
if cds.analyzeBeforeStatistics {
if err := cds.readPool.BeginTxFunc(ctx, pgx.TxOptions{AccessMode: pgx.ReadOnly}, func(tx pgx.Tx) error {
if _, err := tx.Exec(ctx, "ANALYZE "+tableTuple); err != nil {
return fmt.Errorf("unable to analyze tuple table: %w", err)
}
return nil
}); err != nil {
return datastore.Stats{}, err
}
}
var estimatedRelCount uint64
if err := cds.readPool.QueryFunc(ctx, func(ctx context.Context, rows pgx.Rows) error {
hasRows := false
for rows.Next() {
hasRows = true
values, err := rows.Values()
if err != nil {
log.Warn().Err(err).Msg("unable to read statistics")
return nil
}
// Find the row whose column_names contains the expected columns for the
// full relationship.
isFullRelationshipRow := false
for index, fd := range rows.FieldDescriptions() {
if fd.Name != "column_names" {
continue
}
columnNames, ok := values[index].([]any)
if !ok {
log.Warn().Msg("unable to read column names")
return nil
}
if slices.Contains(columnNames, "namespace") &&
slices.Contains(columnNames, "object_id") &&
slices.Contains(columnNames, "relation") &&
slices.Contains(columnNames, "userset_namespace") &&
slices.Contains(columnNames, "userset_object_id") &&
slices.Contains(columnNames, "userset_relation") {
isFullRelationshipRow = true
break
}
}
if !isFullRelationshipRow {
continue
}
// Read the estimated relationship count.
for index, fd := range rows.FieldDescriptions() {
if fd.Name != "row_count" {
continue
}
rowCount, ok := values[index].(int64)
if !ok {
log.Warn().Msg("unable to read row count")
return nil
}
estimatedRelCount = uint64(rowCount)
return nil
}
}
log.Warn().Bool("has-rows", hasRows).Msg("unable to find row count in statistics query result")
return nil
}, "SHOW STATISTICS FOR TABLE relation_tuple;"); err != nil {
return datastore.Stats{}, fmt.Errorf("unable to query unique estimated row count: %w", err)
}
return datastore.Stats{
UniqueID: uniqueID,
EstimatedRelationshipCount: estimatedRelCount,
ObjectTypeStatistics: datastore.ComputeObjectTypeStats(nsDefs),
}, nil
}