doc: Go runtime v0.1.1, баги 4+5, решения pgx/v5 + dynamic timeout

- progress.md: секция v0.1.1 →  ЗАВЕРШЕНО, все задачи, таблица таймаутов, арх. функции
- errors/log.md: Баг 4 (invoke.go 30s хардкод → context deadline exceeded) + Баг 5 (nginx ingress отсутствие proxy-read-timeout → 504)
- decisions/log.md: pgx/v5 vs database/sql+lib/pq (с обоснованием), динамический таймаут (почему +5s, почему не кешировать, деградация)
This commit is contained in:
Naeel
2026-03-19 21:39:34 +03:00
parent d7fda15d35
commit 45bd9389e5
3 changed files with 232 additions and 18 deletions
+69
View File
@@ -861,3 +861,72 @@ for _, k := range keys { envVars = append(envVars, corev1.EnvVar{Name: k, Value:
**Тесты:** 2 теста в `controllers/function_controller_unit_test.go`
(4 env vars → алфавитный порядок после SLESS_ENTRYPOINT; пустой Env → только SLESS_ENTRYPOINT).
---
## 2026-03-19 — pgx/v5 как PG-драйвер для Go функций (vs database/sql + lib/pq)
**Контекст:** Go runtime v0.1.1 — добавляем прямой доступ к PostgreSQL из функций.
Нужно выбрать: database/sql + lib/pq, или чистый pgx/v5?
**Решение:** Использовать `github.com/jackc/pgx/v5` напрямую, без обёртки database/sql.
**Причины:**
1. **pgxpool из коробки**`pgxpool.New()` без дополнительных пакетов. lib/pq требует `sql.Open` + настройку пула через `db.SetMaxOpenConns` и т.д.
2. **Нативный протокол PostgreSQL** — pgx реализует wire protocol напрямую, без CGO.
lib/pq тоже pure Go, но pgx быстрее (~20% в бенчмарках) и активнее поддерживается.
3. **Контекст-нативность**`pgxpool.Pool.Query(ctx, ...)` — context как первый аргумент везде.
В database/sql контекст пришёл только в Go 1.8 как `QueryContext` — неудобный retrofit.
4. **Сканирование строк**`pgx.CollectRows`, `pgx.ForEachRow` — удобнее чем `rows.Scan`.
5. **Экосистема** — pgx — де-факто стандарт в Go+PG проектах (используется в pgx, pgvector, ent).
**Что добавлено в рантайм:**
```
github.com/jackc/pgx/v5 v5.7.2
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-... // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect (connection pool)
golang.org/x/crypto v0.31.0 // indirect (scram auth)
golang.org/x/sync v0.10.0 // indirect
golang.org/x/text v0.21.0 // indirect
```
**go mod download** добавлен в Dockerfile до COPY server.go — слой с зависимостями кешируется отдельно.
Пересборка функции (только изменение handler.go) не перекачивает ~15MB зависимостей.
---
## 2026-03-19 — Динамический таймаут в invoke.go из Function.Spec.TimeoutSec
**Контекст:** invoke.go проксирует HTTP-запросы к подам функций. До этого — глобальный `http.Client{Timeout: 30s}`.
**Проблема:** 30s — константа времени написания кода. Функции с `timeout_sec=700` (stress-тесты, batch-задачи) падают с `context deadline exceeded` раньше чем успевают завершиться.
**Решение:** Перед каждым вызовом читать `Function.Spec.TimeoutSec` из k8s и создавать `http.Client` с таймаутом = `TimeoutSec + 5s`.
**Почему +5s буфер:**
- Нельзя ставить ровно `TimeoutSec` — есть сетевые задержки, TLS handshake, время на DNS резолв внутри кластера.
- 5s достаточно для любых сетевых задержек в локальном k8s кластере.
- Если функция реально завис на TimeoutSec — runtime сам должен прервать работу (это ответственность функции, не прокси).
**Почему не кешировать http.Client:**
- Каждый вызов может прийти к разной функции с разным TimeoutSec.
- http.Client создаётся дёшево — только структура с одним полем Timeout.
- Кеш потребовал бы sync.Map или mutex — лишняя сложность без измеримой пользы.
**Деградация при недоступности k8s:**
```go
if err := h.K8s.Get(r.Context(), client.ObjectKey{...}, fn); err == nil {
timeoutSec = fn.Spec.TimeoutSec
}
// если Get упал — timeoutSec=0 → invokeHTTPClient вернёт 30s (дефолт)
```
Это осознанный выбор: если мы не можем прочитать функцию — мы не знаем её таймаут,
используем разумный дефолт вместо возврата ошибки.
**Коммит:** `d7fda15`, оператор `v0.1.40`