feat(v0.1.58): ImageExists cache hit, timing analysis, in-cluster registry plan
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// 2026-03-21 — chaos_marathon.tf: 15 новых сервисов для часового хаос-марафона.
|
||||
// Три рантайма: python3.11 (9), nodejs20 (2), go1.23 (2).
|
||||
// Два рантайма: python3.11 (9), nodejs20 (2).
|
||||
// Все зависят от sless_job.postgres_table_init_job.
|
||||
|
||||
# ── Python: работа с таблицей ─────────────────────────────────────────────────
|
||||
@@ -233,50 +233,6 @@ resource "sless_service" "js_idempotent" {
|
||||
depends_on = [sless_job.postgres_table_init_job]
|
||||
}
|
||||
|
||||
# ── Go 1.23 ───────────────────────────────────────────────────────────────────
|
||||
|
||||
# Параллельные concurrent INSERTs из N горутин внутри одного пода.
|
||||
resource "sless_service" "go_pg_race" {
|
||||
name = "go-pg-race"
|
||||
runtime = "go1.23"
|
||||
entrypoint = "handler.Handle"
|
||||
memory_mb = 256
|
||||
timeout_sec = 30
|
||||
|
||||
env_vars = {
|
||||
PGHOST = local.pg_host
|
||||
PGPORT = "5432"
|
||||
PGDATABASE = local.pg_database
|
||||
PGUSER = local.pg_username
|
||||
PGPASSWORD = local.pg_password
|
||||
PGSSLMODE = "require"
|
||||
}
|
||||
|
||||
source_dir = "${path.module}/code/go-pg-race"
|
||||
depends_on = [sless_job.postgres_table_init_job]
|
||||
}
|
||||
|
||||
# Atomic-счётчик в памяти + PG INSERT на каждый вызов.
|
||||
resource "sless_service" "go_counter_atomic" {
|
||||
name = "go-counter-atomic"
|
||||
runtime = "go1.23"
|
||||
entrypoint = "handler.Handle"
|
||||
memory_mb = 128
|
||||
timeout_sec = 15
|
||||
|
||||
env_vars = {
|
||||
PGHOST = local.pg_host
|
||||
PGPORT = "5432"
|
||||
PGDATABASE = local.pg_database
|
||||
PGUSER = local.pg_username
|
||||
PGPASSWORD = local.pg_password
|
||||
PGSSLMODE = "require"
|
||||
}
|
||||
|
||||
source_dir = "${path.module}/code/go-counter-atomic"
|
||||
depends_on = [sless_job.postgres_table_init_job]
|
||||
}
|
||||
|
||||
# ── Python: retry ─────────────────────────────────────────────────────────────
|
||||
|
||||
# Запись с retry при transient PG error — тест устойчивости к сбоям.
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// 2026-03-21 — go-counter-atomic: считает вызовы через atomic в памяти + пишет в PG.
|
||||
// Тестирует: in-memory state между вызовами (Go pod остаётся живым), + PG INSERT на каждый вызов.
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// invocations считается между вызовами (пока pod жив).
|
||||
var invocations int64
|
||||
|
||||
// Handle записывает факт вызова в PG и возвращает накопленный счётчик.
|
||||
func Handle(event map[string]interface{}) interface{} {
|
||||
n := atomic.AddInt64(&invocations, 1)
|
||||
|
||||
dsn := fmt.Sprintf(
|
||||
"host=%s port=%s dbname=%s user=%s password=%s sslmode=%s",
|
||||
os.Getenv("PGHOST"), envOrDefault("PGPORT", "5432"),
|
||||
os.Getenv("PGDATABASE"), os.Getenv("PGUSER"),
|
||||
os.Getenv("PGPASSWORD"), envOrDefault("PGSSLMODE", "require"),
|
||||
)
|
||||
pool, err := pgxpool.New(context.Background(), dsn)
|
||||
if err != nil {
|
||||
return map[string]interface{}{"invocation_n": n, "error": err.Error()}
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
title := fmt.Sprintf("go-counter-invoke-%d-%d", n, time.Now().UnixMilli())
|
||||
var id int64
|
||||
err = pool.QueryRow(context.Background(),
|
||||
"INSERT INTO terraform_demo_table (title) VALUES ($1) RETURNING id", title,
|
||||
).Scan(&id)
|
||||
if err != nil {
|
||||
return map[string]interface{}{"invocation_n": n, "error": err.Error()}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"invocation_n": n,
|
||||
"inserted_id": id,
|
||||
"title": title,
|
||||
}
|
||||
}
|
||||
|
||||
func envOrDefault(key, def string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// 2026-03-19
|
||||
// handler.go — быстрая Go функция: факториал + числа Фибоначчи.
|
||||
// Проверяет Go runtime под лёгкой нагрузкой и корректность JSON-ответа.
|
||||
// Entrypoint: handler.Handle
|
||||
package handler
|
||||
|
||||
import "fmt"
|
||||
|
||||
func factorial(n int) uint64 {
|
||||
if n <= 1 {
|
||||
return 1
|
||||
}
|
||||
return uint64(n) * factorial(n-1)
|
||||
}
|
||||
|
||||
func fib(n int) int {
|
||||
if n <= 1 {
|
||||
return n
|
||||
}
|
||||
a, b := 0, 1
|
||||
for i := 2; i <= n; i++ {
|
||||
a, b = b, a+b
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Handle(event map[string]interface{}) interface{} {
|
||||
n := 10
|
||||
if v, ok := event["n"].(float64); ok {
|
||||
n = int(v)
|
||||
if n > 20 {
|
||||
n = 20
|
||||
}
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"runtime": "go1.23",
|
||||
"version": "v1",
|
||||
"n": n,
|
||||
"factorial": fmt.Sprintf("%d", factorial(n)),
|
||||
"fib": fib(n),
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// 2026-03-19
|
||||
// handler.go — намеренный nil pointer dereference в Go.
|
||||
// Проверяет что Go runtime recover() перехватывает панику и платформа возвращает 500.
|
||||
// Entrypoint: handler.Handle
|
||||
package handler
|
||||
|
||||
func Handle(event map[string]interface{}) interface{} {
|
||||
crash := true
|
||||
if v, ok := event["crash"].(bool); ok {
|
||||
crash = v
|
||||
}
|
||||
if crash {
|
||||
var p *string
|
||||
_ = *p // panic: намеренный nil pointer для stress-теста
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"runtime": "go1.23",
|
||||
"version": "v1",
|
||||
"crashed": false,
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
// 2026-03-19
|
||||
// handler.go — Go стресс-тест PostgreSQL через pgxpool.
|
||||
// Запускает N горутин (default 100), каждая в цикле duration_sec (default 600)
|
||||
// долбит PG попеременно: INSERT / SELECT COUNT / SELECT MAX с случайными задержками.
|
||||
// Цель: проверить Go runtime под конкурентной нагрузкой и устойчивость PG connection pool.
|
||||
// Entrypoint: handler.Handle
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// pgDSN собирает DSN из env vars (PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSSLMODE).
|
||||
func pgDSN() string {
|
||||
host := os.Getenv("PGHOST")
|
||||
port := os.Getenv("PGPORT")
|
||||
if port == "" {
|
||||
port = "5432"
|
||||
}
|
||||
db := os.Getenv("PGDATABASE")
|
||||
user := os.Getenv("PGUSER")
|
||||
pass := os.Getenv("PGPASSWORD")
|
||||
sslmode := os.Getenv("PGSSLMODE")
|
||||
if sslmode == "" {
|
||||
sslmode = "require"
|
||||
}
|
||||
return fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=%s",
|
||||
host, port, db, user, pass, sslmode)
|
||||
}
|
||||
|
||||
// worker — одна горутина: чередует INSERT/COUNT/MAX с случайной задержкой до maxDelayMs.
|
||||
// При ошибке инкрементирует errOps и продолжает (не паникует).
|
||||
func worker(ctx context.Context, pool *pgxpool.Pool, workerID int, maxDelayMs int, okOps, errOps *int64) {
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano() + int64(workerID)))
|
||||
op := 0
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// Случайная задержка перед следующей операцией: 0..maxDelayMs мс
|
||||
delay := rng.Intn(maxDelayMs + 1)
|
||||
time.Sleep(time.Duration(delay) * time.Millisecond)
|
||||
|
||||
var err error
|
||||
switch op % 3 {
|
||||
case 0: // INSERT
|
||||
title := fmt.Sprintf("pgstorm-w%d-%d", workerID, time.Now().UnixNano())
|
||||
_, err = pool.Exec(ctx,
|
||||
"INSERT INTO terraform_demo_table (title) VALUES ($1)", title)
|
||||
case 1: // SELECT COUNT
|
||||
var count int64
|
||||
err = pool.QueryRow(ctx,
|
||||
"SELECT COUNT(*) FROM terraform_demo_table").Scan(&count)
|
||||
case 2: // SELECT MAX id
|
||||
var maxID *int64
|
||||
err = pool.QueryRow(ctx,
|
||||
"SELECT MAX(id) FROM terraform_demo_table").Scan(&maxID)
|
||||
}
|
||||
|
||||
if err != nil && ctx.Err() == nil {
|
||||
atomic.AddInt64(errOps, 1)
|
||||
} else if err == nil {
|
||||
atomic.AddInt64(okOps, 1)
|
||||
}
|
||||
op++
|
||||
}
|
||||
}
|
||||
|
||||
func Handle(event map[string]interface{}) interface{} {
|
||||
// Параметры из event (все опциональны — разумные defaults)
|
||||
workers := 100
|
||||
if v, ok := event["workers"].(float64); ok && v > 0 && v <= 500 {
|
||||
workers = int(v)
|
||||
}
|
||||
durationSec := 600
|
||||
if v, ok := event["duration_sec"].(float64); ok && v > 0 && v <= 3600 {
|
||||
durationSec = int(v)
|
||||
}
|
||||
maxDelayMs := 300
|
||||
if v, ok := event["max_delay_ms"].(float64); ok && v >= 0 && v <= 5000 {
|
||||
maxDelayMs = int(v)
|
||||
}
|
||||
|
||||
// Инициализация pgxpool — единый pool на всю функцию, MaxConns ограничен
|
||||
// чтобы не перегрузить managed PG при большом числе горутин.
|
||||
poolCfg, err := pgxpool.ParseConfig(pgDSN())
|
||||
if err != nil {
|
||||
return map[string]interface{}{"error": fmt.Sprintf("parse dsn: %v", err)}
|
||||
}
|
||||
maxConns := 20
|
||||
if workers < 20 {
|
||||
maxConns = workers
|
||||
}
|
||||
poolCfg.MaxConns = int32(maxConns)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(durationSec)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pool, err := pgxpool.NewWithConfig(ctx, poolCfg)
|
||||
if err != nil {
|
||||
return map[string]interface{}{"error": fmt.Sprintf("connect pool: %v", err)}
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
var okOps, errOps int64
|
||||
startTime := time.Now()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < workers; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
worker(ctx, pool, id, maxDelayMs, &okOps, &errOps)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
elapsed := time.Since(startTime).Seconds()
|
||||
total := okOps + errOps
|
||||
opsPerSec := 0.0
|
||||
if elapsed > 0 {
|
||||
opsPerSec = float64(total) / elapsed
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"runtime": "go1.23",
|
||||
"version": "v1",
|
||||
"workers": workers,
|
||||
"duration_sec": durationSec,
|
||||
"max_delay_ms": maxDelayMs,
|
||||
"elapsed_sec": fmt.Sprintf("%.1f", elapsed),
|
||||
"total_ops": total,
|
||||
"ok_ops": okOps,
|
||||
"err_ops": errOps,
|
||||
"ops_per_sec": fmt.Sprintf("%.1f", opsPerSec),
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,7 @@
|
||||
// 2026-03-21 — stress.tf: все стресс-сервисы для комплексного тестирования.
|
||||
// Три рантайма: go1.23 (3), nodejs20 (2), python3.11 (5).
|
||||
// Два рантайма: nodejs20 (2), python3.11 (5).
|
||||
// Все depends_on = [sless_job.postgres_table_init_job] — таблица должна существовать.
|
||||
|
||||
# ── Go 1.23 ─────────────────────────────────────────────────────────────────
|
||||
|
||||
# Быстрая математика: факториал + числа Фибоначчи. Без PG. Проверяет Go runtime.
|
||||
resource "sless_service" "stress_go_fast" {
|
||||
name = "stress-go-fast"
|
||||
runtime = "go1.23"
|
||||
entrypoint = "handler.Handle"
|
||||
memory_mb = 128
|
||||
timeout_sec = 15
|
||||
source_dir = "${path.module}/code/stress-go-fast"
|
||||
|
||||
depends_on = [sless_job.postgres_table_init_job]
|
||||
}
|
||||
|
||||
# Намеренный nil pointer dereference. Без PG. Проверяет recover() в Go runtime.
|
||||
resource "sless_service" "stress_go_nil" {
|
||||
name = "stress-go-nil"
|
||||
runtime = "go1.23"
|
||||
entrypoint = "handler.Handle"
|
||||
memory_mb = 128
|
||||
timeout_sec = 10
|
||||
source_dir = "${path.module}/code/stress-go-nil"
|
||||
|
||||
depends_on = [sless_job.postgres_table_init_job]
|
||||
}
|
||||
|
||||
# Конкурентный PG-шторм через pgxpool: N горутин INSERT/COUNT/MAX.
|
||||
# timeout_sec=660 — покрывает max duration_sec=600 с запасом.
|
||||
# pgx/v5 уже в базовом образе go1.23: user go.mod не нужен.
|
||||
resource "sless_service" "stress_go_pgstorm" {
|
||||
name = "stress-go-pgstorm"
|
||||
runtime = "go1.23"
|
||||
entrypoint = "handler.Handle"
|
||||
memory_mb = 256
|
||||
timeout_sec = 660
|
||||
source_dir = "${path.module}/code/stress-go-pgstorm"
|
||||
|
||||
env_vars = {
|
||||
PGHOST = local.pg_host
|
||||
PGPORT = "5432"
|
||||
PGDATABASE = local.pg_database
|
||||
PGUSER = local.pg_username
|
||||
PGPASSWORD = local.pg_password
|
||||
PGSSLMODE = "require"
|
||||
}
|
||||
|
||||
depends_on = [sless_job.postgres_table_init_job]
|
||||
}
|
||||
|
||||
# ── Node.js 20 ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user