• [^] # Re: En Python (Flask)

    Posté par (Mastodon) . En réponse au journal Le taptempo du web. Évalué à 6.

    Alors pour faire suite à mon commentaire originel que vous trouverez avec le code python en Bottle ici :
    https://linuxfr.org/users/spacefox/journaux/java-presque-9-000-requetes-par-seconde-avec-8-mo-de-ram#comment-1893297

    Rappel des épisodes précédents : le benchmark avec ab donne 3400 req/s en fournissant les images dans la réponse, et seulement 2800 req/s avec la redirection 302, avec 21Mo de RAM utilisée.

    En utilisant le serveur gunicorn (simplement en mettant server="gunicorn", workers=5 dans l'appel à bottle.run()) on monte à 14000 req/s, la machine ayant 4 cœurs, augmenter le nombre de workers au delà de 5 ne fait absolument rien gagner. Mais là on est à 30Mo de RAM pour le master, et 21Mo de RAM pour chacun des 5 workers. Rapide mais on le paye !

    J'ai tenté de mettre l'application bottle derrière apache et le mod_wsgi (processes=5 threads=2), on est à 12000 req/s, les processus mod_wsgi font 23Mo de RAM chacun, on est quasiment à la vitesse et aux ressources de gunicorn mais avec le reste des possibilités d'apache, si on en a besoin.

    Et puis j'ai poussé un petit peu, tester les différents serveurs disponibles comme backend de bottle, il y en a plein.
    Et je suis tombé sur bjoern, qui utilise la libev, bibliothèque C de gestion d'évènements, très - très - rapide.
    On monte à 17000 req/s, en monothread et 20Mo de RAM.
    C'est assez bluffant par rapport à toutes les autres solutions, surtout par rapport aux 3400 du serveur par défaut !

    Mais bon, en fait, pourquoi pas lancer l'application bjoern 5 fois et utiliser un load-balancer devant ?
    lighttpd fait ça très bien et on est à 13000 req/s, honorable, mais sans intérêt.
    Alors on va utiliser un vrai load-balancer : haproxy !
    Et donc 5 backend appli standalone avec bjoern, un haproxy en round-robin devant, on passe à 19500 req/s !
    On a le même score avec 4 backend, la machine a 4 cœurs, fait tourner haproxy et aussi l'outil de benchmark, on sature sans surcharger le nombre de backend.
    Si on descends à 2 backend, on monte à 19700 req/s, à 3 backend on est à 19800 req/s.
    On s'y attends un peu : 5 processus qui bossent (3 backends bottle/bjoern, haproxy, et ab pour les tests) sur 4 cœurs c'est en général ce qui optimise le plus, mais c'est assez marginal.
    Bien sûr, là on est à 3*20Mo+8Mo = 68Mo.

    En bref haproxy ça déchire, on le savait déjà, on le constate encore, 8Mo de RSS, c'est tout mini.
    Et le serveur WSGI bjoern est très impressionnant dans l'écosystème WSGI.
    Et on descends pas en dessous de 20Mo de RAM pour un processus Python.
    Et le Bottle.redirect() est plus lent, avec 3*bjoern+haproxy on est à 15000 req/s, c'est naze.

    Conclusion, fournir le fichier directement et ne pas faire de redirection 302 (de toute façon, derrière, côté navigateur, on fait deux requêtes, c'est atroce), utiliser bjoern en server WSGI, et si on vaut load-balancer entre plusieurs serveurs, dégainer haproxy.

    • Yth.

    Pour info le /etc/haproxy/haproxy.cfg utilisé est le suivant (les timeout servent pas dans notre cas, mais haproxy geint si on les met pas) :

    defaults http
     mode http
     timeout client 1m
     timeout server 1m
     timeout connect 10s
     timeout http-keep-alive 2m
     timeout queue 15s
    frontend img
     bind :8001 name clear
     default_backend bimg
    backend bimg
     balance roundrobin
     server Server00 127.0.0.1:8080 check
     server Server01 127.0.0.1:8081 check
     server Server02 127.0.0.1:8082 check
     #server Server03 127.0.0.1:8083 check

    Et le code final bottle, avec exactement deux dépendances python : bottle et bjoern

    #!/usr/bin/python3
    from bottle import route, run, response
    import glob
    import random
    import sys
    def readfile(path):
     try:
     with open(path, 'rb') as f:
     a = f.read()
     return (a, len(a), f"image/{path[-3:]}")
     except Exception:
     raise
     return None
    files = [
     x
     for ext in ['jpg', 'png']
     for path in glob.glob(f'*.{ext}')
     for x in [readfile(path)]
     if x
    ]
    @route('/')
    def img():
     img, size, ctyp = random.choice(files)
     response.set_header('Content-Type', ctyp)
     response.add_header('Content-Length', size)
     return img
    try:
     port = int(sys.argv[1])
    except Exception:
     port = 8080
    run(host='localhost', port=port, server='bjoern')