diff --git a/examples/POSTGRES/code/stress-bigloop/requirements.txt b/examples/POSTGRES/code/stress-bigloop/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/examples/POSTGRES/code/stress-bigloop/stress_bigloop.py b/examples/POSTGRES/code/stress-bigloop/stress_bigloop.py new file mode 100644 index 0000000..c9ce935 --- /dev/null +++ b/examples/POSTGRES/code/stress-bigloop/stress_bigloop.py @@ -0,0 +1,20 @@ +# 2026-03-19 +# stress_bigloop.py — CPU-интенсивная функция: считает сумму квадратов N чисел. +# Проверяет поведение под нагрузкой (большая и средняя итерация). + +import time + +_VERSION = "v1" + + +def run(event): + n = int(event.get("n", 500_000)) + start = time.monotonic() + total = sum(i * i for i in range(n)) + elapsed = round(time.monotonic() - start, 4) + return { + "version": _VERSION, + "n": n, + "sum_of_squares": total, + "elapsed_sec": elapsed, + } diff --git a/examples/POSTGRES/code/stress-divzero/requirements.txt b/examples/POSTGRES/code/stress-divzero/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/examples/POSTGRES/code/stress-divzero/stress_divzero.py b/examples/POSTGRES/code/stress-divzero/stress_divzero.py new file mode 100644 index 0000000..86746e5 --- /dev/null +++ b/examples/POSTGRES/code/stress-divzero/stress_divzero.py @@ -0,0 +1,13 @@ +# 2026-03-19 +# stress_divzero.py — намеренно делит на ноль (ZeroDivisionError). +# Проверяет: платформа перехватывает панику, возвращает HTTP 500, не роняет под. + +_VERSION = "v1" + + +def run(event): + numerator = int(event.get("n", 42)) + denominator = int(event.get("d", 0)) # по умолчанию 0 — намеренный краш + # ZeroDivisionError: проверяем что платформа обрабатывает исключения + result = numerator / denominator + return {"version": _VERSION, "result": result} diff --git a/examples/POSTGRES/code/stress-go-fast/handler.go b/examples/POSTGRES/code/stress-go-fast/handler.go new file mode 100644 index 0000000..a6e6b5f --- /dev/null +++ b/examples/POSTGRES/code/stress-go-fast/handler.go @@ -0,0 +1,47 @@ +// 2026-03-19 +// handler.go — быстрая Go функция: факториал + числа Фибоначчи. +// Проверяет Go runtime под лёгкой нагрузкой и корректность JSON-ответа. +// +// Entrypoint: handler.Handle + +package handler + +import "fmt" + +// factorial — рекурсивный факториал (для небольших n <= 20). +func factorial(n int) uint64 { +if n <= 1 { +return 1 +} +return uint64(n) * factorial(n-1) +} + +// fib — итеративное число Фибоначчи. +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 +} + +// Handle — точка входа рантайма. +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), +} +} diff --git a/examples/POSTGRES/code/stress-go-nil/handler.go b/examples/POSTGRES/code/stress-go-nil/handler.go new file mode 100644 index 0000000..0ee25f6 --- /dev/null +++ b/examples/POSTGRES/code/stress-go-nil/handler.go @@ -0,0 +1,21 @@ +// 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, + } +} diff --git a/examples/POSTGRES/code/stress-js-async/package.json b/examples/POSTGRES/code/stress-js-async/package.json new file mode 100644 index 0000000..554d7bc --- /dev/null +++ b/examples/POSTGRES/code/stress-js-async/package.json @@ -0,0 +1,7 @@ +{ + "name": "stress-js-async", + "version": "1.0.0", + "dependencies": { + "pg": "^8.11.0" + } +} diff --git a/examples/POSTGRES/code/stress-js-async/stress_js_async.js b/examples/POSTGRES/code/stress-js-async/stress_js_async.js new file mode 100644 index 0000000..022c2b3 --- /dev/null +++ b/examples/POSTGRES/code/stress-js-async/stress_js_async.js @@ -0,0 +1,37 @@ +// 2026-03-19 +// stress_js_async.js — делает 3 параллельных запроса к PG через Promise.all. +// Проверяет nodejs20 runtime под умеренной нагрузкой и async/await. +// +// Entrypoint: stress_js_async.run + +'use strict'; + +const { Client } = require('pg'); + +exports.run = async (event) => { + const client = new Client({ + host: process.env.PGHOST, + port: parseInt(process.env.PGPORT || '5432'), + database: process.env.PGDATABASE, + user: process.env.PGUSER, + password: process.env.PGPASSWORD, + ssl: process.env.PGSSLMODE === 'require' ? { rejectUnauthorized: false } : false, + }); + await client.connect(); + try { + const [ver, cnt, max] = await Promise.all([ + client.query('SELECT version() AS v'), + client.query('SELECT COUNT(*) AS cnt FROM terraform_demo_table'), + client.query('SELECT MAX(id) AS max_id FROM terraform_demo_table'), + ]); + return { + runtime: 'nodejs20', + version: 'v1', + pg_version: ver.rows[0].v.split(' ').slice(0, 2).join(' '), + total_rows: parseInt(cnt.rows[0].cnt, 10), + max_id: max.rows[0].max_id, + }; + } finally { + await client.end(); + } +}; diff --git a/examples/POSTGRES/code/stress-js-badenv/package.json b/examples/POSTGRES/code/stress-js-badenv/package.json new file mode 100644 index 0000000..1b3892c --- /dev/null +++ b/examples/POSTGRES/code/stress-js-badenv/package.json @@ -0,0 +1,5 @@ +{ + "name": "stress-js-badenv", + "version": "1.0.0", + "dependencies": {} +} diff --git a/examples/POSTGRES/code/stress-js-badenv/stress_js_badenv.js b/examples/POSTGRES/code/stress-js-badenv/stress_js_badenv.js new file mode 100644 index 0000000..7169fd8 --- /dev/null +++ b/examples/POSTGRES/code/stress-js-badenv/stress_js_badenv.js @@ -0,0 +1,17 @@ +// 2026-03-19 +// stress_js_badenv.js — читает несуществующую переменную env и падает. +// Проверяет: платформа перехватывает TypeError/undefined, возвращает 500. +// +// Entrypoint: stress_js_badenv.run + +'use strict'; + +exports.run = async (event) => { + const crash = event.crash !== false; // по умолчанию crash=true + if (crash) { + // Читаем несуществующий env, пытаемся вызвать .toUpperCase() на undefined + const val = process.env.THIS_VAR_DOES_NOT_EXIST_AT_ALL; + return { shout: val.toUpperCase() }; // TypeError: Cannot read properties of undefined + } + return { runtime: 'nodejs20', version: 'v1', crashed: false }; +}; diff --git a/examples/POSTGRES/code/stress-slow/requirements.txt b/examples/POSTGRES/code/stress-slow/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/examples/POSTGRES/code/stress-slow/stress_slow.py b/examples/POSTGRES/code/stress-slow/stress_slow.py new file mode 100644 index 0000000..ba54397 --- /dev/null +++ b/examples/POSTGRES/code/stress-slow/stress_slow.py @@ -0,0 +1,18 @@ +# 2026-03-19 +# stress_slow.py — долгая функция: спит N секунд (по умолчанию 8). +# Проверяет что timeout-механизм и параллельные запросы не блокируют друг друга. + +import time +import os + +_VERSION = "v1" + + +def run(event): + secs = int(event.get("sleep", 8)) + time.sleep(secs) + return { + "version": _VERSION, + "slept_sec": secs, + "pid": os.getpid(), + } diff --git a/examples/POSTGRES/code/stress-writer/requirements.txt b/examples/POSTGRES/code/stress-writer/requirements.txt new file mode 100644 index 0000000..58ab769 --- /dev/null +++ b/examples/POSTGRES/code/stress-writer/requirements.txt @@ -0,0 +1 @@ +psycopg2-binary==2.9.9 diff --git a/examples/POSTGRES/code/stress-writer/stress_writer.py b/examples/POSTGRES/code/stress-writer/stress_writer.py new file mode 100644 index 0000000..7eda871 --- /dev/null +++ b/examples/POSTGRES/code/stress-writer/stress_writer.py @@ -0,0 +1,39 @@ +# 2026-03-19 +# stress_writer.py — пишет N строк в terraform_demo_table (по умолчанию 5). +# Проверяет параллельные INSERT'ы и устойчивость соединения с PG при нагрузке. + +import os +import psycopg2 +import time + +_VERSION = "v1" + + +def run(event): + n = int(event.get("rows", 5)) + prefix = event.get("prefix", "stress") + + conn = psycopg2.connect( + host=os.environ["PGHOST"], + port=int(os.environ.get("PGPORT", "5432")), + dbname=os.environ["PGDATABASE"], + user=os.environ["PGUSER"], + password=os.environ["PGPASSWORD"], + sslmode=os.environ.get("PGSSLMODE", "require"), + ) + inserted = [] + try: + with conn.cursor() as cur: + for i in range(n): + title = f"{prefix}-{int(time.time()*1000)}-{i}" + cur.execute( + "INSERT INTO terraform_demo_table (title) VALUES (%s) RETURNING id", + (title,), + ) + row = cur.fetchone() + inserted.append({"id": row[0], "title": title}) + conn.commit() + finally: + conn.close() + + return {"version": _VERSION, "inserted": inserted, "count": len(inserted)} diff --git a/examples/POSTGRES/resources.tf b/examples/POSTGRES/resources.tf index 4b464fe..4260672 100644 --- a/examples/POSTGRES/resources.tf +++ b/examples/POSTGRES/resources.tf @@ -194,3 +194,184 @@ output "table_writer_url" { value = sless_trigger.postgres_table_writer_http.url } +# ============================================================================= +# STRESS-ТЕСТЫ: 8 функций для проверки устойчивости платформы. +# Python: slow, divzero, bigloop, writer +# Go: fast, nil-panic +# NodeJS: async-parallel, badenv +# ============================================================================= + +# --- [1] Python: долгая (sleep N сек) --- +resource "sless_function" "stress_slow" { + name = "stress-slow" + runtime = "python3.11" + entrypoint = "stress_slow.run" + memory_mb = 128 + timeout_sec = 30 + + env_vars = {} + + source_dir = "${path.module}/code/stress-slow" + depends_on = [sless_job.postgres_table_init_job] +} +resource "sless_trigger" "stress_slow_http" { + name = "stress-slow-http" + type = "http" + function = sless_function.stress_slow.name + enabled = true +} + +# --- [2] Python: деление на ноль --- +resource "sless_function" "stress_divzero" { + name = "stress-divzero" + runtime = "python3.11" + entrypoint = "stress_divzero.run" + memory_mb = 128 + timeout_sec = 10 + + env_vars = {} + + source_dir = "${path.module}/code/stress-divzero" + depends_on = [sless_job.postgres_table_init_job] +} +resource "sless_trigger" "stress_divzero_http" { + name = "stress-divzero-http" + type = "http" + function = sless_function.stress_divzero.name + enabled = true +} + +# --- [3] Python: CPU bigloop --- +resource "sless_function" "stress_bigloop" { + name = "stress-bigloop" + runtime = "python3.11" + entrypoint = "stress_bigloop.run" + memory_mb = 256 + timeout_sec = 30 + + env_vars = {} + + source_dir = "${path.module}/code/stress-bigloop" + depends_on = [sless_job.postgres_table_init_job] +} +resource "sless_trigger" "stress_bigloop_http" { + name = "stress-bigloop-http" + type = "http" + function = sless_function.stress_bigloop.name + enabled = true +} + +# --- [4] Python: массовая запись в PG --- +resource "sless_function" "stress_writer" { + name = "stress-writer" + runtime = "python3.11" + entrypoint = "stress_writer.run" + memory_mb = 128 + 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/stress-writer" + depends_on = [sless_job.postgres_table_init_job] +} +resource "sless_trigger" "stress_writer_http" { + name = "stress-writer-http" + type = "http" + function = sless_function.stress_writer.name + enabled = true +} + +# --- [5] Go: быстрая математика (факториал + Фибоначчи) --- +resource "sless_function" "stress_go_fast" { + name = "stress-go-fast" + runtime = "go1.23" + entrypoint = "handler.Handle" + memory_mb = 64 + timeout_sec = 10 + + env_vars = {} + + source_dir = "${path.module}/code/stress-go-fast" + depends_on = [sless_job.postgres_table_init_job] +} +resource "sless_trigger" "stress_go_fast_http" { + name = "stress-go-fast-http" + type = "http" + function = sless_function.stress_go_fast.name + enabled = true +} + +# --- [6] Go: nil pointer panic --- +resource "sless_function" "stress_go_nil" { + name = "stress-go-nil" + runtime = "go1.23" + entrypoint = "handler.Handle" + memory_mb = 64 + timeout_sec = 10 + + env_vars = {} + + source_dir = "${path.module}/code/stress-go-nil" + depends_on = [sless_job.postgres_table_init_job] +} +resource "sless_trigger" "stress_go_nil_http" { + name = "stress-go-nil-http" + type = "http" + function = sless_function.stress_go_nil.name + enabled = true +} + +# --- [7] NodeJS: 3 параллельных запроса к PG через Promise.all --- +resource "sless_function" "stress_js_async" { + name = "stress-js-async" + runtime = "nodejs20" + entrypoint = "stress_js_async.run" + 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/stress-js-async" + depends_on = [sless_job.postgres_table_init_job] +} +resource "sless_trigger" "stress_js_async_http" { + name = "stress-js-async-http" + type = "http" + function = sless_function.stress_js_async.name + enabled = true +} + +# --- [8] NodeJS: TypeError на несуществующей env-переменной --- +resource "sless_function" "stress_js_badenv" { + name = "stress-js-badenv" + runtime = "nodejs20" + entrypoint = "stress_js_badenv.run" + memory_mb = 128 + timeout_sec = 10 + + env_vars = {} + + source_dir = "${path.module}/code/stress-js-badenv" + depends_on = [sless_job.postgres_table_init_job] +} +resource "sless_trigger" "stress_js_badenv_http" { + name = "stress-js-badenv-http" + type = "http" + function = sless_function.stress_js_badenv.name + enabled = true +} + diff --git a/examples/POSTGRES/stress_test.sh b/examples/POSTGRES/stress_test.sh new file mode 100644 index 0000000..c695a37 --- /dev/null +++ b/examples/POSTGRES/stress_test.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# 2026-03-19 — stress test script: параллельный запуск всех 8 стресс-функций +BASE="https://sless.kube5s.ru/fn/sless-ffd1f598c169b0ae" + +echo "=== РАУНД 1: первый холодный запуск ===" +curl -s -m 35 "$BASE/stress-slow" -d '{"sleep":3}' -H "Content-Type:application/json" > /tmp/r_slow.json & +curl -s -m 10 "$BASE/stress-divzero" > /tmp/r_divzero.json & +curl -s -m 40 "$BASE/stress-bigloop" -d '{"n":1000000}' -H "Content-Type:application/json"> /tmp/r_bigloop.json & +curl -s -m 35 "$BASE/stress-writer" -d '{"rows":3,"prefix":"batch1"}' -H "Content-Type:application/json" > /tmp/r_writer.json & +curl -s -m 15 "$BASE/stress-go-fast" -d '{"n":15}' -H "Content-Type:application/json" > /tmp/r_go_fast.json & +curl -s -m 10 "$BASE/stress-go-nil" > /tmp/r_go_nil.json & +curl -s -m 20 "$BASE/stress-js-async" > /tmp/r_js_async.json & +curl -s -m 10 "$BASE/stress-js-badenv" > /tmp/r_js_badenv.json & +wait + +echo "[slow]: $(cat /tmp/r_slow.json)" +echo "[divzero]: $(cat /tmp/r_divzero.json)" +echo "[bigloop]: $(cat /tmp/r_bigloop.json)" +echo "[writer]: $(cat /tmp/r_writer.json)" +echo "[go-fast]: $(cat /tmp/r_go_fast.json)" +echo "[go-nil]: $(cat /tmp/r_go_nil.json)" +echo "[js-async]: $(cat /tmp/r_js_async.json)" +echo "[js-badenv]:$(cat /tmp/r_js_badenv.json)" + +echo "" +echo "=== РАУНД 2: повторный (горячий кэш) ===" +curl -s -m 15 "$BASE/stress-bigloop" -d '{"n":2000000}' -H "Content-Type:application/json" > /tmp/r2_bigloop.json & +curl -s -m 10 "$BASE/stress-go-fast" -d '{"n":20}' -H "Content-Type:application/json" > /tmp/r2_go_fast.json & +curl -s -m 20 "$BASE/stress-js-async" > /tmp/r2_async.json & +curl -s -m 35 "$BASE/stress-writer" -d '{"rows":10,"prefix":"batch2"}' -H "Content-Type:application/json" > /tmp/r2_writer.json & +wait +echo "[bigloop-2M]: $(cat /tmp/r2_bigloop.json)" +echo "[go-fast-20]: $(cat /tmp/r2_go_fast.json)" +echo "[js-async-2]: $(cat /tmp/r2_async.json)" +echo "[writer-10]: $(cat /tmp/r2_writer.json)" + +echo "" +echo "=== РАУНД 3: crash функции с неверными параметрами ===" +curl -s -m 10 "$BASE/stress-divzero" -d '{"n":100,"d":0}' -H "Content-Type:application/json" > /tmp/r3_dz.json & +curl -s -m 10 "$BASE/stress-go-nil" -d '{"crash":true}' -H "Content-Type:application/json" > /tmp/r3_nil.json & +curl -s -m 10 "$BASE/stress-js-badenv" -d '{"crash":true}' -H "Content-Type:application/json" > /tmp/r3_bad.json & +# divzero с нормальным делителем — должен вернуть результат +curl -s -m 10 "$BASE/stress-divzero" -d '{"n":42,"d":7}' -H "Content-Type:application/json" > /tmp/r3_ok.json & +# go-nil без краша — должен вернуть ok +curl -s -m 10 "$BASE/stress-go-nil" -d '{"crash":false}' -H "Content-Type:application/json" > /tmp/r3_nil_ok.json & +wait +echo "[divzero crash]: $(cat /tmp/r3_dz.json)" +echo "[go-nil crash]: $(cat /tmp/r3_nil.json)" +echo "[js-badenv crash]: $(cat /tmp/r3_bad.json)" +echo "[divzero ok 42/7]: $(cat /tmp/r3_ok.json)" +echo "[go-nil ok]: $(cat /tmp/r3_nil_ok.json)" + +echo "" +echo "=== ИТОГ: количество строк в таблице ===" +curl -s -m 15 "$BASE/pg-table-reader" +echo "" +echo "=== DONE ==="