Skip to content

Commit 1d4eed7

Browse files
committed
cluster vs non-cluster mode
1 parent 61fcc42 commit 1d4eed7

File tree

2 files changed

+77
-38
lines changed

2 files changed

+77
-38
lines changed

exporter/keys.go

Lines changed: 73 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,75 @@ type keyInfo struct {
2424

2525
var errKeyTypeNotFound = fmt.Errorf("key not found")
2626

27+
func getStringInfoNotPipelined(c redis.Conn, key string) (keyInfo, error) {
28+
var info keyInfo
29+
var err error
30+
var size int64
31+
32+
// Check PFCOUNT first because STRLEN on HyperLogLog strings returns the wrong length
33+
// while PFCOUNT only works on HLL strings and returns an error on regular strings.
34+
//
35+
// no pipelining / batching for cluster mode, it's not supported
36+
if size, err = redis.Int64(doRedisCmd(c, "PFCOUNT", key)); err == nil {
37+
// hyperloglog
38+
info.size = float64(size)
39+
info.keyType = "HLL"
40+
return info, nil
41+
} else if size, err = redis.Int64(doRedisCmd(c, "STRLEN", key)); err == nil {
42+
info.size = float64(size)
43+
info.keyType = "string"
44+
return info, nil
45+
}
46+
return info, err
47+
}
48+
49+
func getStringInfoPipelined(c redis.Conn, key string) (keyInfo, error) {
50+
var info keyInfo
51+
//
52+
// the following two commands are pipelined/batched to improve performance
53+
// by removing one roundtrip to the redis instance
54+
// see https://github.com/oliver006/redis_exporter/issues/980
55+
//
56+
// This will send both PFCOUNT and STRLEN and then check PFCOUNT first
57+
// For hyperloglog keys this will call STRLEN anyway but it saves the roundtrip
58+
//
59+
// STRLEN on HyperLogLog strings returns the wrong length while PFCOUNT only
60+
// works on HLL strings and returns an error on regular strings.
61+
if err := c.Send("PFCOUNT", key); err != nil {
62+
return info, err
63+
}
64+
if err := c.Send("STRLEN", key); err != nil {
65+
return info, err
66+
}
67+
if err := c.Flush(); err != nil {
68+
return info, err
69+
}
70+
71+
hllSize, hllErr := redis.Int64(c.Receive())
72+
strSize, strErr := redis.Int64(c.Receive())
73+
if hllErr == nil {
74+
// hyperloglog
75+
info.size = float64(hllSize)
76+
77+
// "TYPE" reports hll as string
78+
// this will prevent treating the result as a string by the caller (e.g. call GET)
79+
info.keyType = "HLL"
80+
} else if strErr == nil {
81+
// not hll so possibly a string?
82+
info.size = float64(strSize)
83+
info.keyType = "string"
84+
} else {
85+
// something went wrong, return the error(s)
86+
return info, fmt.Errorf("hllErr: %w strErr: %w", hllErr, strErr)
87+
}
88+
return info, nil
89+
}
90+
2791
// getKeyInfo takes a key and returns the type, and the size or length of the value stored at that key.
28-
func getKeyInfo(c redis.Conn, key string) (info keyInfo, err error) {
92+
func getKeyInfo(c redis.Conn, key string, isCluster bool) (keyInfo, error) {
93+
var info keyInfo
94+
var err error
95+
2996
if info.keyType, err = redis.String(doRedisCmd(c, "TYPE", key)); err != nil {
3097
return info, err
3198
}
@@ -34,41 +101,12 @@ func getKeyInfo(c redis.Conn, key string) (info keyInfo, err error) {
34101
case "none":
35102
return info, errKeyTypeNotFound
36103
case "string":
37-
// Check PFCOUNT first because STRLEN on HyperLogLog strings returns the wrong length
38-
// while PFCOUNT only works on HLL strings and returns an error on regular strings.
39-
40-
//
41-
// the following two commands are pipelined/batched to improve performance
42-
// by removing one roundtrip to the redis instance
43-
// see https://github.com/oliver006/redis_exporter/issues/980
44-
//
45-
if err := c.Send("PFCOUNT", key); err != nil {
46-
return info, err
47-
}
48-
if err := c.Send("STRLEN", key); err != nil {
49-
return info, err
50-
}
51-
if err := c.Flush(); err != nil {
52-
return info, err
53-
}
54-
55-
hllSize, hllErr := redis.Int64(c.Receive())
56-
strSize, strErr := redis.Int64(c.Receive())
57-
if hllErr == nil {
58-
// hyperloglog
59-
info.size = float64(hllSize)
60-
61-
// "TYPE" reports hll as string
62-
// this will prevent treating the result as a string by the caller (e.g. call GET)
63-
info.keyType = "HLL"
64-
} else if strErr == nil {
65-
// not hll so possibly a string?
66-
info.size = float64(strSize)
104+
if isCluster {
105+
// can't pipeline for clusters because redisc doesn't support pipelined calls for clusters
106+
info, err = getStringInfoNotPipelined(c, key)
67107
} else {
68-
// something went wrong, return the error(s)
69-
return info, fmt.Errorf("hllErr: %w strErr: %w", hllErr, strErr)
108+
info, err = getStringInfoPipelined(c, key)
70109
}
71-
72110
case "list":
73111
if size, err := redis.Int64(doRedisCmd(c, "LLEN", key)); err == nil {
74112
info.size = float64(size)
@@ -139,7 +177,7 @@ func (e *Exporter) extractCheckKeyMetrics(ch chan<- prometheus.Metric, c redis.C
139177
}
140178

141179
dbLabel := "db" + k.db
142-
info, err := getKeyInfo(c, k.key)
180+
info, err := getKeyInfo(c, k.key, e.options.IsCluster)
143181
switch err {
144182
case errKeyTypeNotFound:
145183
log.Debugf("Key '%s' not found when trying to get type and size: using default '0.0'", k.key)

exporter/keys_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package exporter
22

33
import (
4+
"errors"
45
"fmt"
56
"net/http/httptest"
67
"net/url"
@@ -516,7 +517,7 @@ func TestGetKeyInfo(t *testing.T) {
516517

517518
// Test all known types
518519
for _, f := range fixtures {
519-
info, err := getKeyInfo(c, f.key)
520+
info, err := getKeyInfo(c, f.key, false)
520521
if err != nil {
521522
t.Errorf("Error getting key info for %#v.", f.key)
522523
}
@@ -529,8 +530,8 @@ func TestGetKeyInfo(t *testing.T) {
529530
}
530531

531532
// Test absent key returns the correct error
532-
_, err = getKeyInfo(c, "absent_key")
533-
if err != errKeyTypeNotFound {
533+
_, err = getKeyInfo(c, "absent_key", false)
534+
if !errors.Is(err, errKeyTypeNotFound) {
534535
t.Error("Expected `errKeyTypeNotFound` for absent key. Got a different error.")
535536
}
536537
}

0 commit comments

Comments
 (0)