@@ -24,8 +24,75 @@ type keyInfo struct {
2424
2525var 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 )
0 commit comments