Неожиданно низкая пропускная способность для сетевого ввода-вывода с использованием Скотти

Я попытался сравнить Scotty с целью проверки эффективности сетевого ввода-вывода и общей пропускной способности.

Для этого я настроил два локальных сервера, написанных на Haskell. Тот, который ничего не делает и просто действует как API.

Код для того же

{-# 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"

Я написал другой сервер, который вызывает этот сервер и возвращает ответ.

{-# 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

С обоими этими серверами я выполнил тестирование wrk и получил очень высокую пропускную способность.

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

Хотя этот показатель был значительно выше, чем у других веб-серверов, таких как Phoenix, я понял, что это ничего не значит, поскольку в большинстве ответов было 500 ошибок, происходящих из-за исчерпания дескриптора файла.

Я проверяю пределы, которые были довольно низкими.

ulimit -n
256

Я увеличил эти пределы до

ulimit -n 10240

Я снова запустил wrk, и на этот раз пропускная способность была значительно снижена.

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

Хотя количество ошибок 500 уменьшилось, они не были устранены. Я тестировал Джин и Феникс, и они были намного лучше, чем Scotty не давая никаких 500 ответов.

Какой кусок головоломки мне не хватает? Я подозреваю, что есть проблема, которую я не могу отладить.

Я понимаю, что http-проводник во многом связан с этими ошибками и http-client библиотека использует его под капотом, и это не имеет ничего общего с Scotty,

1 ответ

Решение

Аналогия @ Юраса была верной. При повторном запуске сервера все проблемы, связанные с кодом состояния, отличным от 2xx, исчезли.

Первая строка в главном блоке была виновником. Я изменил строку с

main = do 
  let man = newManager defaultManagerSettings

в

main = do 
  man <- newManager defaultManagerSettings

и вуаля, не было никаких проблем. Также высокое использование памяти программой стабилизировалось до 21 МБ с 1 ГБ ранее.

Я не знаю причину, хотя Было бы неплохо иметь объяснение этому.

Другие вопросы по тегам