haskell - Rendimiento inesperadamente bajo para la E/S de red utilizando Scotty
benchmarking http-conduit (1)
Intenté comparar Scotty para probar la eficiencia de E / S de la red y el rendimiento general.
Para esto configuré dos servidores locales escritos en Haskell. Una que no hace nada y simplemente actúa como una API.
Código para el mismo es
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Network.Wai.Middleware.RequestLogger
import Control.Monad
import Data.Text
import Control.Monad.Trans
import Data.ByteString
import Network.HTTP.Types (status302)
import Data.Time.Clock
import Data.Text.Lazy.Encoding (decodeUtf8)
import Control.Concurrent
import Network.HTTP.Conduit
import Network.Connection (TLSSettings (..))
import Network.HTTP.Client
import Network
main = do
scotty 4001 $ do
middleware logStdoutDev
get "/dummy_api" $ do
text $ "dummy response"
Escribí otro servidor que llama a este servidor y devuelve la respuesta.
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Network.Wai.Middleware.RequestLogger
import Control.Monad
import Control.Monad.Trans
import qualified Data.Text.Internal.Lazy as LT
import Data.ByteString
import Network.HTTP.Types (status302)
import Data.Time.Clock
import Data.Text.Lazy.Encoding (decodeUtf8)
import Control.Concurrent
import qualified Data.ByteString.Lazy as LB
import Network.HTTP.Conduit
import Network.Connection (TLSSettings (..))
import Network.HTTP.Client
import Network
main = do
let man = newManager defaultManagerSettings
scotty 3000 $ do
middleware logStdoutDev
get "/filters" $ do
response <- liftIO $! (testGet man)
json $ decodeUtf8 (LB.fromChunks response)
testGet :: IO Manager -> IO [B.ByteString]
testGet manager = do
request <- parseUrl "http://localhost:4001/dummy_api"
man <- manager
let req = request { method = "GET", responseTimeout = Nothing, redirectCount = 0}
a <- withResponse req man $ brConsume . responseBody
return $! a
Con estos dos servidores en ejecución, realicé un benchmarking wrk y obtuve un rendimiento extremadamente alto.
wrk -t30 -c100 -d60s "http://localhost:3000/filters"
Running 1m test @ http://localhost:3000/filters
30 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 30.86ms 78.40ms 1.14s 95.63%
Req/Sec 174.05 62.29 1.18k 76.20%
287047 requests in 1.00m, 91.61MB read
Socket errors: connect 0, read 0, write 0, timeout 118
Non-2xx or 3xx responses: 284752
Requests/sec: 4776.57
Transfer/sec: 1.52MB
Si bien esto era significativamente más alto que otros servidores web como Phoenix, me di cuenta de que esto no significaba nada, ya que la mayoría de las respuestas eran 500 errores que se producían debido al agotamiento del descriptor de archivos.
Reviso los límites que eran bastante bajos.
ulimit -n
256
Aumenté estos límites a
ulimit -n 10240
Corrí wrk otra vez y esta vez claramente el rendimiento se había reducido drásticamente.
wrk -t30 -c100 -d60s "http://localhost:3000/filters"
Running 1m test @ http://localhost:3000/filters
30 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 105.69ms 161.72ms 1.24s 96.27%
Req/Sec 19.88 16.62 120.00 58.12%
8207 requests in 1.00m, 1.42MB read
Socket errors: connect 0, read 0, write 0, timeout 1961
Non-2xx or 3xx responses: 1521
Requests/sec: 136.60
Transfer/sec: 24.24KB
Aunque la cantidad de 500 errores se había reducido, no fueron eliminados. Comparé a Gin y Phoenix y fueron mucho mejores que Scotty
sin dar 500 respuestas.
¿Qué pieza del rompecabezas me falta? Sospecho que hay un problema que estoy fallando en la depuración.
Entiendo que http-conduit tiene mucho que ver con estos errores y la biblioteca http-client
usa bajo el capó y esto no tiene nada que ver con Scotty
.
La analogía de @ Yuras era correcta. Al ejecutar el servidor nuevamente, todos los problemas relacionados con el código de estado que no es 2xx desaparecieron.
La primera línea en el bloque principal fue la culpable. Cambié la línea de
main = do
let man = newManager defaultManagerSettings
a
main = do
man <- newManager defaultManagerSettings
y listo, no hubo ningún problema. También el alto uso de memoria del programa se estabilizó a 21MB desde 1GB anterior.
Aunque no sé la razón. Sería bueno tener una explicación para esto.