343 lines
11 KiB
Lua
343 lines
11 KiB
Lua
-- 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()
|