Journal TapTempo du Web en SQL avec SQLPage

Posté par  (site web personnel) . Licence CC By‑SA.
8
25
juil.
2023

L'été dernier, il y a eu une mode sur LinuxFR: le TapTempo du Web. C'est une sorte de concours (pas très malin), où le but est de montrer que son langage de programmation est meilleur que les autres, parce qu'il peut traiter plus de requêtes HTTP par seconde que les autres.

Les règles du jeu

  • Choisir un langage de programmation et un framework pour construire un serveur web avec.
  • Écrire un tout petit serveur web qui ne fait qu'une chose: une redirection vers une autre page
  • Mesurer les performances du serveur, sur son ordinateur, avec ab
  • Comparer son langage et son framework avec les autres de manière pas du tout scientifique, puisque chacun implémente un programme un peu différent et mesure les performances sur son propre ordinateur.

Ma participation

Je propose de relancer le concours cet été, et je commence avec une soumission pour le constructeur de site web low-code SQLPage (présenté précédemment sur linuxfr dans cette dépêche sous forme de tutoriel)

Code source en SQL
select 'redirect' as component, 'http://example.com' as link;
Benchmark

Avec mon CPU 11th Gen Intel Core i7-11390H @ 3.40GHz

❯ ab -n 100000 -c 10 -e /tmp/profile.csv 'http://localhost:8080/x.sql'
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>

[...]

Time taken for tests:   4.693 seconds
Complete requests:      100000
Failed requests:        0

[...]

Requests per second:    21308.09 [#/sec] (mean)
Time per request:       0.469 [ms] (mean)
Time per request:       0.047 [ms] (mean, across all concurrent requests)
Transfer rate:          5014.89 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       2
Processing:     0    0   0.8      0      25
Waiting:        0    0   0.7      0      25
Total:          0    0   0.8      0      25

Percentage of the requests served within a certain time (ms)
  50%      0
  66%      0
  75%      0
  80%      0
  90%      1
  95%      1
  98%      1
  99%      2
 100%     25 (longest request)
Interprétation

On sert donc, en SQL, 21 000 requêtes par seconde, et 90% des requêtes sont servies en moins d'une milliseconde 🤯 Pas mal !

À titre de comparaison, sur le même ordinateur, la version en python de l'année dernière sert 900 requêtes par seconde (23 fois moins), avec plus de 12ms de latence sur les requêtes les plus lentes (12x plus lent).

latency

  • # Python, bottle, bjoern

    Posté par  (Mastodon) . Évalué à 6.

    La machine : 4 coeurs, 8 threads, Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz

    Ça utilise une app WSGI en bottle, avec bjoern comme backend.
    Le code de taptempo.py, la ligne en commentaire est la façon simple de faire en bottle, mais les perfs sont bien moins bonnes, elles seront entre parenthèses dans le rapport en bas :

    #!/usr/bin/python3
    import bottle
    
    
    @bottle.route('/')
    def index():
        bottle.response.status = 303
        bottle.response.set_header('Location', 'http://example.com')
        # bottle.redirect('http://example.com')
    
    
    bottle.run(host='0.0.0.0', port=8080, server='bjoern')

    La mise en place (nécessite libev) :

    mkdir taptempo && cd taptempo
    python3 -m venv venv --prompt taptempo
    source ./venv/bin/activate
    pip install bottle bjoern
    python3 taptempo.py

    Les résultats :

    ab -n 100000 -c 10 -e /tmp/profile.csv 'http://localhost:8080'
    
    This is ApacheBench, Version 2.3 <$Revision: 1903618 $>
    
    Concurrency Level:      10
    Time taken for tests:   7.241 seconds (12.233 seconds)
    Complete requests:      100000
    Failed requests:        0
    
    Requests per second:    13809.69 (8174.30) [#/sec] (mean)
    Time per request:       0.724 (1.223) [ms] (mean)
    Time per request:       0.072 (0.122) [ms] (mean, across all concurrent requests)
    Transfer rate:          1807.13 (1037.75) [Kbytes/sec] received
    
    Connection Times (ms)
                  min     mean[+/-sd]      median   max
    Connect:        0(0)    0   0.0(0.0)      0       1
    Processing:     0(1)    1   0.2(0.3)      1       3(7)
    Waiting:        0(1)    1   0.2(0.3)      1       3(7)
    Total:          0(1)    1   0.2(0.3)      1       3(7)
    
    Percentage of the requests served within a certain time (ms)
      50%      1(1)
      66%      1(1)
      75%      1(1)
      80%      1(1)
      90%      1(2)
      95%      1(2)
      98%      1(2)
      99%      1(2)
     100%      3(7) (longest request)

    Je n'ai pas la moindre idée de ce que ça donnerait avec ton CPU, tu dois pouvoir tester et comparer.

    • Yth.
    • [^] # Re: Python, bottle, bjoern

      Posté par  (site web personnel) . Évalué à 4. Dernière modification le 26 juillet 2023 à 11:57.

      Wouw, c'est tout de suite un autre ordre de grandeur !

      Mais il y a quand même une grosse différence: ce serveur ne log rien du tout ! La version en SQL et celle en flask précédente affichaient une ligne de log dans la console pour chaque requête reçue.

      Voilà donc une nouvelle comparaison, sur mon ordi, sans aucun logging dans aucune des applications:

      latency

      Sans logging, on arrive à ~40 000 requêtes par secondes avec SQLPage.

      • [^] # Re: Python, bottle, bjoern

        Posté par  (Mastodon) . Évalué à 4.

        Alors les logs, j'avoue, j'ai pas trop d'idée de comment les rajouter avec bjoern, et je ne sais pas quel délais en plus ça prendrait…
        J'ai codé un truc à l'arrache non optimisé, et je tombe à 9k req/sec.

        Donc j'ai testé le bouzin en conditions un peu plus réelles sur un Odroid XU4, ARM, soft float (ça fait des années que je dois le réinstaller en HF), 8 cœurs, limités à 500Mhz pour cause de surchauffe (le ventilo est pété, la pâte thermique mérite d'être remplacée), python 3.7, à travers le réseau (gigabit, en câble pas en wifi), test ab depuis ma machine perso.
        700 req/s.
        Bon, OK, je libère le CPU 2 minutes en espérant qu'il ne crashe pas : 4x1.4Ghz et 4x2Ghz.
        1800 req/s.
        À noter que si je passe à 20 connexions en parallèle, on monte à 3300 req/s, parce qu'on a la latence réseau à prendre en compte, et 8 coeurs en face, donc pour les surcharger, 10 connexions parallèles ne suffisent pas.
        On monte même à 3500 avec 100 requêtes parallèles, alors que en local ça ne changeait rien de monter au delà de 10.

        Bon, voilà, à moins de trouver autre chose que je ne connais pas, je ne ferais pas mieux en Python sur ma machine.
        J'avais testé plusieurs serveurs python derrière un apache ou un haproxy en mode load-balancer, d'autres backends que bjoern (waitress est pas mal par exemple), sur un autre projet qui consistait à servir une image aléatoire dans une liste à chaque requête. L'idée était de les précharger en python au début, avec le type, la taille et le contenu, et de servir les headers direct, et le contenu, sans jamais relire sur le disque.
        On a exactement les mêmes perfs que la simple redirection, 13-14k req/s, avec des icônes, donc de petites images.
        J'ai jamais réussi à faire mieux en python qu'une app bottle (mais du flask irait probablement aussi bien pour ça, voire du WSGI brut) propulsée par bjoern.

        Clairement le rust optimisé fait mieux, le contraire m'aurait fortement déçu !

        • Yth.
        • [^] # Re: Python, bottle, bjoern

          Posté par  (site web personnel) . Évalué à 3.

          Ce qui est intéressant, c'est justement que ce n'est pas du rust, mais du SQL !

          La logique du serveur elle-même (ici, juste la redirection), est en SQL.

          La logique bas-niveau est en rust, tout comme la logique bas-niveau de Python et de Bjoern est en C, mais ce qui est vraiment cool, c'est que l'on puisse avoir de super performances avec un langage de très haut niveau comme SQL.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.