-- 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()