// 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 }