Files
futriix/tests/backpressure_test.lua

343 lines
11 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- 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()