Pozorovatelnost v Go: volba mezi QueryTracer, metrikami a OpenTelemetry
Při integraci PostgreSQL do Go aplikací se vývojáři často potýkají s volbou nástrojů pro pozorovatelnost. Metriky, QueryTracer a OpenTelemetry řeší různé úkoly, ale snadno se je zamění. Probereme, kdy použít každý z nich, a jak se vyhnout typickým chybám na základě reálného případu s dekorátorem a transakcemi.
Tři úrovně pozorovatelnosti: metriky, tracing a kontext
Metriky představují základní úroveň pozorovatelnosti. Odpovídají na otázku „co se děje“: například růst počtu chyb po nasazení nebo zvýšení p99 času odpovědi endpointu z 50 ms na 800 ms. Sběr metrik je vůči zdrojům levný a umožňuje rychle odhalit anomálii. Metriky však neposkytují kontext: ukazují, že požadavek zpomalil, ale nevysvětlují proč. Pro odpověď na tuto otázku je potřeba tracing.
QueryTracer je rozhraní poskytované ovladačem pgx pro zachycení operací na úrovni ovladače. Generuje samostatné události pro každou fázi transakce: BEGIN, samotný dotaz a COMMIT. Není to nástroj pro sběr metrik, ale bod pro vložení (hook), do kterého můžete umístit logiku pro vytváření spanů, logování SQL nebo sběr metrik. Je důležité pochopit: QueryTracer sám data neodesílá – pouze poskytuje mechanismus pro jejich generování.
OpenTelemetry (OTel) řeší distribuovaný tracing. Span se vytváří na úrovni obslužného HTTP požadavku a předává se přes kontext (context.Context) hlouběji do zásobníku: služba, repozitář, ovladač. Každá vrstva přidává dětský span a formuje strom provedení. V systému vizualizace (např. Jaeger) vidíte celý obrázek: který konkrétní dotaz k DB způsobil zpoždění a v jakém kontextu. OTel běží neustále, ale pro snížení zátěže používá sampling: např. traceuje jen 10 % požadavků. Pokud požadavek spadne do vzorku, zaznamená se celý strom spanů.
Příklad s dekorátorem: kde se skrývá problém
V typických projektech se pro sběr metrik používá dekorátor nad repozitářem. Podívejme se na příklad implementace:
func WithDBMetricsValueT any (T, error)) (T, error) {
start := time.Now()
result, err := fn()
seconds := time.Since(start).Seconds()
metrics.ObserveDatabaseQueryDuration(operation, seconds)
if err != nil {
metrics.IncDatabaseErrors(operation)
return result, CheckDBError(operation, err)
}
return result, err
}
Pro metody provádějící jeden SQL dotaz funguje dekorátor přesně: metrika odpovídá času provedení dotazu. U metod s transakcemi se však situace mění. Vezměme příklad:
func (r *BookingRepo) CreateBooking(ctx context.Context, b *models.Booking) (int64, error) {
const operation = "create_booking"
return WithDBMetricsValue(operation, func() (int64, error) {
if err := r.validateBooking(b); err != nil {
return 0, err
}
tx, err := r.db.BeginTxx(ctx, nil)
if err != nil {
return 0, err
}
defer func() { _ = tx.Rollback() }()
var id int64
err = tx.QueryRowContext(ctx, createBookingAtomicQuery, ...).Scan(&id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return 0, models.ErrSlotOccupied
}
return 0, err
}
if err = tx.Commit(); err != nil {
return 0, err
}
return id, nil
})
}
Dekorátor měří čas celého bloku včetně validace, zahájení transakce, provedení dotazu a commitu. Metrika create_booking = 45ms neodhalí, která fáze zabírá 40 ms. QueryTracer naopak rozkládá transakci na tři události:
- BEGIN = 0.1ms
- INSERT = 40ms
- COMMIT = 2ms
Tato detailizace je klíčová pro diagnostiku úzkých míst v výkonu.
Implementace QueryTracer: rozhraní a pastě
QueryTracer je soubor rozhraní v pgx, každé pro jiný typ operace. Např. pro Batch operace se používá BatchTracer:
type BatchTracer interface {
TraceBatchStart(ctx context.Context, conn *Conn, data TraceBatchStartData) context.Context
TraceBatchQuery(ctx context.Context, conn *Conn, data TraceBatchQueryData)
TraceBatchEnd(ctx context.Context, conn *Conn, data TraceBatchEndData)
}
Implementace vyžaduje strukturu, která splňuje tato rozhraní:
type MyTracer struct{}
func (t *MyTracer) TraceQueryStart(ctx context.Context, conn *Conn, data TraceQueryStartData) context.Context {
// Onchalo request: sozdaem span or logiruem
return ctx
}
func (t *MyTracer) TraceQueryEnd(ctx context.Context, conn *Conn, data TraceQueryEndData) {
// Konets request: fiksiruem time, otpravlyaem metriku
//
}
// Podklyuchenie to konfiguratsii
config.ConnConfig.Tracer = &MyTracer{}
Klíčové pasti:
- Pokud neimplementujete BatchTracer, Batch operace zůstanou nepozorované. Chyba nevznikne, ale data se ztratí.
- Pro práci s OTel je nutné ručně vytvářet spany uvnitř metod QueryTracer a správně předávat kontext.
- QueryTracer nenahrazuje metriky: pro agregaci dat budete potřebovat další logiku.
Srovnání stacků: sqlx, pgx a vlastní řešení
Volba nástroje závisí na aktuálním stacku a požadavcích na detail. Podívejme se na tři varianty:
- sqlx + otelsql
- Připojení: jedna řádka kódu.
- Výhody: nevyžaduje změnu ovladače, podpora základního tracingu.
- Omezení: chybí podpora Batch a CopyFrom, nelze přidat vlastní logiku.
- Doporučení: vhodné pro jednoduché projekty bez složitých operací s DB.
- pgx + otelpgx
- Připojení: jedna řádka kódu.
- Výhody: plná podpora Batch a CopyFrom, automatické vytváření spanů.
- Omezení: vyžaduje přechod z sqlx na pgx.
- Doporučení: optimální volba pro nové projekty nebo migraci stávajících.
- pgx + MyTracer
- Připojení: ruční implementace rozhraní.
- Výhody: plná kontrola nad logikou (vlastní metriky, rozšířené logování).
- Omezení: vysoká složitost, nutnost údržby.
- Doporučení: pouze při specifických požadavcích, které otelpgx nepokrývá.
Důležité: vůbec všech případech musí být kontext (context.Context) předáván všemi vrstvami aplikace – od handleru po ovladač. Bez toho budou spany kořenové a ztratí spojení s původním uživatelským požadavkem.
Co je důležité
- Metriky ukazují problém, ale ne příčinu. Používejte je pro monitorování, ne pro diagnostiku úzkých míst.
- QueryTracer je hook pro tracing, ne pro metriky. Rozkládá transakce na fáze, což je nutné pro detailní analýzu.
- OpenTelemetry vyžaduje správné předávání kontextu. Ověřte, že context.Context prochází všemi vrstvami aplikace.
- Dekorátor je vhodný pro jednoduché metriky, ne pro transakce. Pro diagnostiku uvnitř transakcí použijte QueryTracer nebo OTel.
- Volba stacku: pokud projekt už používá sqlx a nepotřebuje složité operace – zůstaňte u otelsql. Pro úplný pohled přejděte na pgx s otelpgx.
— Editorial Team
Zatím žádné komentáře.