Jo, Virginia, do *Gëtt* eng Kleeschen Ënnerscheed tëscht Web Frameworks am Joer 2023

Eng defiant Programméierer Rees fir séier leeschtungsfäeg Webservercode ze fannen ier hien dem Maartdrock an technescher Schold erënnert
2023-03-24 11:52:06
👁️ 773
💬 0

Inhalter

  1. Aféierung
  2. Den Test
  3. PHP/Laravel
  4. Pure PHP
  5. Revisit Laravel
  6. Django
  7. Fläsch
  8. Starlette
  9. Node.js/ExpressJS
  10. Rust / Actix
  11. Technesch Schold
  12. Ressourcen

Aféierung

No engem vu menge rezenten Jobinterviews war ech iwwerrascht ze realiséieren datt d'Firma fir déi ech ugemellt hunn nach ëmmer Laravel benotzt, e PHP Kader deen ech virun ongeféier engem Joerzéngt probéiert hunn. Et war anstänneg fir d'Zäit, awer wann et eng Konstant an der Technologie a Moud ass, ass et kontinuéierlech Ännerung an d'Resurfacing vu Stiler a Konzepter. Wann Dir e JavaScript Programméierer sidd, sidd Dir wahrscheinlech mat dësem ale Witz vertraut

Programmer 1: "Ech hunn dësen neie JavaScript Kader net gär!"

Programmer 2: "Keng Suergen. Waart just sechs Méint, an et gëtt nach en ersat!"

Aus Virwëtz hunn ech decidéiert genee ze kucken wat geschitt wa mir al an nei op d'Test stellen. Natierlech ass de Web mat Benchmarks a Fuerderungen gefüllt, vun deenen déi populärste wahrscheinlech ass TechEmpower Web Framework Benchmarks hei . Mir wäerten awer näischt bal sou komplizéiert maachen wéi haut. Mir halen d'Saache flott an einfach souwuel sou datt dësen Artikel net verwandelt gëtt Krich a Fridden , an datt Dir eng liicht Chance hutt fir waakreg ze bleiwen an der Zäit wou Dir fäerdeg sidd ze liesen. Déi üblech Virwarnungen gëllen: dëst funktionnéiert vläicht net d'selwecht op Ärer Maschinn, verschidde Softwareversioune kënnen d'Performance beaflossen, an d'Schrödinger's Kaz gouf tatsächlech eng Zombiekaz, déi gläichzäiteg hallef lieweg an hallef dout war.

Den Test

Testen Ëmfeld

Fir dësen Test wäert ech mäi Laptop benotzen, bewaffnet mat engem schwaache i5 deen Manjaro Linux leeft wéi hei gewisen.

╰─➤  uname -a
Linux jimsredmi 5.10.174-1-MANJARO #1 SMP PREEMPT Tuesday Mar 21 11:15:28 UTC 2023 x86_64 GNU/Linux

╰─➤  cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family  : 6
model   : 126
model name  : Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz
stepping  : 5
microcode : 0xb6
cpu MHz   : 990.210
cache size  : 6144 KB

D'Aufgab op Hand

Eise Code wäert dräi einfach Aufgaben fir all Ufro hunn:

  1. Liest den aktuelle Benotzersession ID vun engem Cookie
  2. Lued zousätzlech Informatioun aus enger Datebank
  3. Gitt dës Informatioun un de Benotzer zréck

Wat fir eng idiotesch Test ass dat, kënnt Dir froen? Ee, wann Dir op d'Netz Demanden fir dës Säit kucken, Dir wäert mierken een genannt sessionvars.js déi genee déi selwecht Saach mécht.

Den Inhalt vun sessionvars.js

Dir gesitt, modern Websäite si komplizéiert Kreaturen, an eng vun den heefegsten Aufgaben ass komplex Säiten ze cachen fir iwwerschësseg Belaaschtung op den Datebankserver ze vermeiden.

Wa mir eng komplex Säit all Kéier wann e Benotzer et freet nei maachen, da kënne mir nëmmen ongeféier 600 Benotzer pro Sekonn déngen.

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1/system/index.en.html      
Running 10s test @ http://127.0.0.1/system/index.en.html
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   186.83ms  174.22ms   1.06s    81.16%
    Req/Sec   166.11     58.84   414.00     71.89%
  6213 requests in 10.02s, 49.35MB read
Requests/sec:    619.97
Transfer/sec:      4.92MB

Awer wa mir dës Säit als statesch HTML-Datei cache loossen an Nginx se séier aus der Fënster op de Benotzer werfen, da kënne mir 32.000 Benotzer pro Sekonn déngen, d'Performance vun engem Faktor vun 50x erhéijen.

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1/system/index.en.html
Running 10s test @ http://127.0.0.1/system/index.en.html
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     3.03ms  511.95us   6.87ms   68.10%
    Req/Sec     8.20k     1.15k   28.55k    97.26%
  327353 requests in 10.10s, 2.36GB read
Requests/sec:  32410.83
Transfer/sec:    238.99MB

De statesche index.en.html ass den Deel deen un jiddereen geet, an nëmmen déi Deeler déi vum Benotzer ënnerscheeden ginn an sessionvars.js geschéckt. Dëst reduzéiert net nëmmen d'Datebankbelaaschtung a schaaft eng besser Erfarung fir eis Benotzer, awer reduzéiert och d'Quante Wahrscheinlechkeeten datt eise Server spontan an engem Warp Core Verstouss verdampft wann d'Klingons attackéieren.

Code Ufuerderunge

De zréckginn Code fir all Kader wäert eng einfach Fuerderung hunn: weist de Benotzer wéi oft se d'Säit erfrëscht hunn andeems se "Count is x" soen. Fir d'Saachen einfach ze halen, bleiwe mir fir de Moment ewech vu Redis Schlaangen, Kubernetes Komponenten oder AWS Lambdas.

Weist wéi oft Dir d'Säit besicht hutt

All Benotzer Sessiounsdaten ginn an enger PostgreSQL Datebank gespäichert.

D'Benotzersessions Dësch

An dës Datebank Tabelle gëtt virun all Test ofgeschnidden.

Den Dësch nodeems se ofgeschnidden

Einfach awer effektiv ass de Pafera Motto ... ausserhalb vun der däischterster Timeline souwisou ...

Tatsächlech Test Resultater

PHP/Laravel

Okay, also elo kënne mir endlech ufänken eis Hänn dreckeg ze maachen. Mir sprangen de Setup fir Laravel well et just eng Rëtsch Komponist an Handwierker ass commandéiert.

Als éischt setze mir eis Datebank Astellungen an der .env Datei op

DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest

Da setzen mir eng eenzeg Fallback Route déi all Ufro un eise Controller schéckt.

Route::fallback(SessionController::class);

A set de Controller fir d'Zuel ze weisen. Laravel, par défaut, späichert Sessiounen an der Datebank. Et bitt och de session() Funktioun fir mat eise Sessiounsdaten ze interface, also alles wat et gedauert huet war e puer Zeilen Code fir eis Säit ze maachen.

class SessionController extends Controller
{
  public function __invoke(Request $request)
  {
    $count  = session('count', 0);

    $count  += 1;

    session(['count' => $count]);

    return 'Count is ' . $count;
  }
}

Nodeems Dir php-fpm an Nginx opgeriicht hutt, gesäit eis Säit zimlech gutt aus ...

╰─➤  php -v
PHP 8.2.2 (cli) (built: Feb  1 2023 08:33:04) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.2, Copyright (c) Zend Technologies
    with Xdebug v3.2.0, Copyright (c) 2002-2022, by Derick Rethans

╰─➤  sudo systemctl restart php-fpm
╰─➤  sudo systemctl restart nginx

Op d'mannst bis mir d'Testresultater tatsächlech gesinn ...

PHP/Laravel

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1
Running 10s test @ http://127.0.0.1
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.08s   546.33ms   1.96s    65.71%
    Req/Sec    12.37      7.28    40.00     56.64%
  211 requests in 10.03s, 177.21KB read
  Socket errors: connect 0, read 0, write 0, timeout 176
Requests/sec:     21.04
Transfer/sec:     17.67KB

Nee, dat ass keen Tippfeeler. Eis Testmaschinn ass vu 600 Ufroe pro Sekonn gaang fir eng komplex Säit ze maachen ... op 21 Ufroen pro Sekonn Rendering "Count is 1".

Also wat ass falsch gaang? Ass eppes falsch mat eiser PHP Installatioun? Ass Nginx iergendwéi méi lues wann Dir mat php-fpm interfacéiert?

Pure PHP

Loosst eis dës Säit am pure PHP Code nei maachen.

<?php

// ====================================================================
function uuid4() 
{
  return sprintf(
    '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
    mt_rand(0, 0xffff), mt_rand(0, 0xffff),
    mt_rand(0, 0xffff),
    mt_rand(0, 0x0fff) | 0x4000,
    mt_rand(0, 0x3fff) | 0x8000,
    mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
  );
}

// ====================================================================
function Query($db, $query, $params = [])
{
  $s  = $db->prepare($query);
  
  $s->setFetchMode(PDO::FETCH_ASSOC);
  $s->execute(array_values($params));
  
  return $s;
}

// ********************************************************************
session_start();

$sessionid  = 0;

if (isset($_SESSION['sessionid']))
{
  $sessionid  = $_SESSION['sessionid'];
}

if (!$sessionid)
{
  $sessionid              = uuid4();
  $_SESSION['sessionid']  = $sessionid;
}

$db   = new PDO('pgsql:host=127.0.0.1 dbname=sessiontest user=sessiontest password=sessiontest');
$data = 0;

try
{
  $result = Query(
    $db,
    'SELECT data FROM usersessions WHERE uid = ?',
    [$sessionid]
  )->fetchAll();
  
  if ($result)
  {
    $data = json_decode($result[0]['data'], 1);
  } 
} catch (Exception $e)
{
  echo $e;

  Query(
    $db,
    'CREATE TABLE usersessions(
      uid     TEXT PRIMARY KEY,
      data    TEXT
    )'
  );
}

if (!$data)
{
  $data = ['count'  => 0];
}

$data['count']++;

if ($data['count'] == 1)
{
  Query(
    $db,
    'INSERT INTO usersessions(uid, data)
    VALUES(?, ?)',
    [$sessionid, json_encode($data)]
  );
} else
{
  Query(
    $db,
    'UPDATE usersessions
      SET data = ?
      WHERE uid = ?',
    [json_encode($data), $sessionid]
  );
}

echo 'Count is ' . $data['count'];

Mir hunn elo 98 Zeilen Code benotzt fir ze maachen wat véier Zeilen Code (an eng ganz Rëtsch Konfiguratiounsaarbechten) zu Laravel gemaach hunn. (Natierlech, wa mir richteg Feeler Ëmgank an Benotzer konfrontéiert Messagen gemaach, dëst wier ongeféier duebel d'Zuel vun Linnen.) Vläicht kënne mir et maachen 30 Demanden pro Sekonn?

PHP/Pure PHP

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1                  
Running 10s test @ http://127.0.0.1
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   140.79ms   27.88ms 332.31ms   90.75%
    Req/Sec   178.63     58.34   252.00     61.01%
  7074 requests in 10.04s, 3.62MB read
Requests/sec:    704.46
Transfer/sec:    369.43KB

Whoa! Et gesäit aus wéi wann et näischt falsch mat eiser PHP Installatioun ass. Déi reng PHP Versioun mécht 700 Ufroen pro Sekonn.

Wann et näischt falsch mat PHP ass, hu mir vläicht Laravel falsch konfiguréiert?

Revisit Laravel

Nodeem de Web fir Konfiguratiounsprobleemer a Performance Tipps duerchgefouert gouf, waren zwee vun de populäersten Technike fir d'Configuratioun an d'Routedaten ze cache fir se fir all Ufro ze vermeiden. Dofir wäerte mir hir Rotschléi huelen an dës Tipps probéieren.

╰─➤  php artisan config:cache

   INFO  Configuration cached successfully.  

╰─➤  php artisan route:cache

   INFO  Routes cached successfully.  

Alles gesäit gutt op der Kommandozeil. Loosst eis de Benchmark nei maachen.

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1
Running 10s test @ http://127.0.0.1
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.13s   543.50ms   1.98s    61.90%
    Req/Sec    25.45     13.39    50.00     55.77%
  289 requests in 10.04s, 242.15KB read
  Socket errors: connect 0, read 0, write 0, timeout 247
Requests/sec:     28.80
Transfer/sec:     24.13KB

Gutt, mir hunn elo d'Performance vun 21,04 op 28,80 Ufro pro Sekonn erhéicht, en dramateschen Uplift vu bal 37%! Dëst wier zimmlech beandrockend fir all Software Package ... ausser datt mir nach ëmmer nëmmen 1/24 vun der Unzuel vun Ufroe vun der purer PHP Versioun maachen.

Wann Dir denkt datt eppes mat dësem Test falsch muss sinn, sollt Dir mam Auteur vum Lucinda PHP Kader schwätzen. A sengen Testresultater huet hien Lucinda schléit Laravel duerch 36x fir HTML Ufroen an 90x fir JSON Ufroen.

Nodeems ech op menger eegener Maschinn mat Apache an Nginx getest hunn, hunn ech kee Grond him ze zweifelen. Laravel ass wierklech just dat lues! PHP vu sech selwer ass net sou schlecht, awer wann Dir all déi extra Veraarbechtung bäidréit, déi Laravel zu all Ufro bäidréit, dann fannen ech et ganz schwéier Laravel als Choix am Joer 2023 ze recommandéieren.

Django

PHP / Wordpress Konte fir iwwer 40% vun all Websäite op de Web , mécht et bei wäitem de dominante Kader. Perséinlech fannen ech awer, datt d'Popularitéit net onbedéngt an d'Qualitéit iwwersetzt, wéi ech en plötzlechen onkontrolléierbaren Drang fir dat aussergewéinlecht Gourmet-Iessen vun de beléifste Restaurant an der Welt ... McDonald & # x27; Well mir scho pure PHP Code getest hunn, wäerte mir Wordpress net selwer testen, well alles wat Wordpress involvéiert wier ouni Zweifel méi niddereg wéi déi 700 Ufroen pro Sekonn, déi mir mat pure PHP observéiert hunn.

Django ass en anere populäre Kader dee scho laang existéiert. Wann Dir et an der Vergaangenheet benotzt hutt, erënnert Dir Iech wahrscheinlech gär un seng spektakulär Datebankverwaltungsinterface zesumme mat wéi lästeg et war alles ze konfiguréieren just wéi Dir wollt. Loosst eis kucken wéi gutt Django am Joer 2023 funktionnéiert, besonnesch mat der neier ASGI Interface, déi et bäigefüügt huet wéi d'Versioun 4.0.

Django opbauen ass bemierkenswäert ähnlech wéi Laravel opzestellen, well se allebéid aus dem Alter waren wou MVC Architekturen stilvoll a korrekt waren. Mir sprangen déi langweileg Konfiguratioun iwwer a ginn direkt fir d'Vue opzestellen.

from django.shortcuts import render
from django.http import HttpResponse

# =====================================================================
def index(request):
  count = request.session.get('count', 0)
  count += 1
  request.session['count']  = count 
  return HttpResponse(f"Count is {count}")

Véier Zeilen vum Code ass d'selwecht wéi mat der Laravel Versioun. Loosst eis kucken wéi et funktionnéiert.

╰─➤  python --version
Python 3.10.9

Python/Django
╰─➤  gunicorn --access-logfile - -k uvicorn.workers.UvicornWorker -w 4 djangotest.asgi
[2023-03-21 15:20:38 +0800] [2886633] [INFO] Starting gunicorn 20.1.0

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000/sessiontest/
Running 10s test @ http://127.0.0.1:8000/sessiontest/
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   277.71ms  142.84ms 835.12ms   69.93%
    Req/Sec    91.21     57.57   230.00     61.04%
  3577 requests in 10.06s, 1.46MB read
Requests/sec:    355.44
Transfer/sec:    148.56KB

guer net schlecht bei 355 Ufroen pro Sekonn. Et ass nëmmen d'Halschent vun der Leeschtung vun der purer PHP Versioun, awer et ass och 12x déi vun der Laravel Versioun. Django vs Laravel schéngt guer kee Concours ze sinn.

Fläsch

Ausser de gréisseren alles-inklusive-de-Kichen-Sink-Frameworks, ginn et och méi kleng Kaderen déi just e puer Basis-Setup maachen, während Dir de Rescht léisst. Ee vun de beschten ze benotzen ass Flask a säin ASGI Kolleg Quart. Meng eegen PaferaPy Kader ass op der Flask gebaut, sou datt ech gutt vertraut sinn wéi einfach et ass fir Saachen ze maachen wärend d'Leeschtung behalen.

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Session benchmark test

import json
import psycopg
import uuid

from flask import Flask, session, redirect, url_for, request, current_app, g, abort, send_from_directory
from flask.sessions import SecureCookieSessionInterface

app = Flask('pafera')

app.secret_key  = b'secretkey'

dbconn  = 0

# =====================================================================
@app.route('/', defaults={'path': ''}, methods = ['GET', 'POST'])
@app.route('/<path:path>', methods = ['GET', 'POST'])
def index(path):
  """Handles all requests for the server. 
  
  We route all requests through here to handle the database and session
  logic in one place.
  """
  global dbconn
  
  if not dbconn:
    dbconn  = psycopg.connect('dbname=sessiontest user=sessiontest password=sessiontest')
    
    cursor  = dbconn.execute('''
      CREATE TABLE IF NOT EXISTS usersessions(
        uid     TEXT PRIMARY KEY,
        data    TEXT
      )
    ''')
    cursor.close()
    dbconn.commit()
      
  sessionid = session.get('sessionid', 0)
  
  if not sessionid:
    sessionid = uuid.uuid4().hex
    session['sessionid']  = sessionid
  
  cursor  = dbconn.execute("SELECT data FROM usersessions WHERE uid = %s", [sessionid])
  row     = cursor.fetchone()
  
  count = json.loads(row[0])['count'] if row else 0
  
  count += 1
  
  newdata = json.dumps({'count': count})
  
  if count == 1:
    cursor.execute("""
        INSERT INTO usersessions(uid, data)
        VALUES(%s, %s)
      """,
      [sessionid, newdata]
    )
  else:
    cursor.execute("""
        UPDATE usersessions
        SET data = %s
        WHERE uid = %s
      """,
      [newdata, sessionid]
    )
  
  cursor.close()
  
  dbconn.commit()
  
  return f'Count is {count}'

Wéi Dir kënnt gesinn, ass de Flask Skript méi kuerz wéi de pure PHP Skript. Ech fannen datt aus all de Sproochen déi ech benotzt hunn, Python ass wahrscheinlech déi expressivst Sprooch wat d'Tastekombinatiounen ugeet. Mangel u Klameren a Klammern, Lëscht an Diktat Verständnis, a Blockéierung baséiert op Indentatioun anstatt Semikolon maachen de Python zimlech einfach awer mächteg a senge Fäegkeeten.

Leider ass Python och déi luesst allgemeng Zweck Sprooch dobaussen, trotz wéi vill Software dra geschriwwe gouf. D'Zuel vun de verfügbare Python-Bibliothéiken ass ongeféier véier Mol méi wéi ähnlech Sproochen an deckt eng grouss Quantitéit un Domainen, awer kee géif soen datt Python séier a performant ausserhalb vun Nischen wéi NumPy ass.

Loosst eis kucken wéi eis Flask Versioun mat eise fréiere Kaderen vergläicht.

Python/Flask

╰─➤  gunicorn --access-logfile - -w 4 flasksite:app
[2023-03-21 15:32:49 +0800] [2856296] [INFO] Starting gunicorn 20.1.0

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    91.84ms   11.97ms 149.63ms   86.18%
    Req/Sec   272.04     39.05   380.00     74.50%
  10842 requests in 10.04s, 3.27MB read
Requests/sec:   1080.28
Transfer/sec:    333.37KB

Eise Flask Skript ass tatsächlech méi séier wéi eis reng PHP Versioun!

Wann Dir iwwerrascht sidd vun dësem, sollt Dir mierken datt eis Flask App all seng Initialiséierung a Konfiguratioun mécht wa mir den Gunicorn Server starten, während PHP de Skript nei ausféiert all Kéier wann eng nei Ufro erakënnt. ;s gläichwäerteg mam Flask dee jonken, ängschtleche Taxichauffer deen den Auto scho gestart huet an nieft der Strooss waart, während PHP den ale Chauffer ass, dee bei sengem Haus bleift fir en Uruff ze waarden an eréischt dann fiert eriwwer fir dech opzehuelen. Eng al Schoul Guy ze sinn a kommen aus den Deeg wou PHP eng wonnerbar Ännerung fir einfach HTML an SHTML Dateien war, et ass e bëssen traureg ze realiséieren wéi vill Zäit vergaang ass, awer d'Designdifferenzen maachen et wierklech schwéier fir PHP Konkurréiere géint Python, Java, an Node.js Serveren déi just an der Erënnerung bleiwen an d'Ufro mat der flësseger Liichtegkeet vun engem Jongléier handelen.

Starlette

Flask kéint eise schnellsten Kader bis elo sinn, awer et ass tatsächlech zimlech al Software. D'Python Gemeinschaft huet e puer Joer zréck op déi méi nei asychron ASGI Server gewiesselt, an natierlech hunn ech selwer mat hinnen gewiesselt.

Déi lescht Versioun vum Pafera Framework, PaferaPyAsync , baséiert op Starlette. Och wann et eng ASGI Versioun vu Flask genannt Quart gëtt, waren d'Performance Differenzen tëscht Quart a Starlette genuch fir mech mäi Code op Starlette ze rebaséieren amplaz.

Asychron Programméiere kann fir vill Leit erschreckend sinn, awer et ass tatsächlech kee schwéiert Konzept dank den Node.js Kärelen déi d'Konzept virun engem Joerzéngt populariséiert hunn.

Mir hunn d'Konkurrenz mat Multithreading, Multiprocessing, verdeelt Computing, verspriechen Ketten, an all déi lëschteg Zäiten ze kämpfen, déi vill Veteran Programméierer virzäiteg alen an desicéiert hunn. Elo, mir tippen just async virun eise Funktiounen an await virun all Code, datt eng Zäit huelen kann auszeféieren. Et ass wierklech méi verbose wéi normale Code, awer vill manner lästeg ze benotzen wéi mat Synchroniséierungsprimitiven ze këmmeren, Message Passage, a Verspriechen ze léisen.

Eis Starlette Datei gesäit esou aus:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Session benchmark test

import json
import uuid

import psycopg

from starlette.applications import Starlette
from starlette.responses import Response, PlainTextResponse, JSONResponse, RedirectResponse, HTMLResponse
from starlette.routing import Route, Mount, WebSocketRoute
from starlette_session import SessionMiddleware

dbconn  = 0

# =====================================================================
async def index(R):
  global dbconn
  
  if not dbconn:
    dbconn  = await psycopg.AsyncConnection.connect('dbname=sessiontest user=sessiontest password=sessiontest')
    
    cursor  = await dbconn.execute('''
      CREATE TABLE IF NOT EXISTS usersessions(
        uid     TEXT PRIMARY KEY,
        data    TEXT
      )
    ''')
    await cursor.close()
    await dbconn.commit()
    
  sessionid = R.session.get('sessionid', 0)
  
  if not sessionid:
    sessionid = uuid.uuid4().hex
    R.session['sessionid']  = sessionid
  
  cursor  = await dbconn.execute("SELECT data FROM usersessions WHERE uid = %s", [sessionid])
  row     = await cursor.fetchone()
  
  count = json.loads(row[0])['count'] if row else 0
  
  count += 1
  
  newdata = json.dumps({'count': count})
  
  if count == 1:
    await cursor.execute("""
        INSERT INTO usersessions(uid, data)
        VALUES(%s, %s)
      """,
      [sessionid, newdata]
    )
  else:
    await cursor.execute("""
        UPDATE usersessions
        SET data = %s
        WHERE uid = %s
      """,
      [newdata, sessionid]
    )
  
  await cursor.close()
  await dbconn.commit()
  
  return PlainTextResponse(f'Count is {count}')

# *********************************************************************
app = Starlette(
  debug   = True, 
  routes  = [
    Route('/{path:path}', index, methods = ['GET', 'POST']),
  ],
)

app.add_middleware(
  SessionMiddleware, 
  secret_key  = 'testsecretkey', 
  cookie_name = "pafera",
)

Wéi Dir gesitt, ass et zimlech kopéiert a gepecht aus eisem Flask Skript mat nëmmen e puer Routing Ännerungen an der async/await Schlësselwieder.

Wéi vill Verbesserung kann kopéieren a gepaste Code eis wierklech ginn?

Python/Starlette

╰─➤  gunicorn --access-logfile - -k uvicorn.workers.UvicornWorker -w 4 starlettesite:app                                                                                                130 ↵
[2023-03-21 15:42:34 +0800] [2856220] [INFO] Starting gunicorn 20.1.0

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    21.85ms   10.45ms  67.29ms   55.18%
    Req/Sec     1.15k   170.11     1.52k    66.00%
  45809 requests in 10.04s, 13.85MB read
Requests/sec:   4562.82
Transfer/sec:      1.38MB

Mir hunn en neie Champion, Dammen an dir Hären! Eise fréiere Héich war eis reng PHP Versioun bei 704 Ufroen pro Sekonn, déi dunn vun eiser Flask Versioun mat 1080 Ufroen pro Sekonn iwwerholl gouf. Eis Starlette Skript zerstéiert all fréiere Kandidate mat 4562 Ufroen pro Sekonn, dat heescht eng 6x Verbesserung iwwer pure PHP a 4x Verbesserung iwwer Flask.

Wann Dir Äre WSGI Python Code nach net op ASGI geännert hutt, ass elo vläicht eng gutt Zäit fir unzefänken.

Node.js/ExpressJS

Bis elo hu mir nëmmen PHP a Python Kaderen ofgedeckt. Wéi och ëmmer, e groussen Deel vun der Welt benotzt Java, DotNet, Node.js, Ruby on Rails, an aner sou Technologien fir hir Websäiten. Dëst ass op kee Fall eng ëmfaassend Iwwersiicht iwwer all d'Ökosystemer a Biome vun der Welt, also fir d'Programméierungs-Äquivalent vun der organescher Chimie ze vermeiden, wäerte mir nëmmen d'Frame wielen déi am einfachsten sinn fir Code ze tippen. . vun deem Java definitiv net ass.

Ausser Dir hutt Iech ënner Ärer Kopie vu K&R C oder Knuth's verstoppt D'Konscht vum Computerprogramméiere fir déi lescht fofzéng Joer, Dir hutt wahrscheinlech vun Node.js héieren & # x27; Déi vun eis, déi zënter dem Ufank vu JavaScript ronderëm sinn, sinn entweder onheemlech erschreckt, iwwerrascht, oder souwuel um Zoustand vum modernen JavaScript, awer et gëtt kee verleegnen datt JavaScript eng Kraaft gouf fir och op Serveren ze berechnen. als Browser. Iwwerhaapt hu mir souguer gebierteg 64 Bit ganz Zuelen elo an der Sprooch! Dat ass vill besser wéi alles dat bei wäitem a 64-Bit Floats gespäichert gëtt!

ExpressJS ass wahrscheinlech deen einfachsten Node.js Server fir ze benotzen, also maache mir eng séier an dreckeg Node.js/ExpressJS App fir eise Konter ze déngen.

/**********************************************************************
 * Simple session test using ExpressJS.
 **********************************************************************/
var L           = console.log;

var uuid        = require('uuid4');
var express     = require('express');
var session     = require('express-session');
var MemoryStore = require('memorystore')(session);

var { Client }  = require('pg')
var db          = 0;
var app       = express();

const PORT    = 8000;

//session middleware
app.use(
  session({
    secret:             "secretkey",
    saveUninitialized:  true,
    resave:             false,
    store:              new MemoryStore({
      checkPeriod: 1000 * 60 * 60 * 24 // prune expired entries every 24h
    })
  })
);

app.get('/',
  async function(req,res)
  {
    if (!db)
    {
      db  = new Client({
        user:     'sessiontest',
        host:     '127.0.0.1',
        database: 'sessiontest',
        password: 'sessiontest'
      });
      
      await db.connect();
      
      await db.query(`
        CREATE TABLE IF NOT EXISTS usersessions(
          uid     TEXT PRIMARY KEY,
          data    TEXT
        )`,
        []
      );
    };
    
    var session = req.session;
    
    if (!session.sessionid)
    {
      session.sessionid = uuid();
    }
    
    var row = 0;
    
    let queryresult = await db.query(`
      SELECT data::TEXT
      FROM usersessions 
      WHERE uid = $1`,
      [session.sessionid]
    );
    
    if (queryresult && queryresult.rows.length)
    {
      row = queryresult.rows[0].data;
    } 
    
    var count = 0;
    
    if (row)
    {
      var data  = JSON.parse(row);
      
      data.count  += 1;
      
      count = data.count;
      
      await db.query(`
          UPDATE usersessions
          SET data = $1
          WHERE uid = $2
        `,
        [JSON.stringify(data), session.sessionid]
      );
    } else
    {
      await db.query(`
        INSERT INTO usersessions(uid, data)
          VALUES($1, $2)`,
        [session.sessionid, JSON.stringify({count: 1})]
      );
      
      count = 1;
    }
    
    res.send(`Count is ${count}`);
  }
);

app.listen(PORT, () => console.log(`Server Running at port ${PORT}`));

Dëse Code war tatsächlech méi einfach ze schreiwen wéi d'Python Versiounen, och wann native JavaScript zimlech onbestänneg gëtt wann d'Applikatioune méi grouss ginn, an all Versuche fir dëst ze korrigéieren wéi TypeScript séier méi verbose wéi Python.

Loosst eis kucken wéi dëst funktionnéiert!

Node.js/ExpressJS

╰─➤  node --version                                                                                                                                                                     v19.6.0

╰─➤  NODE_ENV=production node nodejsapp.js                                                                                                                                             130 ↵
Server Running at port 8000

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    90.41ms    7.20ms 188.29ms   85.16%
    Req/Sec   277.15     37.21   393.00     81.66%
  11018 requests in 10.02s, 3.82MB read
Requests/sec:   1100.12
Transfer/sec:    390.68KB

Dir hutt vläicht antike héieren (antal vun Internet Standarden souwisou ...) Folktales iwwer Node.js & # x27; Geschwindegkeet, an déi Geschichte si meeschtens richteg dank der spektakulärer Aarbecht déi Google mam V8 JavaScript-Motor gemaach huet. An dësem Fall awer, obwuel eis Schnell App de Flask Skript besser ass, ass seng eenzeg threaded Natur vun de véier Async Prozesser besiegt, déi vum Starlette Knight ausgeübt ginn, deen "Ni!" seet.

Loosst eis e bësse méi Hëllef kréien!

╰─➤  pm2 start nodejsapp.js -i 4 

[PM2] Spawning PM2 daemon with pm2_home=/home/jim/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/jim/projects/paferarust/nodejsapp.js in cluster_mode (4 instances)
[PM2] Done.
┌────┬──────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name         │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ nodejsapp    │ default     │ N/A     │ cluster │ 37141    │ 0s     │ 0    │ online    │ 0%       │ 64.6mb   │ jim      │ disabled │
│ 1  │ nodejsapp    │ default     │ N/A     │ cluster │ 37148    │ 0s     │ 0    │ online    │ 0%       │ 64.5mb   │ jim      │ disabled │
│ 2  │ nodejsapp    │ default     │ N/A     │ cluster │ 37159    │ 0s     │ 0    │ online    │ 0%       │ 56.0mb   │ jim      │ disabled │
│ 3  │ nodejsapp    │ default     │ N/A     │ cluster │ 37171    │ 0s     │ 0    │ online    │ 0%       │ 45.3mb   │ jim      │ disabled │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

Okay! Elo ass et e souguer véier op véier Schluecht! Loosst eis benchmarken!

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1:8000
Running 10s test @ http://127.0.0.1:8000
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    45.09ms   19.89ms 176.14ms   60.22%
    Req/Sec   558.93     97.50   770.00     66.17%
  22234 requests in 10.02s, 7.71MB read
Requests/sec:   2218.69
Transfer/sec:    787.89KB

Nach net ganz um Niveau vun Starlette, mee et ass net schlecht fir eng séier fënnef Minutt JavaScript Hack. Vu menger eegener Tester gëtt dëst Skript tatsächlech e bëssen op der Datebank-Interfacing-Niveau zréckgehal well Node-postgres néierens sou effizient ass wéi psycopg fir Python ass. Wiesselen op sqlite als Datebank Chauffer bréngt iwwer 3000 Ufroe pro Sekonn fir deeselwechten ExpressJS Code.

D'Haapt Saach ze notéieren ass datt trotz der lueser Ausféierungsgeschwindegkeet vum Python, ASGI Frameworks tatsächlech kompetitiv kënne mat Node.js Léisunge fir bestëmmte Workloads sinn.

Rust / Actix

Also elo komme mir méi no un d'Spëtzt vum Bierg, a mam Bierg mengen ech déi héchste Benchmarkscores, déi vu Mais a Männer opgeholl goufen.

Wann Dir déi meescht vun de Framework Benchmarks kuckt, déi um Internet verfügbar sinn, mierkt Dir datt et zwou Sproochen sinn déi d'Spëtzt dominéieren: C ++ a Rust. Ech hu mat C ++ zënter den 90er Joren geschafft, an ech hat souguer meng eegen Win32 C ++ Kader zréck ier MFC / ATL eng Saach war, also hunn ech vill Erfahrung mat der Sprooch. Et ass net vill Spaass mat eppes ze schaffen wann Dir et scho wësst, also wäerte mir eng Rust Versioun maachen amplaz. ;)

Rust ass relativ nei sou wäit wéi d'Programméierungssprooche goen, awer et gouf en Objet vu Virwëtzegkeet fir mech wann de Linus Torvalds ugekënnegt huet datt hien Rust als Linux Kernel Programmiersprache géif akzeptéieren. Fir eis eeler Programméierer, dat ass ongeféier d'selwecht wéi ze soen datt dës nei fangled New Age Hippie Saach eng nei Ännerung vun der US Verfassung wäert sinn.

Elo, wann Dir en erfuerene Programméierer sidd, tendéiert Dir net sou séier op de Wagon ze sprangen wéi déi méi jonk Leit, oder soss kënnt Dir duerch séier Ännerunge vun der Sprooch oder Bibliothéiken verbrannt ginn. (Jiddereen deen déi éischt Versioun vun AngularJS benotzt, weess wat ech schwätzen iwwer.) Rust ass nach e bëssen an där experimentell Entwécklung Etapp, an ech fannen et witzeg, datt vill Code Beispiller op de Web net emol & # 8220; kompiléiere méi mat aktuellen Versioune vu Packagen.

Wéi och ëmmer, d'Performance gewisen vu Rust Uwendungen kann net ofgeleent ginn. Wann Dir ni probéiert hutt ripgrep oder fd fannen eraus op grouss Quelltext Beem, Dir sollt hinnen definitiv e spin ginn. Si si souguer verfügbar fir déi meescht Linux Verdeelungen einfach vum Package Manager. Dir austauscht Verbositéit fir Leeschtung mat Rust ... a vill vun Verbositéit fir a vill vun Leeschtung.

De komplette Code fir Rust ass e bësse grouss, also wäerte mir just déi relevant Handler hei kucken:

// =====================================================================
pub async fn RunQuery(
  db:       &web::Data<Pool>,
  query:    &str,
  args:     &[&(dyn ToSql + Sync)]
) -> Result<Vec<tokio_postgres::row::Row>, tokio_postgres::Error>
{  
  let client      = db.get().await.unwrap();
  let statement   = client.prepare_cached(query).await.unwrap();
  
  client.query(&statement, args).await
}

// =====================================================================
pub async fn index(
  req:      HttpRequest,
  session:  Session,
  db:       web::Data<Pool>,
) -> Result<HttpResponse, Error> 
{
  let mut count = 1;
  
  if let Some(sessionid) = session.get::<String>("sessionid")? 
  {
    let rows  = RunQuery(
      &db, 
      "SELECT data 
        FROM usersessions 
        WHERE uid = $1", 
      &[&sessionid]
    ).await.unwrap();
    
    if rows.is_empty()
    {
      let jsondata  = serde_json::json!({
        "count": 1,
      }).to_string();
      
      RunQuery(
        &db, 
        "INSERT INTO usersessions(uid, data)
          VALUES($1, $2)", 
        &[&sessionid, &jsondata]
      ).await
      .expect("Insert failed!");
    } else
    {
      let jsonstring:&str  = rows[0].get(0);
      let countdata: CountData = serde_json::from_str(jsonstring)?;
      
      count = countdata.count;
      
      count += 1;
      
      let jsondata  = serde_json::json!({
        "count": count,
      }).to_string();
      
      RunQuery(
        &db, 
        "UPDATE usersessions
        SET data = $1
        WHERE uid = $2
        ",
        &[&jsondata, &sessionid]
      ).await
      .expect("Update failed!");
    }
  } else 
  {
    let sessionid = Uuid::new_v4().to_string();
    
    let jsondata  = serde_json::json!({
      "count": 1,
    }).to_string();
    
    RunQuery(
      &db, 
      "INSERT INTO usersessions(uid, data)
        VALUES($1, $2)", 
      &[&sessionid, &jsondata]
    ).await
    .expect("Insert failed!");
    
    session.insert("sessionid", sessionid)?;    
  }  
  
  Ok(HttpResponse::Ok().body(format!(
    "Count is {:?}",
    count
  )))
}

Dëst ass vill méi komplizéiert wéi d'Python/Node.js Versiounen ...

Rust/Actix

╰─➤  cargo run --release
[2023-03-21T23:37:25Z INFO  actix_server::builder] starting 4 workers
Server running at http://127.0.0.1:8888/

╰─➤  wrk -d 10s -t 4 -c 100 http://127.0.0.1:8888
Running 10s test @ http://127.0.0.1:8888
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     9.93ms    3.90ms  77.18ms   94.87%
    Req/Sec     2.59k   226.41     2.83k    89.25%
  102951 requests in 10.03s, 24.59MB read
Requests/sec:  10267.39
Transfer/sec:      2.45MB

A vill méi performant!

Eise Rust Server mat Actix/deadpool_postgres schléit praktesch eise fréiere Champion Starlette mat +125%, ExpressJS mat +362%, a pure PHP ëm +1366%. (Ech loossen d'Performance Delta mat der Laravel Versioun als Übung fir de Lieser.)

Ech hu festgestallt datt d'Rust Sprooch selwer ze léieren méi schwéier war wéi aner Sproochen well et vill méi Gotchas huet wéi alles wat ech ausserhalb vun der 6502 Assemblée gesinn hunn, awer wann Äre Rust Server 14x d'Zuel vun Benotzer als Äre PHP-Server, dann ass et vläicht nach eppes ze gewannen mat Schalttechnologien. Dofir wäert déi nächst Versioun vum Pafera Framework op Rust baséieren. D'Léierkurve ass vill méi héich wéi Skriptsproochen, awer d'Leeschtung wäert et wäert sinn. Wann Dir d'Zäit net setzt fir Rust ze léieren, dann ass Ären Tech Stack op Starlette oder Node.js och keng schlecht Entscheedung ze baséieren.

Technesch Schold

An de leschten zwanzeg Joer si mir vu bëllegen statesche Hosting-Siten op Shared Hosting mat LAMP-Stacken gaang fir VPSen op AWS, Azure an aner Cloud-Servicer ze lounen. Hautdesdaags si vill Firmen zefridden mat Designentscheedungen ze huelen baséiert op wien se fannen dat ass verfügbar oder bëllegst zënter dem Advent vu praktesche Cloud-Servicer et einfach gemaach hunn méi Hardware op luesen Serveren an Uwendungen ze werfen. Dëst huet hinnen grouss kuerzfristeg Gewënn op Käschte vun laangfristeg technesch Schold ginn.

Kalifornien Chirurg General Warnung: Dëst ass net e richtege Raumhond.

Virun 70 Joer gouf et eng grouss Weltraumrace tëscht der Sowjetunioun an den USA. D'Sowjets hunn déi meescht vun de fréie Meilesteen gewonnen. Si haten den éischte Satellit am Sputnik, den éischten Hond am Weltraum zu Laika, déi éischt Moundraumschëff am Luna 2, den éischte Mann a Fra am Weltraum am Yuri Gagarin a Valentina Tereshkova, a sou weider ...

Awer si hu lues a lues technesch Scholden accumuléiert.

Och wann d'Sowjets als éischt fir all dës Erreeche waren, hunn hir Ingenieursprozesser an Ziler dozou gefouert datt se op kuerzfristeg Erausfuerderunge konzentréieren anstatt laangfristeg Machbarkeet. Si gewannen all Kéier wann se sprangen, awer si gi méi midd a méi lues, während hir Géigner konsequent Schrëtt op d'Arrivée maachen.

Eemol den Neil Armstrong seng historesch Schrëtt um Mound op Live Fernseh gemaach huet, hunn d'Amerikaner d'Féierung geholl, an dunn do bliwwen wéi de sowjetesche Programm gefall ass. Dëst ass net anescht wéi Firmen haut, déi sech op déi nächst grouss Saach, déi nächst grouss Ausbezuelung, oder déi nächst grouss Technologie konzentréiert hunn, wärend se net passend Gewunnechten a Strategien fir laang Strecken entwéckelen.

Als éischt um Maart ze sinn heescht net datt Dir den dominante Spiller op deem Maart gëtt. Alternativ, d'Zäit ze huelen fir d'Saachen richteg ze maachen, garantéiert net Erfolleg, awer erhéicht sécher Är Chancen op laangfristeg Leeschtungen. Wann Dir den Tech Lead fir Är Firma sidd, wielt déi richteg Richtung an Tools fir Är Aarbechtsbelaaschtung. Loosst d'Popularitéit net Leeschtung an Effizienz ersetzen.

Ressourcen

Wëllt Dir eng 7z Datei eroflueden mat de Rust, ExpressJS, Flask, Starlette, a Pure PHP Scripten?

Iwwer den Auteur

Den Jim huet programméiert zënter hien en IBM PS/2 an den 90er krut. Bis haut huet hien nach léiwer HTML a SQL mat der Hand ze schreiwen, a konzentréiert sech op Effizienz a Korrektheet a senger Aarbecht.