95 lines
2.2 KiB
Go
95 lines
2.2 KiB
Go
// 2026-03-21 — go-pg-race: параллельные INSERT из нескольких горутин внутри одной функции.
|
|
// Тестирует: race condition устойчивость Go + PG при concurrent writes из одного пода.
|
|
// Использует pgx/v5 (pre-cached в base image).
|
|
package handler
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// Handle запускает workers горутин, каждая делает n_per_worker INSERTs.
|
|
func Handle(event map[string]interface{}) interface{} {
|
|
workers := intParam(event, "workers", 5)
|
|
if workers > 20 {
|
|
workers = 20
|
|
}
|
|
nPerWorker := intParam(event, "n_per_worker", 10)
|
|
if nPerWorker > 50 {
|
|
nPerWorker = 50
|
|
}
|
|
|
|
dsn := fmt.Sprintf(
|
|
"host=%s port=%s dbname=%s user=%s password=%s sslmode=%s",
|
|
os.Getenv("PGHOST"), getenv("PGPORT", "5432"),
|
|
os.Getenv("PGDATABASE"), os.Getenv("PGUSER"),
|
|
os.Getenv("PGPASSWORD"), getenv("PGSSLMODE", "require"),
|
|
)
|
|
pool, err := pgxpool.New(context.Background(), dsn)
|
|
if err != nil {
|
|
return map[string]interface{}{"error": err.Error()}
|
|
}
|
|
defer pool.Close()
|
|
|
|
var (
|
|
wg sync.WaitGroup
|
|
ok int64
|
|
errCount int64
|
|
)
|
|
t0 := time.Now()
|
|
for w := 0; w < workers; w++ {
|
|
wg.Add(1)
|
|
go func(wid int) {
|
|
defer wg.Done()
|
|
for i := 0; i < nPerWorker; i++ {
|
|
title := fmt.Sprintf("go-race-w%d-%d-%d", wid, time.Now().UnixMilli(), i)
|
|
_, err := pool.Exec(context.Background(),
|
|
"INSERT INTO terraform_demo_table (title) VALUES ($1)", title)
|
|
if err != nil {
|
|
atomic.AddInt64(&errCount, 1)
|
|
} else {
|
|
atomic.AddInt64(&ok, 1)
|
|
}
|
|
}
|
|
}(w)
|
|
}
|
|
wg.Wait()
|
|
elapsed := time.Since(t0).Seconds()
|
|
|
|
return map[string]interface{}{
|
|
"workers": workers,
|
|
"n_per_worker": nPerWorker,
|
|
"inserted": ok,
|
|
"errors": errCount,
|
|
"elapsed_sec": elapsed,
|
|
"ops_per_sec": float64(ok) / elapsed,
|
|
}
|
|
}
|
|
|
|
func intParam(event map[string]interface{}, key string, def int) int {
|
|
v, ok := event[key]
|
|
if !ok {
|
|
return def
|
|
}
|
|
switch val := v.(type) {
|
|
case float64:
|
|
return int(val)
|
|
case int:
|
|
return val
|
|
}
|
|
return def
|
|
}
|
|
|
|
func getenv(key, def string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|