← Назад к вопросам
HTTP сервис с Prometheus метриками
1.0 Junior🔥 121 комментариев
#Основы Go
Условие
Напишите HTTP сервис, который слушает входящие запросы, преобразует их в запрос к PostgreSQL, выполняет запрос и возвращает ответ клиенту.
Требования
- Ограничить максимальное количество одновременных коннектов к БД
- Добавить Prometheus метрики на вызовы:
- Количество запросов
- Время выполнения запросов
- Количество ошибок
Сигнатура
type Server struct {
db *sql.DB
maxConns int
// ваши поля
}
func NewServer(db *sql.DB, maxConns int) *Server
func (s *Server) HandleQuery(w http.ResponseWriter, r *http.Request)
Это реальное тестовое задание
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
HTTP сервис с Prometheus метриками - реальное решение
Описание задачи
Нужно реализовать HTTP сервис, который:
- Принимает запросы от клиентов
- Выполняет SQL запросы к PostgreSQL
- Ограничивает количество одновременных соединений
- Собирает Prometheus метрики
- Возвращает результаты в JSON
Архитектурное решение
Компоненты:
- Семафор (semaphore) - ограничение одновременных коннектов
- Prometheus metrics - счетчики и гистограммы
- Middleware - сбор метрик для каждого запроса
- Connection pooling - встроено в database/sql
Реализация
package main
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"sync"
"time"
_ "github.com/lib/pq"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
queryCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_queries_total",
Help: "Total number of HTTP queries processed",
},
[]string{"status"},
)
queryDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_query_duration_seconds",
Help: "Time spent processing queries",
Buckets: []float64{0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1},
},
[]string{"operation"},
)
dbErrors = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "db_errors_total",
Help: "Total number of database errors",
},
[]string{"error_type"},
)
activeConnections = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "active_db_connections",
Help: "Number of active database connections",
},
)
)
func init() {
prometheus.MustRegister(queryCounter)
prometheus.MustRegister(queryDuration)
prometheus.MustRegister(dbErrors)
prometheus.MustRegister(activeConnections)
}
type Server struct {
db *sql.DB
maxConns int
semaphore chan struct{}
activeCount int32
mu sync.Mutex
}
func NewServer(db *sql.DB, maxConns int) *Server {
return &Server{
db: db,
maxConns: maxConns,
semaphore: make(chan struct{}, maxConns),
}
}
type QueryRequest struct {
Query string `json:"query"`
Args []interface{} `json:"args"`
}
type QueryResponse struct {
Columns []string `json:"columns"`
Rows [][]interface{} `json:"rows"`
Count int `json:"count"`
}
func (s *Server) HandleQuery(w http.ResponseWriter, r *http.Request) {
start := time.Now()
var req QueryRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, fmt.Sprintf("Invalid request: %v", err), http.StatusBadRequest)
queryCounter.WithLabelValues("error").Inc()
dbErrors.WithLabelValues("parse_error").Inc()
return
}
s.semaphore <- struct{}{}
defer func() { <-s.semaphore }()
s.mu.Lock()
s.activeCount++
activeConnections.Set(float64(s.activeCount))
s.mu.Unlock()
defer func() {
s.mu.Lock()
s.activeCount--
activeConnections.Set(float64(s.activeCount))
s.mu.Unlock()
}()
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
defer cancel()
rows, err := s.db.QueryContext(ctx, req.Query, req.Args...)
if err != nil {
http.Error(w, fmt.Sprintf("Query error: %v", err), http.StatusInternalServerError)
queryCounter.WithLabelValues("error").Inc()
dbErrors.WithLabelValues("query_error").Inc()
queryDuration.WithLabelValues("query").Observe(time.Since(start).Seconds())
return
}
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
http.Error(w, fmt.Sprintf("Columns error: %v", err), http.StatusInternalServerError)
dbErrors.WithLabelValues("columns_error").Inc()
return
}
var result QueryResponse
result.Columns = columns
result.Rows = make([][]interface{}, 0)
for rows.Next() {
values := make([]interface{}, len(columns))
valuePtrs := make([]interface{}, len(columns))
for i := range columns {
valuePtrs[i] = &values[i]
}
if err := rows.Scan(valuePtrs...); err != nil {
http.Error(w, fmt.Sprintf("Scan error: %v", err), http.StatusInternalServerError)
dbErrors.WithLabelValues("scan_error").Inc()
return
}
result.Rows = append(result.Rows, values)
}
result.Count = len(result.Rows)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
queryCounter.WithLabelValues("success").Inc()
queryDuration.WithLabelValues("query").Observe(time.Since(start).Seconds())
}
func main() {
dsn := "postgres://user:password@localhost/dbname?sslmode=disable"
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatal(err)
}
defer db.Close()
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
if err := db.Ping(); err != nil {
log.Fatal(err)
}
server := NewServer(db, 20)
http.HandleFunc("/query", server.HandleQuery)
http.Handle("/metrics", promhttp.Handler())
fmt.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Ключевые компоненты
1. Семафор через канал:
semaphore := make(chan struct{}, maxConns)
s.semaphore <- struct{}{}
<-s.semaphore
2. Prometheus метрики:
- queryCounter - счетчик успешных/неудачных запросов
- queryDuration - гистограмма времени выполнения
- dbErrors - счетчик ошибок по типам
- activeConnections - текущее количество активных соединений
3. Connection pooling:
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
Анализ сложности
- Time: O(n) где n - количество строк результата
- Space: O(n) для хранения результата
- Concurrency: O(maxConns) одновременных запросов
Best Practices
- Используй context.WithTimeout - всегда ставь таймауты на БД запросы
- Настраивай пул соединений - SetMaxOpenConns, SetMaxIdleConns
- Собирай метрики - Prometheus для мониторинга в production
- Обработка ошибок - разные типы ошибок требуют разных действий
- Graceful shutdown - закрывай БД перед выходом