Files
futriix/tests/backpressure_test.lua

343 lines
11 KiB
Lua
Raw Normal View History

2026-06-12 18:07:05 +00:00
-- tests/backpressure_test.lua
-- Тестирование механизма backpressure при перегрузке
local function setup_backpressure_test()
print("=== Setup Backpressure Test ===")
db.execute("CREATE DATABASE IF NOT EXISTS backpressure_test")
db.execute("USE backpressure_test")
db.execute([[
CREATE COLLECTION IF NOT EXISTS test_collection
WITH SETTINGS {
max_documents = 10000000
}
]])
-- Включаем backpressure с агрессивными настройками для теста
db.execute([[
SET BACKPRESSURE CONFIG {
enabled = true,
cpu_threshold = 0.50,
memory_threshold = 0.60,
queue_size_threshold = 100,
connection_threshold = 50,
low_delay_ms = 50,
medium_reject_prob = 30,
high_reject_prob = 70
}
]])
return true
end
local function test_backpressure_level_none()
print("\n=== Test 1: Backpressure Level NONE ===")
-- При нормальной нагрузке
local start_time = os.clock()
local success_count = 0
local reject_count = 0
for i = 1, 100 do
local success, err = pcall(function()
return db.execute(string.format([[
INSERT INTO test_collection VALUES {
_id = "doc_%d",
value = %d,
timestamp = %d
}
]], i, i, os.time() * 1000))
end)
if success then
success_count = success_count + 1
else
reject_count = reject_count + 1
if string.find(err, "backpressure") then
print("Unexpected rejection: " .. err)
end
end
end
local duration = os.clock() - start_time
print(string.format("Success: %d, Rejected: %d, Duration: %.3fs",
success_count, reject_count, duration))
if reject_count == 0 then
print("✅ No rejections under normal load")
return true
else
print("❌ Unexpected rejections: " .. reject_count)
return false
end
end
local function simulate_backpressure_condition()
print("Simulating high load condition...")
-- Симулируем высокую нагрузку
local threads = {}
for i = 1, 100 do
table.insert(threads, coroutine.create(function()
for j = 1, 100 do
local success = pcall(function()
db.execute(string.format([[
SELECT * FROM test_collection WHERE value > %d
]], math.random(1, 50)))
end)
if not success then
coroutine.yield(false)
end
db.sleep(0.001)
end
coroutine.yield(true)
end))
end
-- Запускаем все потоки
for _, thread in ipairs(threads) do
coroutine.resume(thread)
end
-- Ждём, пока сработает backpressure
db.sleep(5)
return true
end
local function test_backpressure_level_medium()
print("\n=== Test 2: Backpressure Level MEDIUM ===")
simulate_backpressure_condition()
local status = db.execute("SHOW BACKPRESSURE STATUS")
local bp_status = db.json_decode(status)
print(string.format("Current backpressure level: %s", bp_status.current_level))
print(string.format("Write allowed: %s", tostring(bp_status.write_allowed)))
print(string.format("Read allowed: %s", tostring(bp_status.read_allowed)))
print(string.format("Reject probability: %d%%", bp_status.reject_probability or 0))
-- Тестируем процент отказов
local total_requests = 500
local rejected = 0
for i = 1, total_requests do
local success, err = pcall(function()
return db.execute("INSERT INTO test_collection VALUES {_id = 'test_" .. i .. "', value = " .. i .. "}")
end)
if not success and string.find(err or "", "rejected") then
rejected = rejected + 1
end
end
local reject_rate = (rejected / total_requests) * 100
print(string.format("Actual reject rate: %.1f%% (expected: ~%d%%)",
reject_rate, bp_status.reject_probability or 30))
-- Проверяем, что reject_rate в разумных пределах
local expected_reject = bp_status.reject_probability or 30
if math.abs(reject_rate - expected_reject) < 20 then
print("✅ Rejection rate within acceptable range")
return true
else
print("❌ Rejection rate outside expected range")
return false
end
end
local function test_backpressure_with_delay()
print("\n=== Test 3: Backpressure with Delay ===")
-- Замеряем время выполнения при backpressure
local start_time = os.clock()
local delayed_requests = 0
for i = 1, 50 do
local req_start = os.clock()
db.execute("SELECT * FROM test_collection LIMIT 10")
local req_end = os.clock()
if req_end - req_start > 0.05 then -- больше 50ms задержки
delayed_requests = delayed_requests + 1
end
end
local total_duration = os.clock() - start_time
local avg_delay = total_duration / 50 * 1000 -- в миллисекундах
print(string.format("Avg request time: %.2f ms", avg_delay))
print(string.format("Requests with >50ms delay: %d/%d", delayed_requests, 50))
if avg_delay > 40 then
print("✅ Backpressure delays detected as expected")
return true
else
print("⚠️ Lower than expected delays")
return false
end
end
local function test_backpressure_recovery()
print("\n=== Test 4: Backpressure Recovery ===")
-- Даём системе восстановиться
print("Waiting for system recovery...")
db.sleep(30)
local status = db.execute("SHOW BACKPRESSURE STATUS")
local bp_status = db.json_decode(status)
print(string.format("Recovered backpressure level: %s", bp_status.current_level))
-- Проверяем, что запросы снова проходят
local success_count = 0
for i = 1, 100 do
local success = pcall(function()
return db.execute("INSERT INTO test_collection VALUES {_id = 'recovery_" .. i .. "', value = " .. i .. "}")
end)
if success then success_count = success_count + 1 end
end
print(string.format("Successful writes after recovery: %d/100", success_count))
if success_count > 95 then
print("✅ System recovered successfully")
return true
else
print("❌ Recovery incomplete: " .. success_count .. " successes")
return false
end
end
local function test_backpressure_metrics()
print("\n=== Test 5: Backpressure Metrics ===")
local metrics = db.execute("SHOW BACKPRESSURE METRICS")
local metrics_data = db.json_decode(metrics)
print("Backpressure metrics:")
for key, value in pairs(metrics_data) do
print(string.format(" %s: %s", key, tostring(value)))
end
local required_fields = {
"current_level", "write_allowed", "read_allowed",
"rejected_count", "delayed_count", "cpu_threshold"
}
for _, field in ipairs(required_fields) do
if metrics_data[field] == nil then
print("❌ Missing metric: " .. field)
return false
end
end
print("✅ All required metrics present")
return true
end
local function test_backpressure_burst_handling()
print("\n=== Test 6: Burst Handling ===")
-- Генерируем burst запросов
local burst_size = 1000
local start_time = os.clock()
local results = {}
-- Используем coroutines для параллельных запросов
local handlers = {}
for i = 1, burst_size do
table.insert(handlers, coroutine.create(function()
local success = pcall(function()
return db.execute(string.format([[
INSERT INTO test_collection VALUES {
_id = "burst_%d_%d",
value = %d,
timestamp = %d
}
]], os.time(), i, i, os.time() * 1000)
end)
coroutine.yield(success)
end))
end
-- Запускаем все handlers
for _, handler in ipairs(handlers) do
coroutine.resume(handler)
end
-- Собираем результаты
local success_count = 0
for _, handler in ipairs(handlers) do
local success = coroutine.resume(handler)
if success then
local result = coroutine.status(handler) == "dead" and true or false
if result then success_count = success_count + 1 end
end
end
local duration = os.clock() - start_time
local qps = burst_size / duration
print(string.format("Burst: %d requests in %.2fs (%.0f QPS)",
burst_size, duration, qps))
print(string.format("Successful: %d, Failed: %d",
success_count, burst_size - success_count))
if qps > 100 then
print("✅ Backpressure handling burst correctly")
return true
else
print("⚠️ Lower than expected QPS")
return false
end
end
local function cleanup_backpressure()
print("\n=== Cleanup ===")
db.execute("DROP DATABASE IF EXISTS backpressure_test")
-- Сбрасываем backpressure настройки
db.execute("SET BACKPRESSURE CONFIG DEFAULT")
print("Cleanup completed")
end
local function run_all_backpressure_tests()
print("╔══════════════════════════════════════════════════════════════╗")
print("║ BACKPRESSURE TESTS SUITE ║")
print("╚══════════════════════════════════════════════════════════════╝")
setup_backpressure_test()
local results = {
test_backpressure_level_none(),
test_backpressure_level_medium(),
test_backpressure_with_delay(),
test_backpressure_recovery(),
test_backpressure_metrics(),
test_backpressure_burst_handling()
}
print("\n╔══════════════════════════════════════════════════════════════╗")
print("║ TEST RESULTS ║")
print("╚══════════════════════════════════════════════════════════════╝")
local passed = 0
for i, result in ipairs(results) do
local status = result and "✅ PASSED" or "⚠️ SKIPPED/FAILED"
print(string.format("Test %d: %s", i, status))
if result then passed = passed + 1 end
end
print(string.format("\nTotal: %d/%d tests passed", passed, #results))
cleanup_backpressure()
return passed == #results
end
return run_all_backpressure_tests()