🔧 feat: enhance readiness endpoint with database health check
- Added CheckDatabaseHealth() method to UserRepository interface - Implemented database connectivity check in SQLite repository - Enhanced /ready endpoint to verify database health before reporting ready - Improved readiness logic: checks both server shutdown status and database connectivity - Better observability: Logs database health check failures with warnings Benefits: - More accurate readiness reporting for Kubernetes/container environments - Detects database connectivity issues before accepting traffic - Prevents application from accepting requests when database is unavailable - Maintains backward compatibility with existing readiness checks Implementation: - Simple COUNT query to test database responsiveness - Graceful handling of database unavailability - Proper HTTP 503 status when not ready - Comprehensive logging for troubleshooting Testing: - ✅ Readiness endpoint returns true when database is healthy - ✅ Readiness endpoint returns false when database is unhealthy - ✅ All existing functionality preserved - ✅ All 25 BDD scenarios passing - ✅ All unit tests passing Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -213,7 +213,7 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
|||||||
// handleReadiness godoc
|
// handleReadiness godoc
|
||||||
//
|
//
|
||||||
// @Summary Readiness check
|
// @Summary Readiness check
|
||||||
// @Description Check if the service is ready to accept traffic
|
// @Description Check if the service is ready to accept traffic including database connectivity
|
||||||
// @Tags System/Health
|
// @Tags System/Health
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
@@ -223,12 +223,23 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
|||||||
func (s *Server) handleReadiness(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleReadiness(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Trace().Msg("Readiness check requested")
|
log.Trace().Msg("Readiness check requested")
|
||||||
|
|
||||||
|
// Check if server is shutting down
|
||||||
select {
|
select {
|
||||||
case <-s.readyCtx.Done():
|
case <-s.readyCtx.Done():
|
||||||
log.Trace().Msg("Readiness check: not ready (shutting down)")
|
log.Trace().Msg("Readiness check: not ready (shutting down)")
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
w.Write([]byte(`{"ready":false}`))
|
w.Write([]byte(`{"ready":false}`))
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
|
// Server is not shutting down, check database if available
|
||||||
|
if s.userRepo != nil {
|
||||||
|
if err := s.userRepo.CheckDatabaseHealth(r.Context()); err != nil {
|
||||||
|
log.Warn().Err(err).Msg("Database health check failed")
|
||||||
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
|
w.Write([]byte(`{"ready":false}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
log.Trace().Msg("Readiness check: ready")
|
log.Trace().Msg("Readiness check: ready")
|
||||||
w.Write([]byte(`{"ready":true}`))
|
w.Write([]byte(`{"ready":true}`))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,6 +201,17 @@ func (r *SQLiteRepository) Close() error {
|
|||||||
return sqlDB.Close()
|
return sqlDB.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckDatabaseHealth checks if the database is healthy and responsive
|
||||||
|
func (r *SQLiteRepository) CheckDatabaseHealth(ctx context.Context) error {
|
||||||
|
// Simple query to test database connectivity
|
||||||
|
var count int64
|
||||||
|
result := r.db.WithContext(ctx).Model(&User{}).Count(&count)
|
||||||
|
if result.Error != nil {
|
||||||
|
return fmt.Errorf("database health check failed: %w", result.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// createSpan creates a new telemetry span if persistence telemetry is enabled
|
// createSpan creates a new telemetry span if persistence telemetry is enabled
|
||||||
func (r *SQLiteRepository) createSpan(ctx context.Context, operation string) (context.Context, trace.Span) {
|
func (r *SQLiteRepository) createSpan(ctx context.Context, operation string) (context.Context, trace.Span) {
|
||||||
if r.config == nil || !r.config.GetPersistenceTelemetryEnabled() {
|
if r.config == nil || !r.config.GetPersistenceTelemetryEnabled() {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type UserRepository interface {
|
|||||||
AllowPasswordReset(ctx context.Context, username string) error
|
AllowPasswordReset(ctx context.Context, username string) error
|
||||||
CompletePasswordReset(ctx context.Context, username, newPassword string) error
|
CompletePasswordReset(ctx context.Context, username, newPassword string) error
|
||||||
UserExists(ctx context.Context, username string) (bool, error)
|
UserExists(ctx context.Context, username string) (bool, error)
|
||||||
|
CheckDatabaseHealth(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthService defines interface for authentication operations
|
// AuthService defines interface for authentication operations
|
||||||
|
|||||||
Reference in New Issue
Block a user