feat(service): timeout_sec без дефолта; 0=нет таймаута; operator v0.1.48
- api/v1alpha1/service_types.go: убрать +kubebuilder:default=30
- invoke.go: TimeoutSec=0 → &http.Client{} (без таймаута)
- services.go: валидация timeout_sec < 0 || > 900 → HTTP 400
- service_resource.go: TF schema Optional (без Computed); 0 → Int64Null()
- deployments/k8s/operator.yaml: v0.1.47 → v0.1.48
- doc/: progress.md + api/design.md (модель Service) + decisions/log.md
- examples/POSTGRES/: bug_hunter.sh, chaos_marathon.sh, chaos_marathon.tf
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user