🔧 feat: enhance readiness endpoint with database health check
Some checks failed
CI/CD Pipeline / CI Pipeline (push) Has been cancelled
CI/CD Pipeline / CI Pipeline (pull_request) Successful in 11m58s

- 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:
2026-04-07 01:02:53 +02:00
parent db1b277464
commit 760d1cc8b0
3 changed files with 24 additions and 1 deletions

View File

@@ -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}`))
} }

View File

@@ -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() {

View File

@@ -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