docs: bench PV results 2026-05-20 PASS=20/20; misc updates
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
# Руководство: контракт функций в Fission runtime
|
||||
|
||||
Дата: 2026-05-19
|
||||
Версия консоли: v1.3.94+
|
||||
Источник: дизассемблирование реальных env-образов в кластере + отладка тестов
|
||||
|
||||
---
|
||||
|
||||
## Содержание
|
||||
|
||||
1. [PHP](#php)
|
||||
2. [Node.js](#nodejs)
|
||||
3. [Python](#python)
|
||||
4. [Ruby](#ruby)
|
||||
5. [Найденные баги и исправления](#баги)
|
||||
|
||||
---
|
||||
|
||||
## PHP
|
||||
|
||||
### Образ
|
||||
```
|
||||
ghcr.io/fission/php-env:latest
|
||||
```
|
||||
Сервер: `ReactPHP` на порту `:8888`, файл `/app/server.php`.
|
||||
|
||||
### Entrypoint
|
||||
```
|
||||
main.php::handler
|
||||
```
|
||||
Формат: `filename::functionName`. Двойное двоеточие — разделитель файла и функции.
|
||||
|
||||
### ⚠️ ГЛАВНЫЙ БАГ (2026-05-19)
|
||||
|
||||
**`return ['statusCode' => 200, 'body' => ...]` — PHP env ИГНОРИРУЕТ return value функции.**
|
||||
|
||||
Почему: server.php создаёт `$response = new Response()` (пустой PSR-7 объект),
|
||||
вызывает функцию и возвращает этот же объект без чтения результата вызова:
|
||||
|
||||
```php
|
||||
// server.php — реальный код PHP env:
|
||||
if (function_exists($userFunction)) {
|
||||
$response = new Response();
|
||||
ob_end_clean();
|
||||
$userFunction(['request' =>$request, 'response' => $response, 'logger' => $logger]);
|
||||
return $response; // ← возвращает оригинальный объект, не результат вызова
|
||||
}
|
||||
```
|
||||
|
||||
### Правильный формат — PSR-7
|
||||
|
||||
Функция должна **писать** в `$ctx["response"]->getBody()`:
|
||||
|
||||
```php
|
||||
<?php
|
||||
function handler($ctx) {
|
||||
$response = $ctx["response"];
|
||||
$response->getBody()->write(json_encode([
|
||||
'status' => 'ok',
|
||||
'value' => 42,
|
||||
]));
|
||||
}
|
||||
```
|
||||
|
||||
Доступные ключи контекста:
|
||||
- `$ctx["request"]` — `Psr\Http\Message\ServerRequestInterface`
|
||||
- `$ctx["response"]` — `Psr\Http\Message\ResponseInterface` (RingCentral\Psr7)
|
||||
- `$ctx["logger"]` — `Monolog\Logger`
|
||||
|
||||
### Backwards compatibility (echo)
|
||||
|
||||
Если функция `handler` НЕ объявлена, PHP env перехватывает весь вывод через `ob_start()` и возвращает его как тело ответа:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Работает, но нет доступа к request/response/logger
|
||||
echo json_encode(['key' => 'value']);
|
||||
```
|
||||
|
||||
Использовать только для самых простых случаев без работы с запросом.
|
||||
|
||||
### Примеры
|
||||
|
||||
**Минимальный (PSR-7):**
|
||||
```php
|
||||
<?php
|
||||
function handler($ctx) {
|
||||
$ctx["response"]->getBody()->write("Hello from PHP");
|
||||
}
|
||||
```
|
||||
|
||||
**С JSON-ответом:**
|
||||
```php
|
||||
<?php
|
||||
function handler($ctx) {
|
||||
$result = ['sum' => 1 + 2, 'lang' => 'php'];
|
||||
$ctx["response"]->getBody()->write(json_encode($result));
|
||||
}
|
||||
```
|
||||
|
||||
**С чтением тела запроса:**
|
||||
```php
|
||||
<?php
|
||||
function handler($ctx) {
|
||||
$body = json_decode((string)$ctx["request"]->getBody(), true);
|
||||
$name = $body['name'] ?? 'World';
|
||||
$ctx["response"]->getBody()->write("Hello, $name!");
|
||||
}
|
||||
```
|
||||
|
||||
**CPU-нагрузка:**
|
||||
```php
|
||||
<?php
|
||||
function fib($n) {
|
||||
if ($n < 2) return $n;
|
||||
return fib($n-1) + fib($n-2);
|
||||
}
|
||||
function handler($ctx) {
|
||||
$ctx["response"]->getBody()->write('result:' . fib(28));
|
||||
}
|
||||
```
|
||||
|
||||
### Что НЕ работает
|
||||
|
||||
```php
|
||||
// ❌ НЕПРАВИЛЬНО — return игнорируется
|
||||
function handler(array $ctx): array {
|
||||
return ['statusCode' => 200, 'body' => 'hello'];
|
||||
}
|
||||
|
||||
// ❌ НЕПРАВИЛЬНО — echo не работает если функция объявлена
|
||||
function handler($ctx) {
|
||||
echo "hello"; // ob_start уже закрыт перед вызовом
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Node.js
|
||||
|
||||
### Образ
|
||||
```
|
||||
ghcr.io/fission/node-env:latest
|
||||
```
|
||||
Сервер: Express на порту `:8888`, файл `/usr/src/app/server.js`.
|
||||
|
||||
### Entrypoint
|
||||
```
|
||||
main
|
||||
```
|
||||
**Без расширения `.js`.**
|
||||
|
||||
### ⚠️ ГЛАВНЫЙ БАГ (2026-05-19)
|
||||
|
||||
**`functionName = "main.js"` ломает специализацию.**
|
||||
|
||||
Почему: server.js делает `split(".")` для любого functionName содержащего точку:
|
||||
|
||||
```js
|
||||
// server.js — реальный код node-env:
|
||||
if (req.body.functionName && req.body.functionName.includes('.')) {
|
||||
// ПРЕДПОЛАГАЕТСЯ формат: 'file.function'
|
||||
const entrypoint = req.body.functionName.split(".");
|
||||
filename = entrypoint[0]; // "main"
|
||||
funcname = entrypoint[1]; // "js" ← ищет экспорт с именем "js"!
|
||||
}
|
||||
```
|
||||
|
||||
`"main.js"` → `filename="main"`, `funcname="js"` → после загрузки модуля ищет `module["js"]` → undefined → specialize 500.
|
||||
|
||||
**Правильно:** `functionName = "main"` → `funcname = undefined` → берёт `default export` = `module.exports`.
|
||||
|
||||
### Формат кода
|
||||
|
||||
Консоль оборачивает пользовательский код в CJS wrapper. Пользователь пишет:
|
||||
|
||||
```js
|
||||
// Простая функция, экспортирует async handler:
|
||||
module.exports = async function(ctx) {
|
||||
return { status: 200, body: "Hello from Node.js" };
|
||||
};
|
||||
```
|
||||
|
||||
Или через именованный экспорт:
|
||||
```js
|
||||
async function handler(ctx) {
|
||||
return { status: 200, body: "ok" };
|
||||
}
|
||||
module.exports = handler;
|
||||
```
|
||||
|
||||
Формат возвращаемого объекта:
|
||||
```js
|
||||
{ status: 200, body: "строка или JSON" }
|
||||
```
|
||||
|
||||
### Примеры
|
||||
|
||||
**Минимальный:**
|
||||
```js
|
||||
module.exports = async function(ctx) {
|
||||
return { status: 200, body: "hello-node" };
|
||||
};
|
||||
```
|
||||
|
||||
**С JSON:**
|
||||
```js
|
||||
module.exports = async function(ctx) {
|
||||
const result = { lang: "nodejs", fib25: 75025 };
|
||||
return { status: 200, body: JSON.stringify(result) };
|
||||
};
|
||||
```
|
||||
|
||||
**CPU-нагрузка:**
|
||||
```js
|
||||
function fib(n) { return n < 2 ? n : fib(n-1) + fib(n-2); }
|
||||
module.exports = async function(ctx) {
|
||||
return { status: 200, body: "fib35:" + fib(35) };
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Python
|
||||
|
||||
### Образ
|
||||
```
|
||||
naeel/fission-python-env:v1.1
|
||||
```
|
||||
(НЕ использовать официальный `ghcr.io/fission/python-env` — не поддерживает `def main(event, context)`)
|
||||
|
||||
### Entrypoint
|
||||
```
|
||||
main.main
|
||||
```
|
||||
Формат: `module.function`.
|
||||
|
||||
### Формат функции
|
||||
|
||||
```python
|
||||
def main(event, context):
|
||||
return {
|
||||
"status": 200,
|
||||
"body": "Hello from Python"
|
||||
}
|
||||
```
|
||||
|
||||
### Примеры
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
def main(event, context):
|
||||
result = {"lang": "python", "sum": sum(range(100))}
|
||||
return {"status": 200, "body": json.dumps(result)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ruby
|
||||
|
||||
### Образ
|
||||
```
|
||||
ghcr.io/fission/ruby-env:latest
|
||||
```
|
||||
|
||||
### Entrypoint
|
||||
```
|
||||
handler
|
||||
```
|
||||
Имя метода в загруженном файле.
|
||||
|
||||
### Формат функции
|
||||
|
||||
```ruby
|
||||
def handler(event, context)
|
||||
{ statusCode: 200, body: "Hello from Ruby" }
|
||||
end
|
||||
```
|
||||
|
||||
### Примеры
|
||||
|
||||
```ruby
|
||||
require 'json'
|
||||
|
||||
def handler(event, context)
|
||||
result = { lang: "ruby", value: (1..10).reduce(:+) }
|
||||
{ statusCode: 200, body: result.to_json }
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Баги
|
||||
|
||||
### БАГ A — ExpiryReaper race condition (исправлен в v1.3.93)
|
||||
|
||||
**Симптом:** PHP/последний язык в цикле CREATE теряет Package — удаляется ExpiryReaper'ом.
|
||||
|
||||
**Причина:** `activeFunctions` snapshot снимается ДО создания Function. Reaper проверяет orphan packages по устаревшему снимку → последний Package = orphan → удалён.
|
||||
|
||||
**Коммит:** `72e5d50`
|
||||
**Файл:** `console/internal/cloud/tenant.go`
|
||||
**Fix:** свежий LIST functions (`freshFunctions`) перед orphan-check вместо кешированного снимка.
|
||||
|
||||
---
|
||||
|
||||
### БАГ B — Node.js entrypoint `"main.js"` ломает specializeV2 (исправлен в v1.3.94)
|
||||
|
||||
**Симптом:** Node.js функция не специализируется, invoke timeout.
|
||||
|
||||
**Причина:** node-env `specializeV2` делает `split(".")` по любой точке в functionName. `"main.js"` → `funcname="js"` → ищет `module["js"]` → undefined → specialize 500 → бесконечный retry.
|
||||
|
||||
**Коммит:** `090a7af`
|
||||
**Файл:** `console/internal/runtime/entrypoint.go`
|
||||
**Fix:** entrypoint Node.js = `"main"` (без расширения). funcname=undefined → берёт `default export`.
|
||||
|
||||
---
|
||||
|
||||
### БАГ C — PHP `return array` игнорируется (исправлен в test_heavy.sh, v1.3.94)
|
||||
|
||||
**Симптом:** PHP invoke возвращает `status=200` но `response_raw=""`.
|
||||
|
||||
**Причина:** PHP env создаёт пустой `$response` объект, вызывает функцию, возвращает оригинальный объект — return value функции не читается.
|
||||
|
||||
**Коммит:** `042b1e5`
|
||||
**Файл:** `scripts/test_heavy.sh`
|
||||
**Fix:** PHP код в тестах переписан на PSR-7: `$ctx["response"]->getBody()->write(...)`.
|
||||
|
||||
---
|
||||
|
||||
## Сводная таблица: правильный контракт
|
||||
|
||||
| Язык | Entrypoint | Формат функции | return/write |
|
||||
|--------|---------------------|-------------------------------------------------------|---------------------------|
|
||||
| Python | `main.main` | `def main(event, context):` | `return {"status":200,...}`|
|
||||
| Node.js| `main` | `module.exports = async function(ctx) {...}` | `return { status, body }` |
|
||||
| PHP | `main.php::handler` | `function handler($ctx) {...}` | `$ctx["response"]->getBody()->write(...)` |
|
||||
| Ruby | `handler` | `def handler(event, context)` | `{ statusCode: 200, body: ... }` |
|
||||
@@ -0,0 +1,40 @@
|
||||
# Bench Results — PV Storage — 2026-05-20
|
||||
|
||||
**RUNID:** 9250986
|
||||
**Дата:** Wed May 20 07:23:06–07:27:07 MSK 2026
|
||||
**Storage:** PVC (PV)
|
||||
**API:** https://fission.kube5s.ru/console/api
|
||||
**SUB:** bench@bench.local
|
||||
**Итог: PASS=20 FAIL=0 TOTAL=20**
|
||||
|
||||
## Таблица результатов
|
||||
|
||||
| LANG | SIZE | UPLOAD(ms) | COLD(ms) | HOT_AVG(ms) | STATUS |
|
||||
|--------|---------|-----------|---------|------------|--------|
|
||||
| python | tiny | 308 | 4384 | 771 | OK |
|
||||
| python | small | 161 | 1947 | 1860 | OK |
|
||||
| python | medium | 2751 | 1387 | 1183 | OK |
|
||||
| python | large | 2764 | 1382 | 1184 | OK |
|
||||
| nodejs | tiny | 3150 | 8011 | 1175 | OK |
|
||||
| nodejs | small | 2764 | 1526 | 1198 | OK |
|
||||
| nodejs | medium | 2745 | 2597 | 1185 | OK |
|
||||
| nodejs | large | 2737 | 2603 | 1314 | OK |
|
||||
| ruby | tiny | 3148 | 2105 | 1209 | OK |
|
||||
| ruby | small | 2768 | 1339 | 1194 | OK |
|
||||
| ruby | medium | 2759 | **81549** | 1183 | OK (аномалия cold, 3-й прогон подряд) |
|
||||
| ruby | large | 2743 | 1390 | 787 | OK |
|
||||
| php | tiny | 1547 | 2546 | 94 | OK |
|
||||
| php | small | 904 | 1106 | 1213 | OK |
|
||||
| php | medium | 2744 | 1571 | 1198 | OK ✓ FIXED |
|
||||
| php | large | 2732 | 1508 | 1151 | OK ✓ FIXED |
|
||||
| python | archive | 2759 | 1392 | 1178 | OK |
|
||||
| nodejs | archive | 3177 | 1545 | 1197 | OK |
|
||||
| ruby | archive | 2780 | 1376 | 1187 | OK |
|
||||
| php | archive | 2785 | 1416 | 1172 | OK |
|
||||
|
||||
## Что было исправлено
|
||||
|
||||
PHP medium/large — заменены arrow functions `fn()=>` на традиционный синтаксис `function(){}`.
|
||||
PHP env в кластере < 7.4, arrow functions добавлены в PHP 7.4.
|
||||
|
||||
Лог на VM: `/home/naeel/terra/fission/test-results/bench-func/bench_full_2026-05-20_07-23-06.log`
|
||||
@@ -1,5 +1,98 @@
|
||||
# Progress Log
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-19 (вечер) — Node.js require fix (v1.3.95) + суровые тесты
|
||||
|
||||
### Ветка: `feat/console-pv-storage` (git commit `fe8870a`)
|
||||
|
||||
---
|
||||
|
||||
### Исправленный баг: Node.js `require` недоступен внутри функции
|
||||
|
||||
**Симптом:** Любая Node.js функция, использующая `require(...)`, падала с:
|
||||
```
|
||||
ERR_MODULE_NOT_FOUND: Cannot find module '/userfunc/deployarchive/main'
|
||||
```
|
||||
|
||||
**Реальная причина:** В `console/internal/runtime/nodejs.go` wrapper генерировался как:
|
||||
```js
|
||||
(new Function('module', 'exports', code))(__mod, __mod.exports);
|
||||
```
|
||||
`new Function` выполняется в изолированном глобальном scope — `require` там не существует.
|
||||
Вызов `require('crypto')` внутри кода → `ReferenceError` → ESM fallback → `import('file:///userfunc/deployarchive/main')` без `.js` → ERR_MODULE_NOT_FOUND.
|
||||
|
||||
**Fix** (`console/internal/runtime/nodejs.go`):
|
||||
```js
|
||||
// было:
|
||||
(new Function('module', 'exports', code))(__mod, __mod.exports);
|
||||
// стало:
|
||||
(new Function('module', 'exports', 'require', code))(__mod, __mod.exports, require);
|
||||
```
|
||||
|
||||
**Также исправлено** (`console/internal/api/package.go`):
|
||||
Regex `nodeJSWrapperPattern` обновлён — принимает оба формата (старый без `require` и новый с `require`), backward compatible.
|
||||
|
||||
**Unit-тест** (`console/internal/runtime/nodejs_test.go`):
|
||||
Убраны устаревшие ESM-проверки, добавлены актуальные CJS-проверки.
|
||||
|
||||
**Образ:** `naeel/fission-console:v1.3.95`
|
||||
|
||||
---
|
||||
|
||||
### Прогоны `test_heavy.sh`
|
||||
|
||||
Два прогона подряд с теми же условиями (namespace `fission-c3511430abe5675b`):
|
||||
|
||||
| Секция | af2f674f | be04cc1b | Вывод |
|
||||
|--------|---------|---------|-------|
|
||||
| S1 CREATE (4 яз) | PHP 502 → FAIL | **4/4 PASS** | PHP 502 в первом — transient |
|
||||
| S1 invoke "не стабилизировался" | FAIL×4 | FAIL×4 | баг теста — WAIT_SEC < cold start |
|
||||
| S2 invoke "не стабилизировался" | FAIL×4 | FAIL×4 | то же |
|
||||
| S3 BIG RESPONSE size=46/48 | FAIL×2 | FAIL×2 | баг теста — проверяет поле `raw` (truncated), не реальный ответ |
|
||||
| S4 invoke "не стабилизировался" | FAIL×4 | FAIL×4 | баг теста — WAIT_SEC < cold start |
|
||||
| S5 RAPID 12/12 | PASS | PASS | ✓ |
|
||||
| S6 Python PARALLEL 5/5 | PASS | PASS | ✓ |
|
||||
| S6 Node.js PARALLEL 5/5 | **2/5 FAIL** | **2/5 FAIL** | pool size=1 не хватает на 5 параллельных — стабильно |
|
||||
| S6 Ruby PARALLEL 5/5 | PASS | PASS | ✓ |
|
||||
| S6 PHP PARALLEL 5/5 | 1/5 FAIL | **PASS** | первый — transient |
|
||||
| S7 Python SLOW(6s) | PASS | PASS | ✓ |
|
||||
| S7 Node.js SLOW(6s) | PASS (TRY=2) | PASS (TRY=1) | ✓ require fix работает |
|
||||
| S8 Python MEMORY(20MB) | PASS | PASS | ✓ |
|
||||
| S8 Node.js MEMORY(20MB) | PASS | PASS | ✓ |
|
||||
| **ИТОГ** | **PASS=29 FAIL=17** | **PASS=31 FAIL=15** | |
|
||||
|
||||
Логи: `test-results/2026-05-19_*` (два файла)
|
||||
|
||||
---
|
||||
|
||||
### Анализ стабильных FAIL=15 (оба прогона)
|
||||
|
||||
#### 1. S1+S2+S4 "не стабилизировался" (12 FAIL)
|
||||
**Причина:** `WAIT_SEC` в `test_heavy.sh` меньше реального cold start.
|
||||
Функции реально работают — проверено прямыми SSH-запросами через `sleep + curl`.
|
||||
**Что нужно:** увеличить `WAIT_SEC` или добавить retry-loop с адекватным таймаутом.
|
||||
|
||||
#### 2. S3 BIG RESPONSE (2 FAIL)
|
||||
**Причина:** тест проверяет `response_raw length` — это длина JSON-поля `raw` из ответа консоли.
|
||||
Консоль обрезает `raw` до ~46-48 символов. Реальный ответ функции = 50KB (status=200 подтверждено).
|
||||
**Что нужно:** либо фиксить тест (проверять Content-Length/реальный body), либо фиксить консоль (не обрезать `raw`).
|
||||
|
||||
#### 3. S6 Node.js PARALLEL 2/5 (1 FAIL)
|
||||
**Причина:** pool size=1 (один pod). При 5 параллельных запросах 2 таймаутятся пока ждут специализацию нового пода.
|
||||
Python/Ruby справляются 5/5 — у них специализация быстрее.
|
||||
**Что нужно:** либо увеличить pool size в Node.js environment, либо уменьшить параллельность в тесте до 3.
|
||||
|
||||
---
|
||||
|
||||
### Что делать дальше с тестами
|
||||
|
||||
- [ ] Исправить `test_heavy.sh` S1/S2/S4: увеличить `WAIT_SEC` до 120-180s или добавить retry-poll
|
||||
- [ ] Исправить `test_heavy.sh` S3: проверять реальный ответ, не поле `raw`
|
||||
- [ ] Решить S6 Node.js PARALLEL: увеличить pool или снизить параллельность
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-19 — PV Storage Migration + Fetcher v1.23.0
|
||||
|
||||
### Ветка: `feat/console-pv-storage` (git commit `2aadae3`, pushed)
|
||||
|
||||
Reference in New Issue
Block a user