Сравнительное нагрузочное тестирование Lua-коннекторов для Tarantool из NGINX

AnGeL

Местный
270
172
6 Мар 2016
В последнее время на появляется достаточно много статей про Для просмотра ссылки Войди или Зарегистрируйся — базу данных и сервер приложений, который используется в Mail.Ru Group, Avito, Yota в разных интересных проектах. И вот, я подумал – а чем мы хуже? Давайте тоже попробуем.

В силу своей профессиональной деформации буду рассматривать следующий кейс:

  • Есть Web-ресурс, доступ к которому мы хотим ограничить;
  • Сам ресурс менять нельзя или крайне нежелательно.

Как подступиться к данной задаче?
Для просмотра ссылки Войди или Зарегистрируйся, но непонятно как совместить это с Tarantool. Давайте последуем примеру OpenResty, и будем использовать для интеллектуализации нашего шлюза Lua, а именно Для просмотра ссылки Войди или Зарегистрируйся.

Для обращения к Tarantool изнутри NGINX нам потребуется соответствующий драйвер, или «коннектор» для выбранного языка. Сами авторы Tarantool пишут, что если вам понадобилось иметь коннектор к Tarantool из Lua – то у вас что-то не то с архитектурой. Но в случае со связкой NGINX+Lua это может быть оправданно.

Гугление показывает наличие в природе аж трёх кандидатов:

  • Для просмотра ссылки Войди или Зарегистрируйся — Реализован на nginx cosockets. Правда давно не обновлялся.
  • Для просмотра ссылки Войди или Зарегистрируйся — официальный драйвер от разработчиков Tarantool. Является доработанным форком первого кандидата. Помимо nginx cosockets поддерживает обычные сокеты Lua.
  • Для просмотра ссылки Войди или Зарегистрируйся — Уже два года без коммитов, нет поддержки семантики вызовов процедур Tarantool 1.7.

Что выбрать? Надо тестировать. Тем и займёмся.

Тестовый стенд:

4593516108d948a398c1817cd5b3ebf5.png


От генератора нагрузки запросы передаются на шлюз в защищенном TLS виде (не забываем про проф. деформацию). Далее NGINX снимает TLS, и передаёт их защищаемому ресурсу в виде обычного HTTP.

Характеристики тестовых машин

Нагрузочная машина и защищаемый ресурс

(две одинаковые машины)
Код:
CPU 2xIntel Xeon E5 2680 @ 2.70GHz Sandy Bridge-EP/EX 32nm Technology 8 Cores/16 threads
RAM 32,0ГБ DDR3 @ 799MHz (11-11-11-28)
MB Supermicro X9DR3-F
Disk 223GB OCZ-VERTEX3 (SSD)
OS Debian 8.9 x64 (ядро 3.16.39-1)
NGINX 1.12.1
wrk 4.0.2-dirty [epoll] + GOST TLS patches
Шлюз
CPU 1 vCPU
RAM 8 Gb
Platform VMWare Workstation 12.5
Host CPU Intel Core i5 7600K 3.8 GHz
Host RAM 16 Gb
Host OS Windows 10 x64
NGINX 1.12.1
lua-nginx-module Latest master branch

Конфиг NGINX защищаемого ресурса

Ничего необычного — просто пустой GIF.

user nginx;
worker_processes 32;

error_log /var/log/ngate/nginx/error.log warn;
pid /var/run/nginx.pid;

worker_rlimit_nofile 65535;

events {
worker_connections 8192;
}

http {
access_log /var/log/ngate/nginx/access.log main;
keepalive_timeout 65;

server {
listen 80;
server_name fast-ipsec2-db8;

location / {
root /var/www;
index index.html index.htm;
}

location = /ff/empty_gif.gif {
empty_gif;
}
} # end server
}


Конфиг NGINX шлюза:

Код:
worker_processes  1;

error_log  /var/log/nginx/error.log warn;

worker_rlimit_nofile 65535;

events {
   worker_connections  8192;
}

http {
   include       /etc/opt/nginx/mime.types;
   default_type  text/html;
   sendfile      on;
   keepalive_timeout  65;
   autoindex off;
   server_tokens off;

   lua_package_path '?.lua;/opt/lua/?.lua;';

   # HTTPS server
   server {
       listen       443 ssl;
       server_name  perf-test-1;

   ssl_certificate     www.example.com.crt;
   ssl_certificate_key www.example.com.key;
   ssl_protocols       TLSv1;
   ssl_ciphers         HIGH:!aNULL:!MD5;
  
       if ($request_method !~ ^(GET|HEAD|POST)$ )
       {
           return 444;
       }

       # Local Tarantool Node
       set $ng_local_tnt_addr  '127.0.0.1';
       set $ng_local_tnt_port  3320;

       # ff
       location /ff/ {
           proxy_pass http://fast-ipsec2-db8/ff/;
           access_by_lua_file /opt/lua/res_access.lua;
       }

   } # end server perf-test-1
} # end http

Обратите внимание на строчку:

access_by_lua_file /opt/lua/res_access.lua;

именно тут мы проверяем права пользователей.

Код /opt/lua/res_access.lua

Всё просто:

1. Извлекаем авторизационную куку;
2. Парсим запрос чтобы понять к какому ресурсу обращается пользователь;
3. Передаём полученные значения Tarantool, чтобы тот принял решение – пускать пользователя или нет.
4. Обрабатываем ответ Tarantool
5. В зависимости от ответа пускаем пользователя, или говорим «Access Denied».
Для простоты оставим за рамками статьи работу с правами доступа, и будем всегда пускать пользователя к ресурсу.

Код:
local auth_cookie_value = ngx.var.cookie_nginxauth
if auth_cookie_value == nil then
   ngx.log(ngx.WARN, "Authentication cookie not provided.")
   ngx.exit(ngx.HTTP_NOT_FOUND)
end

local uri_root_regex = "(\\/[a-zA-Z0-9\\-\\._]+\\/)"
local m, err = ngx.re.match(ngx.var.uri, uri_root_regex, "ai")
if err then
   ngx.log(ngx.ERR, "Error in regexp: ", err)
   ngx.exit(ngx.HTTP_NOT_FOUND)
end

if m == nil then
   ngx.log(ngx.ERR, "Regexp returned nil value.")
   ngx.exit(ngx.HTTP_NOT_FOUND)
end

local uri_root = m[0]

if uri_root == nil then
   ngx.log(ngx.ERR, "error in regexp")
   ngx.exit(ngx.HTTP_NOT_FOUND)
end

local tnt = require 'resty.tarantool'
#local tnt = require 'tarantool-lua.tarantool'

local tar, err = tnt:new({
   host = ngx.var.ng_local_tnt_addr,
   port = ngx.var.ng_local_tnt_port,
   --Default value 2000
   socket_timeout = 500,
#   connect_now = false,
})

if not tar:connect() then
   ngx.log(ngx.ERR, "TNT connection failed.")
   ngx.exit(ngx.HTTP_NOT_FOUND)
end

local res, err = tar:call('check_access', {auth_cookie_value, uri_root})

if not tar:set_keepalive() then
   ngx.log(ngx.WARN, "TNT connection not set as keep-alive.")
end

if not res then
   ngx.log(ngx.ERR, "TNT call failed: " .. err)
   ngx.exit(ngx.HTTP_NOT_FOUND)
end

if res[1] ~= nil and res[1][1] == true then
   -- Access granted
   ngx.log(ngx.INFO, "Resource access granted: " .. uri_root)
   return
else
   ngx.log(ngx.ERR, "Resource access denied: " .. uri_root)
   ngx.exit(ngx.HTTP_FORBIDDEN)
end


Код хранимой процедуры Tarantool

Так как авторизационная кука у нас всё равно игнорируется, не будем для простоты рассматривать процесс её получения и формирования.

Код:
local strict = require('strict')
strict.on()

function check_access(session_id, resource_name)
   if session_id == nil or resource_name == nil then
       return false
   end

   log.info('Access to resource ' .. resource_name .. ' granted.')
   return true
end


Методика тестирования

Для тестирования будем использовать утилиту wrk. Она хорошо зарекомендовала себя в нагрузочном тестировании и помимо TLS, поддерживает сценарии на Lua (хоть мы их и не будем использовать сейчас). Из особенностей – у wrk неотключаемый TLS Session Resumption, таким образом CPU шлюза не будет тратится на постоянные TLS-хендшейки. Чтобы проверить именно производительность проверок прав доступа в секунду, а не пропускную способность, будем запрашивать с защищаемого ресурса файл минимального размера – пустой GIF, занимающий 43 байта.

Приступим к тестированию.

Кандидат 1 (lua-nginx-tarantool):

Не работает совсем. Если использовать его согласно документации, то на


local tnt = require 'lua-nginx-tarantool.tarantool'
local tar, err = tnt:new({...})


Появляется ошибка:

runtime error: /opt/lua/res_access.lua:33: attempt to index local 'tnt' (a boolean value)


Разбираться не будем.

Кандидат 2 (tarantool-lua):

./wrk -t32 -c32 -d30s --latency --timeout 10s -H "Host: perf-test-1" -H "Cookie: nginxauth=XXX " Для просмотра ссылки Войди или Зарегистрируйся
Thread Stats Avg Stdev Max +/- Stdev
Latency 252.12ms 248.32ms 514.22ms 31.13%
Req/Sec 4.55 5.54 60.00 95.77%
Latency Distribution
50% 21.75ms
75% 502.22ms
90% 503.61ms
99% 507.63ms
3767 requests in 30.04s, 1.33MB read
Non-2xx or 3xx responses: 1868
Requests/sec: 125.40
Transfer/sec: 45.49KB

Во-первых – мало. Всего 125 запросов в секунду.

Во-вторых – больше половины ответов – не ожидаемые 200, а что-то иное. Что же это? Ответ находится в error_log NGIXN:

[error] 11856#0: *23410 [lua] res_access.lua:42: TNT connection failed.

Что-то идёт не так – то ли Tarantool отвергает соединения, то ли NGINX не может их переварить.
Но попробуем дальше.

Кандидат 3 (lua-resty-tarantool):

./wrk -t32 -c32 -d30s --latency --timeout 10s -H "Host: perf-test-1" -H "Cookie: nginxauth=XXX " Для просмотра ссылки Войди или Зарегистрируйся
Thread Stats Avg Stdev Max +/- Stdev
Latency 9.03ms 1.13ms 28.79ms 79.90%
Req/Sec 110.13 10.43 131.00 75.49%
Latency Distribution
50% 8.63ms
75% 9.46ms
90% 10.64ms
99% 11.81ms
105344 requests in 30.03s, 43.40MB read
Requests/sec: 3508.50
Transfer/sec: 1.45MB

Ого! 3500 запросов в секунду, и ни единой ошибки!
И в error_log NGINX – тишина.

Выводы

Несмотря на почтенный возраст, и некоторые недочёты Кандидат 3 (lua-resty-tarantool) явно является не просто лидером, а единственным вариантом использования в production. И ещё раз мы убедились в необходимости тестирования различных вариантов использования перед тем как принимать решение о использовании или не использовании той или иной технологии в реальных проектах.