top of page

Redis, memcached, Rack, Nginx… y lua

  • Foto del escritor: Alejandro Rivero
    Alejandro Rivero
  • 20 jun 2019
  • 5 Min. de lectura

Quiero montar un sistema de polling (como AnyCable o Action Cable pero sin websockets. O bien polls de alta frecuencia o bien long polling) y si bien parece logico que los mensajes se sirvan desde alguna arquitectura basada en RAM, no esta claro que haya muchas formas de acceder rapidamente a un mensaje que este alojado en Redis.

En primera aproximacion, si todo estuviera corriendo en un solo nucleo, la velocidad del codigo nos la daria el numero de tareas por segundo. Si tardamos un milisegundo, cada nucleo podra servir mil solicitudes por segundo, cada una tendra que esperar del orden de un segundo a que le toque turno, modulo numero de hilos, multiplexado y demas.  Asi que cuando estamos en el orden del milisegundo, cualquier detallito que te aumente el tiempo de ejecucion te quita capacidad de servicio. Eso descarta servir desde el framework; Rails en un solo core no va a poder hacer mas de 80 o 100 queries por segundo, porque tiene demasiado que ejecutar.

Si queremos mantenernos dentro de ruby, lo mas rapido es incorporar una microapp de Rack en paralelo con el rails, algo asi como esta:

class RodaApp < Roda redis = Redis.new route do |r| r.on "get" do response['Content-Type'] = 'application/text' redis.get(r.params["key"]) end r.on "set" do redis.set(r.params["key"],r.params["value"]) end end end map "/RedisRoda" do run RodaApp.freeze.app end

Instalado en un webserver Puma con dos cores y probando desde un servidor vecino (con la herramienta «hey», luego le doy un repaso con «wrk») esto parece aguantar unas 4500 conexiones por segundo, lo que significa que incluso con 1000 conexiones se puede contestar la clave en un tiempo razonable, de hecho una media de 0.2 segundos.

Pero todavia es mejorable. Es una capacidad similar a la que tiene puma para servir ficheros estaticos, una tarea para la que normalmente delegamos en el servidor web principal. Pero claro, si le decimos al nginx que llame a ruby o a python, ya no tenemos nada que ganar.

Ahora, en ubuntu viene una expansion para nginx que se llama lua-nginx-redis y que corresponde no a la distribucion oficial sino a otra bastante facil de encontrar online, openresty, que pivota sobre codigo en lua. Asi añade a nginx la capacidad para generar contenido directamente. Sin compilar a bytecode, un script metido a lo bruto en el nginx seria:

lua_package_path "/usr/share/lua/5.1/nginx/?.lua;;"; init_by_lua_block { local json require "cjson" local redis = require "redis" } location /luaRedis { content_by_lua ' local redis = require "redis" local red = redis:new() red:set_timeout(1000) local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.say("failed to connect: ", err) ngx.exit(501) return end --local args = ngx.req.get_post_args() local res, err = red:get("data") ngx.say("data:",res) --red:close() local ok, err = red:set_keepalive(10000, 1024) '; }

En este caso es dificil estimar la velocidad, el script lua se ejecuta en el nginx, pero mientras llama y espera respuesta del redis puede atender otro de manera que no se bloquea. El resultado es que en realidad estamos empleando dos nucleos y medio, 200% para nginx, 50% para redis. Se sigue consiguiendo una respuesta media de 0.25 segundos por llamada, pero es posible encajar unas 25000-30000 llamadas por segundo.  Practicamente el mismo resultado que si estuviera sirviendo ficheros estaticos, aunque a costa de quemar CPU.

Una alternativa a Redis es memcached, bien tambien con el lua, bien utilizando los filtros en la configuracion del nginx. Esto ultimo tendria la ventaja de ser completamente out-of-the-box en el lado de lectura, dado que nginx viene ya con una instruccion memcached_pass. Simplemente hay que meter los datos, bien con el nginx de los de taobao, bien con llamadas desde algun otro cliente.

Por ultimo, se pueden considerar frameworks de comunicacion ya completos. Aqui estarian los de AccionCable y Anycable, si queremos ir al mundo de los websockets. Pero tambien habria que considerar nchan, que tiene modulo propio para nginx y con un nchan_redis_pass puede pivotar sobre rediris, asi que en cierto modo es la solucion ya hecha.

EDIT: he probado los tiempos desde una maquina de amazon y si bien la regla general es la misma, el escalado depende mucho de los hilos con los que se ataque.  Vease la tabla que sigue. Probando con otras herramientas y tal las conclusiones pueden ser:

  • En estatico uno puede contar con que nginx llegue a servir hasta 30000 conexiones por segundo y nucleo virtual (o hyperthread).

    • Quizas hasta 45000 segun carga y disponibilidad, o en una bare metal segun lo reciente que sea.

    • En general se acerca a golpear los anchos de banda de las tarjetas de red, asi que no todos los hilos de una maquina deberian dedicarse a servir ficheros.

    • La gente de techempower ya noto en el 2014 que su maquina de 24 hilos, la tipica Xeon E5 de la epoca, marcaba mas de un millon de requests por segundo, eso son 25000 por hilo.

  • Con lua dentro de nginx, se pueden servir hasta 20000. Quizas es prudente estimar que se sirve la mitad de lo que puede hacer nginx desde estaticos.

    • ¿esto significa que todavia hoy en dia se puede considerar modificar los estaticos como forma de comunicacion? Bueno, falta probar memcached, que es nativo de nginx.

  • Si se trata de ejecutar Ruby, una app bare desde puma podra hacer 4000 request por segundo, un poco menos, digamos 2500 r/s, si necesita llamar a algo como Redis.

  • Si se trata de ejecutar Rails sin ningun tipo de cache, solo vamos a tener 80-90 requests por segundo por nucleo virtual. Obviamente acelerables si se cachea parte del procesado de pagina.

Ademas, hay que considerar que la parte record de la velocidad de conexion se alcanza gracias a poder acumular el trafico y reusar el TCP, porque estamos ya en rangos de decimas o centesimas de milisegundo. Esta no es inhabitual porque los servidores estan detras del balanceador de carga, pero naturalmente deja la duda de cuantos usuarios desde IPs y puertos diferentes aguanta un haproxy.

conexiones e hilos

tipo

Total r/s

Latencia

Req/s???

30000/4000

ficheroNginx

80135.11

72.77ms

27.39


RedisNginx

43483.24

63.84ms

28.04


RedisRoda

14817.24

123.74ms

19.39

20000/2500

ficheroNginx

64567.08

79.07ms

31.14


RedisNginx

35710.03

79.41ms

31.26


RedisRoda

12034.15

132.66ms

23.36

10000/2500

ficheroNginx

54178.17

82.66ms

28.66


RedisNginx

15688.18

46.92ms

26.40


TrivialNginx

56044.39

116.54ms

30.69


RedisRoda

11898.33

148.82ms

18.31


TrivialRack

6550.91

233.04ms

11.22

5000/2500

ficheroNginx

48145.33

87.80ms

27.72


RedisNginx

28712.54

57.59ms

22.61


TrivialNginx

38812.61

60.89ms

29.14


RedisRoda

11325.65

158.92ms

12.57


TrivialRack

6552.57

281.89ms

8.03

5000/3000

Rails page

238.90

1.20s

0.00 (alto Timeout)

3000/3000

Rails Page

243.03


0.00 (alto Timeout)

300/300

Rails Page

1.23MB


(alto Timeout)

1000/1000

Hijack3

5313.03

45.03ms

11.25


Hijack2

2400.95

45.40ms

10.85


Hijack1

1972.60

48.75ms

10.39


Rails Page

175.82

1.26s

alto timeout


RedisRoda

11996.85

79.29ms

13.34


TrivialRack

6572.38

106.25ms

10.57

8000/4000

RedisNginx

32444.77

87.09ms

20.13

4000/4000

RedisNginx

32241.75

107.81ms

16.87

10000/2000

RedisNginx

33203.54

69.05ms

26.83


TrivialNginx

54505.66

117.90ms

33.29

30000/2800

fichero2Nginx

54863.56

186.82ms

21.67

20000/2800

fichero2Nginx

58768.45

157.51ms

23.32

10000/1800

fichero2Nginx

48682.03

144.22ms

31.24

4000/1800

fichero2Nginx

50704.06

63.21ms

34.33

4000/180

fichero2Nginx

46088.55

109.98ms

257.91

40000/1800

fichero2Nginx

96831.63

207.53ms

32.22

6000/1800

fichero2Nginx

47161.88

97.16ms

33.85

6000/1200

fichero2Nginx

50326.41

82.44ms

57.86

6000/450

fichero2Nginx

49396.65

96.93ms

419.36

4000/120

fichero2Nginx

49923.31

82.13ms

118.32

400/12

fichero2Nginx

7990.46

39.44ms

670.37

1000 hey

fichero2Nginx

11821.1741

0.0427 secs


500 hey

fichero2Nginx

11709.3041

0.0389


50 hey

fichero2Nginx

1142.0410

0.0434


6000/1800

RedisNginx

29991.12

90.76ms

27.52

2000/800

RedisNginx

22359.20

42.49ms

34.84

600/200

RedisNginx

13775.93

39.95ms

68.99

600/200

TrivialNginx

23622.84

51.45ms

36.03

2000/1200

TrivialNginx

25016.90

39.68ms

25.17









































Entradas recientes

Ver todo
Mass Gap from Kaluza Klein

This is just a series of proposed blog posts from chatGPT, each in separate markdown format See also https://chatgpt.com/c/6953f699-3088-832d-8e4f-9104a9264251

 
 
 
vLLM con ray a mano

#necesarioexport SSL_CERT_FILE=/fs/agustina/arivero/supercomplex/.local/lib/python3.11/site-packages/certifi/cacert.pem export RAY_NODE_MANAGER_HEARTBEAT_TIMEOUT_MILLISECONDS=20000 # 20 seconds expor

 
 
 

Comentarios


Never Miss a Post. Subscribe Now!

I'm a paragraph. Click here to add your own text and edit me. It's easy.

Thanks for submitting!

© 2035 by Kathy Schulders. Powered and secured by Wix

  • Grey Twitter Icon
bottom of page