haskell benchmarking http-conduit scotty

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.