Já, Virginia, það *Er* a Jólasveinninn Mismunur á milli veframma árið 2023

Ferðalag eins ögrandi forritara til að finna fljótvirkan vefþjónskóða áður en hann lætur undan markaðsþrýstingi og tæknilegum skuldum
2023-03-24 11:52:06
👁️ 800
💬 0

Rust er tiltölulega nýtt hvað forritunarmál varðar, en það varð mér forvitnilegt þegar Linus Torvalds tilkynnti að hann myndi samþykkja Rust sem Linux kjarna forritunarmál. Fyrir okkur eldri forritarana er það um það bil það sama og að segja að þessi nýja fangleði nýaldar hippaþráður myndi verða ný breyting á stjórnarskrá Bandaríkjanna.

  1. Nú, þegar þú ert reyndur forritari, hefur þú tilhneigingu til að hoppa ekki á vagninn eins hratt og yngra fólkið gerir, annars gætirðu brennt þig af hröðum breytingum á tungumálinu eða bókasöfnum. (Allir sem notuðu fyrstu útgáfuna af AngularJS vita hvað ég er að tala um.) Ryð er enn nokkuð á tilraunastigi og mér finnst fyndið að mörg kóðadæmi á vefnum eru ekki einu sinni setja saman lengur með núverandi útgáfum af pakka.
  2. Hins vegar er ekki hægt að afneita frammistöðunni sem Rust forritin sýna. Ef þú hefur aldrei reynt
  3. ripgrep
  4. eða
  5. fd-finna
  6. út á stórum frumkóðatré, þú ættir örugglega að gefa þeim snúning. Þeir eru jafnvel fáanlegir fyrir flestar Linux dreifingar einfaldlega frá pakkastjóranum. Þú ert að skipta um orðræðu fyrir frammistöðu með Rust... a
  7. mikið
  8. af orðræðu fyrir a
  9. mikið
  10. af frammistöðu.
  11. Heili kóðinn fyrir Rust er svolítið stór, svo við munum bara skoða viðeigandi meðhöndlun hér:
  12. Þetta er miklu flóknara en Python/Node.js útgáfurnar...

Og miklu afkastameiri!

Rust netþjónninn okkar sem notar Actix/deadpool_postgres slær fyrri meistara Starlette vel um +125%, ExpressJS um +362% og hreint PHP um +1366%. (Ég mun skilja eftir frammistöðudeltuna með Laravel útgáfunni sem æfingu fyrir lesandann.)

Ég hef komist að því að það hefur verið erfiðara að læra Rust tungumálið sjálft en önnur tungumál þar sem það hefur miklu fleiri möguleika en allt sem ég hef séð utan 6502 Assembly, en ef Rust þjónninn þinn getur tekið á sig 14x fjölda notendur sem PHP netþjóninn þinn, þá er kannski eitthvað hægt að vinna með því að skipta um tækni eftir allt saman. Þess vegna verður næsta útgáfa af Pafera Framework byggð á Rust. Námsferillinn er miklu hærri en forskriftarmál, en frammistaðan verður þess virði. Ef þú getur ekki lagt tíma í að læra Rust, þá er það heldur ekki slæm ákvörðun að byggja tæknibunkann þinn á Starlette eða Node.js.

Tækniskuld

Á síðustu tuttugu árum höfum við farið frá ódýrum kyrrstæðum hýsingarsíðum yfir í sameiginlega hýsingu með LAMP stafla til að leigja VPS til AWS, Azure og annarra skýjaþjónustu. Nú á dögum eru mörg fyrirtæki ánægð með að taka hönnunarákvarðanir byggðar á hverjum þeim sem þeir geta fundið að er í boði eða ódýrast þar sem tilkoma þægilegrar skýjaþjónustu hefur gert það auðvelt að henda meiri vélbúnaði á hæga netþjóna og forrit. Þetta hefur gefið þeim mikinn skammtímahagnað á kostnað langtíma tæknilegra skulda. Viðvörun skurðlæknis í Kaliforníu: Þetta er ekki raunverulegur geimhundur. . Fyrir 70 árum var mikið geimkapphlaup milli Sovétríkjanna og Bandaríkjanna. Sovétmenn unnu flest fyrstu tímamótin. Þeir voru með fyrsta gervihnöttinn í Spútnik, fyrsta hundinn í geimnum í Laika, fyrsta tunglgeimfarið í Luna 2, fyrsta karlinn og konuna í geimnum í Yuri Gagarin og Valentina Tereshkova, og svo framvegis... En þeir voru hægt og rólega að safna tæknilegum skuldum. , Þrátt fyrir að Sovétmenn hafi verið fyrstir að öllum þessum afrekum, ollu verkfræðiferli þeirra og markmiðum þess að þeir einbeittu sér að skammtímaáskorunum frekar en langtímafýsileika. Þeir unnu í hvert skipti sem þeir stökkva, en þeir voru að verða þreyttari og hægari á meðan andstæðingar þeirra héldu áfram að taka stöðugt skref í átt að marklínunni.

Þegar Neil Armstrong tók söguleg skref sín á tunglinu í beinni sjónvarpsútsendingu tóku Bandaríkjamenn forystuna og héldu sig síðan þar sem sovéska dagskráin hiknaði. Þetta er ekkert öðruvísi en fyrirtæki í dag sem hafa einbeitt sér að næsta stóra hlutnum, næsta stóra afborgun eða næstu stóru tækni á meðan ekki tekst að þróa viðeigandi venjur og aðferðir til lengri tíma litið.

Að vera fyrstur á markað þýðir ekki að þú verðir ráðandi aðili á þeim markaði. Að öðrum kosti, að taka tíma til að gera hlutina rétt tryggir ekki árangur, en eykur vissulega möguleika þína á langtíma árangri. Ef þú ert tæknileiðtogi fyrirtækisins þíns skaltu velja réttu stefnuna og verkfærin fyrir vinnuálagið. Ekki láta vinsældir koma í stað frammistöðu og skilvirkni.

Auðlindir

╰─➤  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

Viltu hlaða niður 7z skrá sem inniheldur Rust, ExpressJS, Flask, Starlette og Pure PHP forskriftirnar?

Komdu!

  1. Lestu setuauðkenni núverandi notanda úr vafraköku
  2. Hlaða viðbótarupplýsingum úr gagnagrunni
  3. Skilaðu þeim upplýsingum til notandans

Hvers konar fávitapróf er það, gætirðu spurt? Jæja, ef þú skoðar netbeiðnirnar fyrir þessa síðu muntu taka eftir einni sem heitir sessionvars.js sem gerir nákvæmlega það sama.

Innihald sessionvars.js

Þú sérð, nútíma vefsíður eru flóknar verur og eitt algengasta verkefnið er að vista flóknar síður til að forðast of mikið álag á gagnagrunnsþjóninn.

Ef við endurgerum flókna síðu í hvert sinn sem notandi biður um hana, þá getum við aðeins þjónað um 600 notendum á sekúndu.

╰─➤  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

En ef við vistum þessa síðu sem kyrrstæða HTML-skrá og leyfum Nginx að henda henni fljótt út um gluggann til notandans, þá getum við þjónað 32.000 notendum á sekúndu, aukið afköst um 50x.

╰─➤  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

Static index.en.html er sá hluti sem fer til allra og aðeins þeir hlutar sem eru mismunandi eftir notendum eru sendir í sessionvars.js. Þetta dregur ekki aðeins úr gagnagrunnsálagi og skapar betri upplifun fyrir notendur okkar, heldur dregur einnig úr skammtafræðilegum líkum á því að þjónninn okkar gufi upp af sjálfu sér í skekkjukjarnabroti þegar Klingons ráðast á.

Kröfur um kóða

Kóðinn sem skilað er fyrir hvern ramma mun hafa eina einföldu kröfu: Sýndu notandanum hversu oft hann hefur endurnýjað síðuna með því að segja "Count er x". Til að hafa hlutina einfalda, munum við halda okkur fjarri Redis biðröðum, Kubernetes íhlutum eða AWS Lambdas í bili.

Sýnir hversu oft þú hefur heimsótt síðuna

Lotugögn hvers notanda verða vistuð í PostgreSQL gagnagrunni.

Taflan notendalota

Og þessi gagnagrunnstafla verður stytt fyrir hvert próf.

Taflan eftir að hafa verið stytt

Einfalt en áhrifaríkt er kjörorð Pafera... fyrir utan myrkustu tímalínuna samt...

Raunverulegar niðurstöður prófa

PHP/Laravel

Jæja, nú getum við loksins farið að skíta okkur í hendurnar. Við munum sleppa uppsetningunni fyrir Laravel þar sem það er bara fullt af tónskáldum og handverksmönnum skipanir.

Í fyrsta lagi munum við setja upp gagnagrunnsstillingar okkar í .env skránni

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

Síðan munum við setja eina bakleið sem sendir allar beiðnir til stjórnandans okkar.

Route::fallback(SessionController::class);

Og stilltu stjórnandann til að sýna fjöldann. Laravel geymir sjálfgefið lotur í gagnagrunninum. Það veitir einnig session() virka til að tengjast við lotugögnin okkar, svo það eina sem þurfti voru nokkrar línur af kóða til að gera síðuna okkar.

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

    $count  += 1;

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

    return 'Count is ' . $count;
  }
}

Eftir að hafa sett upp php-fpm og Nginx lítur síðan okkar nokkuð vel út...

╰─➤  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

Að minnsta kosti þangað til við sjáum niðurstöðurnar í raun og veru...

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

Nei, það er ekki innsláttarvilla. Prófunarvélin okkar hefur farið úr 600 beiðnum á sekúndu sem skilar flókinni síðu... í 21 beiðni á sekúndu sem skilar "Count is 1".

Svo hvað fór úrskeiðis? Er eitthvað athugavert við PHP uppsetninguna okkar? Er Nginx einhvern veginn að hægja á sér þegar það tengist php-fpm?

Hreint PHP

Við skulum endurtaka þessa síðu í hreinum PHP kóða.

<?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'];

Við höfum nú notað 98 línur af kóða til að gera það sem fjórar línur af kóða (og fullt af stillingarvinnu) í Laravel gerðu. (Auðvitað, ef við gerðum rétta villumeðferð og skilaboð sem snúa að notendum, þá væri þetta um það bil tvöfalt fleiri línur.) Kannski getum við náð 30 beiðnum á sekúndu?

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

vá! Það lítur út fyrir að ekkert sé athugavert við PHP uppsetninguna okkar eftir allt saman. Hreina PHP útgáfan gerir 700 beiðnir á sekúndu.

Ef það er ekkert athugavert við PHP, höfum við kannski rangt stillt Laravel?

Að skoða Laravel aftur

Eftir að hafa skoðað vefinn að uppsetningarvandamálum og ráðleggingum um frammistöðu voru tvær af vinsælustu aðferðunum að vista stillingarnar og leiða gögnin til að forðast að vinna úr þeim fyrir hverja beiðni. Þess vegna munum við taka ráðum þeirra og prófa þessar ráðleggingar.

╰─➤  php artisan config:cache

   INFO  Configuration cached successfully.  

╰─➤  php artisan route:cache

   INFO  Routes cached successfully.  

Allt lítur vel út á skipanalínunni. Við skulum endurtaka viðmiðið.

╰─➤  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

Jæja, við höfum nú aukið frammistöðu úr 21,04 í 28,80 beiðnir á sekúndu, sem er stórkostleg hækkun upp á næstum 37%! Þetta væri alveg áhrifamikið fyrir hvaða hugbúnaðarpakka sem er... nema fyrir þá staðreynd að við erum enn að gera aðeins 1/24 hluta af fjölda beiðna í hreinu PHP útgáfunni.

Ef þú heldur að eitthvað hljóti að vera athugavert við þetta próf, ættirðu að tala við höfund Lucinda PHP ramma. Í niðurstöðum sínum hefur hann Lucinda barði Laravel um 36x fyrir HTML beiðnir og 90x fyrir JSON beiðnir.

Eftir að hafa prófað á eigin vél með bæði Apache og Nginx hef ég enga ástæðu til að efast um hann. Laravel er í raun bara það hægt! PHP í sjálfu sér er ekki svo slæmt, en þegar þú bætir við allri aukavinnslunni sem Laravel bætir við hverja beiðni, þá finnst mér mjög erfitt að mæla með Laravel sem vali árið 2023.

Django

PHP/Wordpress reikningar fyrir um 40% allra vefsíðna á vefnum , sem gerir það að langstærsta umgjörðinni. Persónulega finnst mér þó vinsældir ekki endilega skila sér í gæðum frekar en ég finn fyrir skyndilegri óviðráðanlega löngun í þennan óvenjulega sælkeramat frá vinsælasti veitingastaður í heimi ... McDonald&#x27;s. Þar sem við höfum þegar prófað hreinan PHP kóða, ætlum við ekki að prófa Wordpress sjálft, þar sem allt sem tengist Wordpress væri án efa lægra en 700 beiðnir á sekúndu sem við sáum með hreinu PHP.

Django er annar vinsæll rammi sem hefur verið til í langan tíma. Ef þú hefur notað það í fortíðinni, ertu líklega með ánægju að muna eftir stórbrotnu gagnagrunnsviðmóti þess ásamt því hversu pirrandi það var að stilla allt eins og þú vildir. Við skulum sjá hversu vel Django virkar árið 2023, sérstaklega með nýja ASGI viðmótinu sem það hefur bætt við frá og með útgáfu 4.0.

Uppsetning Django er ótrúlega lík því að setja upp Laravel, þar sem þeir voru báðir frá þeim aldri þar sem MVC arkitektúr var stílhrein og rétt. Við munum sleppa leiðinlegu uppsetningunni og fara beint í að setja upp útsýnið.

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}")

Fjórar línur af kóða er það sama og með Laravel útgáfuna. Við skulum sjá hvernig það virkar.

╰─➤  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

Alls ekki slæmt við 355 beiðnir á sekúndu. Það er aðeins helmingur af frammistöðu hreinu PHP útgáfunnar, en það er líka 12x af Laravel útgáfunni. Django gegn Laravel virðist alls ekki vera nein keppni.

Flaska

Fyrir utan stærri allt-þar á meðal-eldhúsvaskinn ramma, þá eru líka smærri rammar sem gera bara grunnuppsetningu en leyfa þér að sjá um afganginn. Einn af þeim bestu til að nota er Flask og ASGI hliðstæða Quart. Minn eigin PaferaPy Framework er byggt ofan á Flask, þannig að ég þekki vel hversu auðvelt það er að koma hlutum í verk og halda frammistöðu.

#!/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}'

Eins og þú sérð er Flask handritið styttra en hreina PHP handritið. Ég kemst að því að af öllum tungumálum sem ég hef notað er Python líklega tjáningarmesta tungumálið með tilliti til ásláttar sem slegnar eru inn. Skortur á axlaböndum og svigum, skilningi á lista og fyrirsögnum og lokun byggð á inndrætti frekar en semíkommum gerir Python frekar einfalt en öflugt í getu sinni.

Því miður er Python líka hægasta almenna tungumálið sem til er, þrátt fyrir hversu mikill hugbúnaður hefur verið skrifaður í það. Fjöldi Python bókasöfna sem eru í boði er um það bil fjórfalt fleiri en svipuð tungumál og nær yfir mikið magn af lénum, ​​en samt myndi enginn segja að Python sé hraðvirkur né afkastamikill utan veggskota eins og NumPy.

Við skulum sjá hvernig Flask útgáfan okkar er í samanburði við fyrri ramma okkar.

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

Flask handritið okkar er í raun hraðar en hreina PHP útgáfan okkar!

Ef þú ert hissa á þessu ættirðu að gera þér grein fyrir því að Flask appið okkar gerir allar frumstillingar sínar og stillingar þegar við ræsum gunicorn þjóninn, á meðan PHP keyrir skriftuna aftur í hvert skipti sem ný beiðni kemur inn. Það&#x27 það jafngildir því að Flask sé ungi, ákafur leigubílstjórinn sem er búinn að setja bílinn í gang og bíður við hliðina á veginum, en PHP er gamli bílstjórinn sem situr heima hjá sér og bíður eftir að hringt sé inn og keyrir þá fyrst. yfir til að sækja þig. Þar sem ég er gamaldags strákur og kemur frá þeim tímum þar sem PHP var dásamleg breyting á venjulegar HTML og SHTML skrár, það er dálítið leiðinlegt að átta sig á hversu langur tími hefur liðið, en hönnunarmunurinn gerir það virkilega erfitt fyrir PHP að keppa á móti Python, Java og Node.js netþjónum sem halda sig bara í minni og sinna beiðni með lipurri vellíðan gúllara.

Starlette

Flaskan gæti verið hraðvirkasta umgjörðin okkar hingað til, en það er í raun ansi gamall hugbúnaður. Python samfélagið skipti yfir í nýrri ósamstilltu ASGI netþjóna fyrir nokkrum árum og auðvitað hef ég sjálfur skipt með þeim.

Nýjasta útgáfan af Pafera Framework, PaferaPyAsync , er byggð á Starlette. Þrátt fyrir að það sé til ASGI útgáfa af Flask sem heitir Quart, var frammistöðumunurinn á Quart og Starlette nóg til að ég endurbætti kóðann minn á Starlette í staðinn.

Ósamstillt forritun getur verið ógnvekjandi fyrir marga, en það er í raun ekki erfitt hugtak þökk sé Node.js strákunum sem gerðu hugmyndina vinsæla fyrir meira en áratug síðan.

Við notuðum til að berjast gegn samhliða fjölþráðum, fjölvinnslu, dreifðri tölvuvinnslu, loforðstengingu og öllum þessum skemmtilegu tímum sem ótímabært eldra og þurrka marga gamalreynda forritara. Nú skrifum við bara async fyrir framan aðgerðir okkar og await fyrir framan hvaða kóða sem gæti tekið smá tíma að keyra. Hann er að sönnu orðrænnari en venjulegur kóði, en mun minna pirrandi í notkun en að þurfa að takast á við frumsamstillingar, sendingu skilaboða og að leysa loforð.

Starlette skráin okkar lítur svona út:

#!/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",
)

Eins og þú sérð er það nokkurn veginn afritað og límt úr Flask skriftunni okkar með aðeins nokkrum leiðarbreytingum og async/await leitarorð.

Hversu miklar umbætur geta afritað og límt kóða raunverulega gefið okkur?

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

Við erum með nýjan meistara, dömur og herrar! Fyrra hámarkið okkar var hreina PHP útgáfan okkar með 704 beiðnir á sekúndu, sem síðan var tekin af Flask útgáfunni okkar með 1080 beiðnum á sekúndu. Starlette handritið okkar eyðir öllum fyrri keppinautum við 4562 beiðnir á sekúndu, sem þýðir 6x framför á hreinu PHP og 4x framför á Flask.

Ef þú hefur ekki breytt WSGI Python kóðanum þínum yfir í ASGI ennþá, gæti verið góður tími til að byrja núna.

Node.js/ExpressJS

Hingað til höfum við aðeins fjallað um PHP og Python ramma. Hins vegar notar stór hluti heimsins í raun Java, DotNet, Node.js, Ruby on Rails og aðra slíka tækni fyrir vefsíður sínar. Þetta er alls ekki yfirgripsmikið yfirlit yfir öll vistkerfi heimsins og lífverur, svo til að forðast forritunarsamsvar lífrænnar efnafræði, veljum við aðeins þá ramma sem auðveldast er að slá inn kóða fyrir. . sem Java er örugglega ekki.

Nema þú hafir falið þig undir eintakinu þínu af K&R C eða Knuth&#x27;s Listin að forritun síðustu fimmtán ár hefur þú sennilega heyrt um Node.js. Við sem höfum verið til frá upphafi JavaScript erum annaðhvort ótrúlega hrædd, undrandi eða bæði yfir ástandi nútíma JavaScript, en því er ekki að neita að JavaScript er orðið að afl sem þarf líka á netþjónum sem vafrar. Þegar öllu er á botninn hvolft höfum við meira að segja innfæddar 64 bita heiltölur núna í tungumálinu! Það er miklu betra en allt sem er geymt í 64 bita fljótum!

ExpressJS er líklega auðveldasti Node.js þjónninn í notkun, svo við munum gera fljótlegt og óhreint Node.js/ExpressJS app til að þjóna teljaranum okkar.

/**********************************************************************
 * 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}`));

Þessi kóði var í raun auðveldari að skrifa en Python útgáfurnar, þó að innfæddur JavaScript verði frekar ómeðhöndlaður þegar forrit verða stærri og allar tilraunir til að leiðrétta þetta eins og TypeScript verða fljótt orðaðari en Python.

Við skulum sjá hvernig þetta virkar!

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

Þú gætir hafa heyrt fornar (fornar á netstaðla alla vega...) þjóðsögur um Node.js&#x27; hraða, og þessar sögur eru að mestu sannar þökk sé stórbrotnu starfi sem Google hefur unnið með V8 JavaScript vélinni. Í þessu tilviki þó, þó að skyndiforritið okkar standi sig betur en Flask-handritið, er einþráður eðli þess sigrað af fjórum ósamstillingarferlunum sem Starlette Knight beitir sem segir "Ni!".

Við skulum fá meiri hjálp!

╰─➤  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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

Allt í lagi! Nú er bardaginn jafn fjórir á móti fjórum! Við skulum mæla!

╰─➤  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

Enn ekki alveg á stigi Starlette, en það er ekki slæmt fyrir fljótlega fimm mínútna JavaScript hakk. Frá eigin prófun minni er þessu handriti í raun haldið aftur af gagnagrunnstengingarstigi vegna þess að node-postgres er hvergi nærri eins skilvirkt og psycopg er fyrir Python. Að skipta yfir í sqlite þar sem gagnagrunnsbílstjórinn skilar yfir 3000 beiðnum á sekúndu fyrir sama ExpressJS kóða.

Aðalatriðið sem þarf að hafa í huga er að þrátt fyrir hægan framkvæmdarhraða Python geta ASGI rammar í raun verið samkeppnishæfir við Node.js lausnir fyrir ákveðið vinnuálag.

Ryð/Actix

Þannig að núna erum við að nálgast toppinn á fjallinu, og með fjalli á ég við hæstu viðmiðunarstig sem skráð hafa verið af músum og körlum.

Ef þú skoðar flestar rammaviðmiðanir sem til eru á vefnum muntu taka eftir því að það eru tvö tungumál sem hafa tilhneigingu til að ráða efst: C++ og Rust. Ég hef unnið með C++ síðan á tíunda áratugnum, og ég hafði meira að segja minn eigin Win32 C++ ramma áður en MFC/ATL var eitthvað, svo ég hef mikla reynslu af tungumálinu. Það er ekki skemmtilegt að vinna með eitthvað þegar þú veist það nú þegar, svo við ætlum að gera Rust útgáfu í staðinn. ;)

Rust er tiltölulega nýtt hvað forritunarmál varðar, en það varð mér forvitnilegt þegar Linus Torvalds tilkynnti að hann myndi samþykkja Rust sem Linux kjarna forritunarmál. Fyrir okkur eldri forritarana er það um það bil það sama og að segja að þessi nýja fangleði nýaldar hippaþráður myndi verða ný breyting á stjórnarskrá Bandaríkjanna.

Nú, þegar þú ert reyndur forritari, hefur þú tilhneigingu til að hoppa ekki á vagninn eins hratt og yngra fólkið gerir, annars gætirðu brennt þig af hröðum breytingum á tungumálinu eða bókasöfnum. (Allir sem notuðu fyrstu útgáfuna af AngularJS vita hvað ég er að tala um.) Ryð er enn nokkuð á tilraunastigi og mér finnst fyndið að mörg kóðadæmi á vefnum eru ekki einu sinni setja saman lengur með núverandi útgáfum af pakka.

Hins vegar er ekki hægt að afneita frammistöðunni sem Rust forritin sýna. Ef þú hefur aldrei reynt ripgrep eða fd-finna út á stórum frumkóðatré, þú ættir örugglega að gefa þeim snúning. Þeir eru jafnvel fáanlegir fyrir flestar Linux dreifingar einfaldlega frá pakkastjóranum. Þú ert að skipta um orðræðu fyrir frammistöðu með Rust... a mikið af orðræðu fyrir a mikið af frammistöðu.

Heili kóðinn fyrir Rust er svolítið stór, svo við munum bara skoða viðeigandi meðhöndlun hér:

// =====================================================================
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
  )))
}

Þetta er miklu flóknara en Python/Node.js útgáfurnar...

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

Og miklu afkastameiri!

Rust netþjónninn okkar sem notar Actix/deadpool_postgres slær fyrri meistara Starlette vel um +125%, ExpressJS um +362% og hreint PHP um +1366%. (Ég mun skilja eftir frammistöðudeltuna með Laravel útgáfunni sem æfingu fyrir lesandann.)

Ég hef komist að því að það hefur verið erfiðara að læra Rust tungumálið sjálft en önnur tungumál þar sem það hefur miklu fleiri möguleika en allt sem ég hef séð utan 6502 Assembly, en ef Rust þjónninn þinn getur tekið á sig 14x fjölda notendur sem PHP netþjóninn þinn, þá er kannski eitthvað hægt að vinna með því að skipta um tækni eftir allt saman. Þess vegna verður næsta útgáfa af Pafera Framework byggð á Rust. Námsferillinn er miklu hærri en forskriftarmál, en frammistaðan verður þess virði. Ef þú getur ekki lagt tíma í að læra Rust, þá er það heldur ekki slæm ákvörðun að byggja tæknibunkann þinn á Starlette eða Node.js.

Tækniskuld

Á síðustu tuttugu árum höfum við farið frá ódýrum kyrrstæðum hýsingarsíðum yfir í sameiginlega hýsingu með LAMP stafla til að leigja VPS til AWS, Azure og annarra skýjaþjónustu. Nú á dögum eru mörg fyrirtæki ánægð með að taka hönnunarákvarðanir byggðar á hverjum þeim sem þeir geta fundið að er í boði eða ódýrast þar sem tilkoma þægilegrar skýjaþjónustu hefur gert það auðvelt að henda meiri vélbúnaði á hæga netþjóna og forrit. Þetta hefur gefið þeim mikinn skammtímahagnað á kostnað langtíma tæknilegra skulda.

Viðvörun skurðlæknis í Kaliforníu: Þetta er ekki raunverulegur geimhundur.

Fyrir 70 árum var mikið geimkapphlaup milli Sovétríkjanna og Bandaríkjanna. Sovétmenn unnu flest fyrstu tímamótin. Þeir voru með fyrsta gervihnöttinn í Spútnik, fyrsta hundinn í geimnum í Laika, fyrsta tunglgeimfarið í Luna 2, fyrsta karlinn og konuna í geimnum í Yuri Gagarin og Valentina Tereshkova, og svo framvegis...

En þeir voru hægt og rólega að safna tæknilegum skuldum.

Þrátt fyrir að Sovétmenn hafi verið fyrstir að öllum þessum afrekum, ollu verkfræðiferli þeirra og markmiðum þess að þeir einbeittu sér að skammtímaáskorunum frekar en langtímafýsileika. Þeir unnu í hvert skipti sem þeir stökkva, en þeir voru að verða þreyttari og hægari á meðan andstæðingar þeirra héldu áfram að taka stöðugt skref í átt að marklínunni.

Þegar Neil Armstrong tók söguleg skref sín á tunglinu í beinni sjónvarpsútsendingu tóku Bandaríkjamenn forystuna og héldu sig síðan þar sem sovéska dagskráin hiknaði. Þetta er ekkert öðruvísi en fyrirtæki í dag sem hafa einbeitt sér að næsta stóra hlutnum, næsta stóra afborgun eða næstu stóru tækni á meðan ekki tekst að þróa viðeigandi venjur og aðferðir til lengri tíma litið.

Að vera fyrstur á markað þýðir ekki að þú verðir ráðandi aðili á þeim markaði. Að öðrum kosti, að taka tíma til að gera hlutina rétt tryggir ekki árangur, en eykur vissulega möguleika þína á langtíma árangri. Ef þú ert tæknileiðtogi fyrirtækisins þíns skaltu velja réttu stefnuna og verkfærin fyrir vinnuálagið. Ekki láta vinsældir koma í stað frammistöðu og skilvirkni.

Auðlindir

Viltu hlaða niður 7z skrá sem inniheldur Rust, ExpressJS, Flask, Starlette og Pure PHP forskriftirnar?

Um höfundinn

Jim hefur verið að forrita síðan hann fékk IBM PS/2 aftur á tíunda áratugnum. Enn þann dag í dag vill hann frekar skrifa HTML og SQL í höndunum og leggur áherslu á skilvirkni og réttmæti í starfi sínu.