Redis
To test the Redis Health Check endpoint, use the following curl command:
curl http://localhost:PORT/health
Health Function
The Health
function orchestrates the health assessment of the Redis server by invoking the checkRedisHealth
function and returning the collected statistics.
Functionality
Check Redis Health: The function pings the Redis server to check its availability and adds the response to the stats map.
- If the ping fails, it logs the error and terminates the program.
- If the ping succeeds, it proceeds to retrieve additional information.
Retrieve Redis Information: The function retrieves information about the Redis server, including version, mode, connected clients, memory usage, uptime, etc.
- If an error occurs during info retrieval, it updates the health message accordingly.
Evaluate Redis Statistics: The function evaluates the collected statistics to identify potential issues and updates the health message accordingly.
- It checks for high number of connected clients, stale connections, memory usage, recent restart, high idle connections, and high connection pool utilization.
Sample Output
The Health
function returns a JSON-like map structure with various keys representing different health metrics and their corresponding values.
{
"redis_active_connections": "0",
"redis_connected_clients": "1",
"redis_hits_connections": "1",
"redis_idle_connections": "1",
"redis_max_memory": "0",
"redis_message": "Redis has been recently restarted",
"redis_misses_connections": "1",
"redis_mode": "standalone",
"redis_ping_response": "PONG",
"redis_pool_size_percentage": "0.42%",
"redis_stale_connections": "0",
"redis_status": "up",
"redis_timeouts_connections": "0",
"redis_total_connections": "1",
"redis_uptime_in_seconds": "55",
"redis_used_memory": "980704",
"redis_used_memory_peak": "980704",
"redis_version": "7.2.4"
}
Serialization/deserialization
The Sample Output
is dynamic and unstructured since it depends on the raw map. To make it structurable, it must implement JSON serialization/deserialization
or Other serialization/deserialization
(e.g, XML serialization/deserialization
) to bind it. For example:
JSON serialization/deserialization
{
"redis_health": {
"status": "up",
"message": "Redis connection pool utilization is high",
"stats": {
"version": "7.0.15",
"mode": "standalone",
"connected_clients": "10",
"memory": {
"used": {
"mb": "22.38",
"gb": "0.02"
},
"peak": {
"mb": "46.57",
"gb": "0.05"
},
"free": {
"mb": "1130.00",
"gb": "1.10"
},
"percentage": "1.98%"
},
"uptime_stats": "6 days, 3 hours, 37 minutes, 20 seconds",
"uptime": [
{
"day": "6"
},
{
"hour": "3"
},
{
"minute": "37"
},
{
"second": "20"
}
],
"pooling": {
"figures": {
"hits": "10",
"misses": "2",
"timeouts": "0",
"total": "4",
"stale": "9",
"idle": "5",
"active": "0",
"percentage": "62.50%"
},
"observed_total": "26"
}
}
}
}
XML serialization/deserialization
<?xml version="1.0" encoding="UTF-8"?>
<redis_health>
<status>up</status>
<message>Redis connection pool utilization is high</message>
<stats>
<version>7.0.15</version>
<mode>standalone</mode>
<connected_clients>10</connected_clients>
<memory>
<used>
<mb>22.38</mb>
<gb>0.02</gb>
</used>
<peak>
<mb>46.57</mb>
<gb>0.05</gb>
</peak>
<free>
<mb>1130.00</mb>
<gb>1.10</gb>
</free>
<percentage>1.98%</percentage>
</memory>
<uptime_stats>6 days, 3 hours, 37 minutes, 20 seconds</uptime_stats>
<uptime>
<day>6</day>
<hour>3</hour>
<minute>37</minute>
<second>20</second>
</uptime>
<pooling>
<figures>
<hits>10</hits>
<misses>2</misses>
<timeouts>0</timeouts>
<total>4</total>
<stale>9</stale>
<idle>5</idle>
<active>0</active>
<percentage>62.50%</percentage>
</figures>
<observed_total>26</observed_total>
</pooling>
</stats>
</redis_health>
Code Implementation
func (s *service) Health() map[string]string {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // Default is now 5s
defer cancel()
stats := make(map[string]string)
stats = s.checkRedisHealth(ctx, stats)
return stats
}
func (s *service) checkRedisHealth(ctx context.Context, stats map[string]string) map[string]string {
pong, err := s.db.Ping(ctx).Result()
if err != nil {
log.Fatalf("db down: %v", err)
}
stats["redis_status"] = "up"
stats["redis_message"] = "It's healthy"
stats["redis_ping_response"] = pong
info, err := s.db.Info(ctx).Result()
if err != nil {
stats["redis_message"] = fmt.Sprintf("Failed to retrieve Redis info: %v", err)
return stats
}
redisInfo := parseRedisInfo(info)
poolStats := s.db.PoolStats()
stats["redis_version"] = redisInfo["redis_version"]
stats["redis_mode"] = redisInfo["redis_mode"]
stats["redis_connected_clients"] = redisInfo["connected_clients"]
stats["redis_used_memory"] = redisInfo["used_memory"]
stats["redis_used_memory_peak"] = redisInfo["used_memory_peak"]
stats["redis_uptime_in_seconds"] = redisInfo["uptime_in_seconds"]
stats["redis_hits_connections"] = strconv.FormatUint(uint64(poolStats.Hits), 10)
stats["redis_misses_connections"] = strconv.FormatUint(uint64(poolStats.Misses), 10)
stats["redis_timeouts_connections"] = strconv.FormatUint(uint64(poolStats.Timeouts), 10)
stats["redis_total_connections"] = strconv.FormatUint(uint64(poolStats.TotalConns), 10)
stats["redis_idle_connections"] = strconv.FormatUint(uint64(poolStats.IdleConns), 10)
stats["redis_stale_connections"] = strconv.FormatUint(uint64(poolStats.StaleConns), 10)
stats["redis_max_memory"] = redisInfo["maxmemory"]
activeConns := uint64(math.Max(float64(poolStats.TotalConns-poolStats.IdleConns), 0))
stats["redis_active_connections"] = strconv.FormatUint(activeConns, 10)
poolSize := s.db.Options().PoolSize
connectedClients, _ := strconv.Atoi(redisInfo["connected_clients"])
poolSizePercentage := float64(connectedClients) / float64(poolSize) * 100
stats["redis_pool_size_percentage"] = fmt.Sprintf("%.2f%%", poolSizePercentage)
return s.evaluateRedisStats(redisInfo, stats)
}
func (s *service) evaluateRedisStats(redisInfo, stats map[string]string) map[string]string {
poolSize := s.db.Options().PoolSize
poolStats := s.db.PoolStats()
connectedClients, _ := strconv.Atoi(redisInfo["connected_clients"])
highConnectionThreshold := int(float64(poolSize) * 0.8)
if connectedClients > highConnectionThreshold {
stats["redis_message"] = "Redis has a high number of connected clients"
}
minStaleConnectionsThreshold := 500
if int(poolStats.StaleConns) > minStaleConnectionsThreshold {
stats["redis_message"] = fmt.Sprintf("Redis has %d stale connections.", poolStats.StaleConns)
}
usedMemory, _ := strconv.ParseInt(redisInfo["used_memory"], 10, 64)
maxMemory, _ := strconv.ParseInt(redisInfo["maxmemory"], 10, 64)
if maxMemory > 0 {
usedMemoryPercentage := float64(usedMemory) / float64(maxMemory) * 100
if usedMemoryPercentage >= 90 {
stats["redis_message"] = "Redis is using a significant amount of memory"
}
}
uptimeInSeconds, _ := strconv.ParseInt(redisInfo["uptime_in_seconds"], 10, 64)
if uptimeInSeconds < 3600 {
stats["redis_message"] = "Redis has been recently restarted"
}
idleConns := int(poolStats.IdleConns)
highIdleConnectionThreshold := int(float64(poolSize) * 0.7)
if idleConns > highIdleConnectionThreshold {
stats["redis_message"] = "Redis has a high number of idle connections"
}
poolUtilization := float64(poolStats.TotalConns-poolStats.IdleConns) / float64(poolSize) * 100
highPoolUtilizationThreshold := 90.0
if poolUtilization > highPoolUtilizationThreshold {
stats["redis_message"] = "Redis connection pool utilization is high"
}
return stats
}