En son iş görüşmelerimden birinin ardından, başvurduğum şirketin yaklaşık on yıl önce denediğim bir PHP çerçevesi olan Laravel'i hâlâ kullandığını fark ettiğimde şaşırdım. O zamanlar için fena değildi, ancak teknoloji ve modada bir sabit varsa, o da stillerin ve kavramların sürekli değişmesi ve yeniden ortaya çıkmasıdır. Eğer bir JavaScript programcısıysanız, muhtemelen bu eski şakayı biliyorsunuzdur
Programcı 1: "Bu yeni JavaScript çerçevesini beğenmedim!"
Programcı 2: "Endişelenmeye gerek yok. Sadece altı ay bekleyin, yerini alacak başkası gelecek!"
Meraktan, eski ve yeniyi test ettiğimizde tam olarak ne olacağını görmeye karar verdim. Elbette, web kıyaslamalar ve iddialarla dolu, bunların en popüleri muhtemelen TechEmpower Web Çerçevesi Karşılaştırmaları burada . Bugün onlar kadar karmaşık bir şey yapmayacağız. Her şeyi güzel ve basit tutacağız, böylece bu makale bir Savaş ve Barış , ve okumayı bitirdiğinizde uyanık kalma şansınızın da az da olsa olacağı. Her zamanki uyarılar geçerli: bu sizin makinenizde aynı şekilde çalışmayabilir, farklı yazılım sürümleri performansı etkileyebilir ve Schrödinger'in kedisi aslında aynı anda yarı canlı yarı ölü olan bir zombi kediye dönüştü.
Bu test için, burada gösterildiği gibi Manjaro Linux çalıştıran zayıf bir i5'e sahip dizüstü bilgisayarımı kullanacağım.
╰─➤ 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
Kodumuz her istek için üç basit göreve sahip olacak:
Bu ne tür bir aptalca test diye sorabilirsiniz? Pekala, bu sayfa için ağ isteklerine bakarsanız, sessionvars.js adında, tam olarak aynı şeyi yapan bir tane göreceksiniz.
Görüyorsunuz, modern web sayfaları karmaşık yaratıklardır ve en yaygın görevlerden biri de veritabanı sunucusunda aşırı yükü önlemek için karmaşık sayfaları önbelleğe almaktır.
Karmaşık bir sayfayı her kullanıcı istediğinde yeniden oluşturursak, saniyede yalnızca yaklaşık 600 kullanıcıya hizmet verebiliriz.
╰─➤ 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
Ancak bu sayfayı statik bir HTML dosyası olarak önbelleğe alırsak ve Nginx'in bunu hızla kullanıcıya göndermesine izin verirsek, saniyede 32.000 kullanıcıya hizmet verebilir ve performansı 50 kat artırabiliriz.
╰─➤ 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
Statik index.en.html herkese giden kısımdır ve sadece kullanıcıya göre farklılık gösteren kısımlar sessionvars.js'de gönderilir. Bu sadece veritabanı yükünü azaltmakla ve kullanıcılarımız için daha iyi bir deneyim yaratmakla kalmaz, aynı zamanda Klingonlar saldırdığında sunucumuzun bir warp çekirdeği ihlalinde kendiliğinden buharlaşmasının kuantum olasılıklarını da azaltır.
Her çerçeve için döndürülen kodun basit bir gereksinimi olacak: kullanıcıya sayfayı kaç kez yenilediğini "Sayı x" diyerek göster. İşleri basit tutmak için şimdilik Redis kuyruklarından, Kubernetes bileşenlerinden veya AWS Lambdalardan uzak duracağız.
Her kullanıcının oturum verileri PostgreSQL veritabanına kaydedilecektir.
Ve bu veritabanı tablosu her testten önce kesilecektir.
Basit ama etkili olan budur Pafera'nın sloganı... en karanlık zaman diliminin dışında bile...
Tamam, şimdi nihayet ellerimizi kirletmeye başlayabiliriz. Laravel için kurulumu atlayacağız çünkü bu sadece bir grup composer ve artisan komutu.
İlk olarak .env dosyasında veritabanı ayarlarımızı yapacağız
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest
Daha sonra, her isteği kontrolcümüze gönderen tek bir geri dönüş rotası ayarlayacağız.
Route::fallback(SessionController::class);
Ve denetleyiciyi sayımı görüntüleyecek şekilde ayarlayın. Laravel, varsayılan olarak oturumları veritabanında depolar. Ayrıca şunları da sağlar: session()
Oturum verilerimizle arayüz oluşturmak için bir fonksiyon kullandık, bu yüzden sayfamızı oluşturmak için sadece birkaç satır kod yazmamız yeterliydi.
class SessionController extends Controller
{
public function __invoke(Request $request)
{
$count = session('count', 0);
$count += 1;
session(['count' => $count]);
return 'Count is ' . $count;
}
}
Php-fpm ve Nginx'i kurduktan sonra sayfamız oldukça güzel görünüyor...
╰─➤ 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
En azından test sonuçlarını görene kadar...
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
Hayır, bu bir yazım hatası değil. Test makinemiz karmaşık bir sayfayı işlemek için saniyede 600 istekten... saniyede 21 istekle "Sayı 1" işlemeye geçti.
Peki ne yanlış gitti? PHP kurulumumuzda bir sorun mu var? Nginx php-fpm ile arayüz oluştururken bir şekilde yavaşlıyor mu?
Bu sayfayı saf PHP koduyla yeniden yapalım.
<?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'];
Laravel'de dört satır kodun (ve bir sürü yapılandırma çalışmasının) yaptığı şeyi yapmak için artık 98 satır kod kullandık. (Elbette, eğer doğru hata işleme ve kullanıcıya dönük mesajlar yapsaydık, bu satır sayısının yaklaşık iki katı olurdu.) Belki saniyede 30 isteğe ulaşabiliriz?
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
Vay canına! Görünüşe göre PHP kurulumumuzda hiçbir sorun yok. Saf PHP sürümü saniyede 700 istek yapıyor.
PHP'de bir sorun yoksa belki Laravel'i yanlış yapılandırdık?
Web'de yapılandırma sorunları ve performans ipuçlarını taradıktan sonra, en popüler tekniklerden ikisi yapılandırmayı önbelleğe almak ve her istek için işlenmesini önlemek için verileri yönlendirmekti. Bu nedenle, onların tavsiyesini dinleyip bu ipuçlarını deneyeceğiz.
╰─➤ php artisan config:cache
INFO Configuration cached successfully.
╰─➤ php artisan route:cache
INFO Routes cached successfully.
Komut satırında her şey iyi görünüyor. Karşılaştırmayı tekrar yapalım.
╰─➤ 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
Peki, şimdi saniyede 21.04'ten 28.80 isteğe kadar performansı artırdık, neredeyse %37'lik dramatik bir artış! Bu, herhangi bir yazılım paketi için oldukça etkileyici olurdu... saf PHP sürümünün istek sayısının yalnızca 1/24'ünü gerçekleştirdiğimiz gerçeği hariç.
Bu testte bir şeylerin yanlış olduğunu düşünüyorsanız, Lucinda PHP framework'ünün yazarıyla görüşmelisiniz. Test sonuçlarında, Lucinda Laravel'i yendi HTML istekleri için 36x ve JSON istekleri için 90x.
Hem Apache hem de Nginx ile kendi makinemde test ettikten sonra, ondan şüphe etmek için hiçbir nedenim yok. Laravel gerçekten sadece O yavaş! PHP kendi başına o kadar da kötü değil, ancak Laravel'in her isteğe eklediği tüm ekstra işlemleri eklediğinizde, 2023'te Laravel'i bir seçenek olarak önermeyi çok zor buluyorum.
PHP/Wordpress hesapları web'deki tüm web sitelerinin yaklaşık %40'ı , bunu açık ara en baskın çerçeve haline getiriyor. Kişisel olarak, popülerliğin kaliteye dönüşmesinin zorunlu olmadığını düşünüyorum, tıpkı kendimi o olağanüstü gurme yemeğe karşı aniden kontrol edilemez bir istek duyarken bulmam gibi dünyanın en popüler restoranı ... McDonald's. Saf PHP kodunu zaten test ettiğimizden, Wordpress'in kendisini test etmeyeceğiz, çünkü Wordpress'i içeren herhangi bir şey, saf PHP ile gözlemlediğimiz saniyede 700 istekten şüphesiz daha düşük olacaktır.
Django uzun zamandır var olan bir diğer popüler framework'tür. Geçmişte kullandıysanız, muhteşem veritabanı yönetim arayüzünü ve her şeyi istediğiniz gibi yapılandırmanın ne kadar can sıkıcı olduğunu muhtemelen sevgiyle hatırlıyorsunuzdur. Django'nun 2023'te ne kadar iyi çalıştığını, özellikle de 4.0 sürümünden itibaren eklediği yeni ASGI arayüzüyle görelim.
Django'yu kurmak, Laravel'i kurmaya oldukça benzerdir, çünkü ikisi de MVC mimarilerinin şık ve doğru olduğu çağdan kalmadır. Sıkıcı yapılandırmayı atlayıp doğrudan görünümü kurmaya geçeceğiz.
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}")
Dört satır kod Laravel versiyonuyla aynıdır. Nasıl performans gösterdiğine bakalım.
╰─➤ 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
Saniyede 355 istekle hiç de fena değil. Saf PHP sürümünün performansının sadece yarısı, ancak Laravel sürümünün de 12 katı. Django ve Laravel hiç de yarışmıyor gibi görünüyor.
Mutfak lavabosu dahil her şeyi kapsayan daha büyük çerçevelerin yanı sıra, yalnızca bazı temel kurulumları yapıp gerisini sizin halletmenize izin veren daha küçük çerçeveler de vardır. Kullanılabilecek en iyilerinden biri Flask ve onun ASGI karşılığı olan Quart'tır. Benimki PaferaPy Çerçevesi Flask üzerine inşa edilmiştir, bu yüzden performansı korurken işlerin ne kadar kolay halledilebileceğini gayet iyi biliyorum.
#!/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}'
Gördüğünüz gibi, Flask betiği saf PHP betiğinden daha kısadır. Kullandığım tüm diller arasında Python'un muhtemelen tuş vuruşları açısından en ifade edici dil olduğunu düşünüyorum. Parantez ve parantezlerin olmaması, liste ve sözlük kavrayışları ve noktalı virgül yerine girintiye dayalı engelleme Python'u oldukça basit ama yetenekleri açısından güçlü kılıyor.
Ne yazık ki, Python, içinde ne kadar çok yazılım yazılmış olursa olsun, aynı zamanda en yavaş genel amaçlı dildir. Mevcut Python kütüphanelerinin sayısı benzer dillerden yaklaşık dört kat daha fazladır ve çok sayıda alanı kapsar, ancak hiç kimse Python'un NumPy gibi nişler dışında hızlı veya performanslı olduğunu söyleyemez.
Flask versiyonumuzu önceki framework'lerimizle karşılaştıralım.
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 betiğimiz aslında saf PHP versiyonumuzdan daha hızlıdır!
Eğer buna şaşırıyorsanız, Flask uygulamamızın tüm başlatma ve yapılandırmasını gunicorn sunucusunu başlattığımızda yaptığını, PHP'nin ise her yeni istek geldiğinde betiği yeniden çalıştırdığını fark etmelisiniz. Bu, Flask'ın arabayı çalıştırmış ve yol kenarında bekleyen genç ve istekli taksi şoförü olmasına, PHP'nin ise bir çağrı gelmesini bekleyip ancak o zaman sizi almaya gelen yaşlı şoför olmasına eşdeğerdir. Eski kafalı biri olarak ve PHP'nin düz HTML ve SHTML dosyalarına harika bir değişiklik olduğu günlerden geliyorum, ne kadar zaman geçtiğini fark etmek biraz üzücü, ancak tasarım farklılıkları PHP'nin sadece bellekte kalan ve istekleri bir hokkabazın çevik kolaylığıyla işleyen Python, Java ve Node.js sunucularına karşı rekabet etmesini gerçekten zorlaştırıyor.
Flask şimdiye kadarki en hızlı framework'ümüz olabilir, ancak aslında oldukça eski bir yazılımdır. Python topluluğu birkaç yıl önce daha yeni asenkron ASGI sunucularına geçti ve tabii ki ben de onlarla birlikte geçtim.
Pafera Framework'ün en yeni sürümü, PaferaPyAsync , Starlette'e dayanmaktadır. Flask'ın Quart adında bir ASGI sürümü olmasına rağmen, Quart ve Starlette arasındaki performans farkları kodumu Starlette'e dayandırmam için yeterliydi.
Asenkron programlama birçok kişi için korkutucu olabilir, ancak Node.js ekibinin on yıldan fazla bir süre önce bu kavramı popülerleştirmesi sayesinde aslında zor bir kavram değildir.
Çoklu iş parçacığı, çoklu işlem, dağıtılmış bilgi işlem, söz zincirleme ve birçok deneyimli programcıyı erken yaşlandıran ve kurutan tüm o eğlenceli zamanlarla eşzamanlılıkla mücadele ediyorduk. Şimdi, sadece yazıyoruz async
fonksiyonlarımızın önünde ve await
Yürütülmesi uzun sürebilecek herhangi bir kodun önünde. Gerçekten de normal koddan daha ayrıntılıdır, ancak senkronizasyon ilkel öğeleri, mesaj geçişi ve vaatleri çözümlemekle uğraşmaktan çok daha az can sıkıcıdır.
Starlette dosyamız şu şekilde görünüyor:
#!/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",
)
Gördüğünüz gibi, Flask betiğimizden kopyalanıp yapıştırılmış, sadece birkaç yönlendirme değişikliği var ve async/await
Anahtar kelimeler.
Kopyalayıp yapıştırılan kod bize gerçekten ne kadar gelişme sağlayabilir?
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
Yeni bir şampiyonumuz var, hanımlar ve beyler! Önceki rekorumuz saniyede 704 istekle saf PHP versiyonumuzdu, ardından saniyede 1080 istekle Flask versiyonumuz tarafından geçildi. Starlette betiğimiz saniyede 4562 istekle önceki tüm rakipleri eziyor, bu da saf PHP'ye göre 6 kat, Flask'a göre 4 kat iyileştirme anlamına geliyor.
Eğer WSGI Python kodunuzu henüz ASGI'ye çevirmediyseniz, şimdi başlamak için iyi bir zaman olabilir.
Şimdiye kadar yalnızca PHP ve Python çerçevelerini ele aldık. Ancak, dünyanın büyük bir kısmı web siteleri için aslında Java, DotNet, Node.js, Ruby on Rails ve diğer benzer teknolojileri kullanıyor. Bu, dünyanın tüm ekosistemleri ve biyomlarının kapsamlı bir genel bakışı değildir, bu nedenle organik kimyanın programlama eşdeğerini yapmaktan kaçınmak için yalnızca kod yazması en kolay çerçeveleri seçeceğiz... ki Java kesinlikle bunlardan biri değildir.
K&R C veya Knuth'un kopyasının altında saklanmadığınız sürece Bilgisayar Programlamanın Sanatı Son on beş yıldır, muhtemelen Node.js'yi duymuşsunuzdur. JavaScript'in başlangıcından beri var olan bizler, modern JavaScript'in durumundan inanılmaz derecede korkuyoruz, hayrete düşüyoruz veya her ikisini birden yaşıyoruz, ancak JavaScript'in sunucularda ve tarayıcılarda hesaba katılması gereken bir güç haline geldiğini inkar edemeyiz. Sonuçta, artık dilde yerel 64 bit tamsayılar bile var! Bu, her şeyin 64 bit kayan noktalı sayılarda depolanmasından çok daha iyi!
ExpressJS muhtemelen kullanımı en kolay Node.js sunucusudur, bu yüzden sayacımızı sunmak için hızlı ve basit bir Node.js/ExpressJS uygulaması yapacağız.
/**********************************************************************
* 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}`));
Bu kod aslında Python versiyonlarından daha kolay yazılıyordu, ancak uygulamalar büyüdükçe yerel JavaScript oldukça kullanışsız hale geliyor ve TypeScript gibi bu durumu düzeltmeye yönelik tüm girişimler Python'dan daha ayrıntılı hale geliyor.
Bakalım nasıl performans gösterecek!
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
Node.js'nin hızı hakkında eski (en azından İnternet standartlarına göre eski...) halk hikayeleri duymuş olabilirsiniz ve bu hikayelerin çoğu Google'ın V8 JavaScript motoruyla yaptığı muhteşem çalışma sayesinde gerçektir. Ancak bu durumda, hızlı uygulamamız Flask betiğinden daha iyi performans gösterse de, tek iş parçacıklı yapısı, "Ni!" diyen Starlette Knight tarafından kullanılan dört asenkron işlem tarafından alt edilir.
Hadi biraz daha yardım alalım!
╰─➤ 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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Tamam! Şimdi dörtlü dörtlü bir mücadele! Hadi kıyaslama yapalım!
╰─➤ 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
Hala Starlette seviyesinde değil, ancak hızlı beş dakikalık bir JavaScript hack'i için fena değil. Kendi testlerime göre, bu betik aslında veritabanı arayüz seviyesinde biraz geride tutuluyor çünkü node-postgres, Python için psycopg kadar verimli değil. Veritabanı sürücüsü olarak sqlite'a geçmek, aynı ExpressJS kodu için saniyede 3000'den fazla istek üretiyor.
Dikkat edilmesi gereken en önemli nokta, Python'un yavaş yürütme hızına rağmen ASGI çerçevelerinin belirli iş yükleri için Node.js çözümleriyle rekabet edebildiğidir.
Yani şimdi dağın tepesine yaklaşıyoruz ve dağ derken, hem fareler hem de insanlar tarafından kaydedilen en yüksek kıyaslama puanlarını kastediyorum.
Web üzerinde mevcut olan framework kıyaslamalarının çoğuna bakarsanız, zirveye hakim olma eğiliminde olan iki dil olduğunu fark edeceksiniz: C++ ve Rust. 90'lardan beri C++ ile çalışıyorum ve hatta MFC/ATL henüz bir şey olmadan önce kendi Win32 C++ framework'üm bile vardı, bu yüzden dil konusunda çok deneyimim var. Zaten bildiğiniz bir şeyle çalışmak pek eğlenceli değil, bu yüzden bunun yerine bir Rust sürümü yapacağız. ;)
Rust, programlama dilleri arasında nispeten yeni bir dildir, ancak Linus Torvalds Rust'ı Linux çekirdek programlama dili olarak kabul edeceğini duyurduğunda benim için bir merak konusu haline geldi. Biz eski programcılar için bu, bu yeni moda yeni çağ hippi şeyinin ABD Anayasası'na yeni bir değişiklik olacağını söylemekle hemen hemen aynı şey.
Şimdi, deneyimli bir programcı olduğunuzda, gençlerin yaptığı kadar hızlı bir şekilde trene atlama eğiliminde olmazsınız, aksi takdirde dil veya kütüphanelerde yapılan hızlı değişiklikler yüzünden yanabilirsiniz. (AngularJS'nin ilk sürümünü kullanan herkes ne demek istediğimi anlayacaktır.) Rust hala deneysel geliştirme aşamasında ve web'deki birçok kod örneğinin artık paketlerin güncel sürümleriyle derlenmemesi bana komik geliyor.
Ancak Rust uygulamalarının gösterdiği performans inkar edilemez. Eğer hiç denemediyseniz ripgrep veya fd-bul büyük kaynak kodu ağaçlarında, kesinlikle onlara bir şans vermelisiniz. Hatta çoğu Linux dağıtımı için paket yöneticisinden bile kullanılabilirler. Rust ile performans için ayrıntılı bilgi alışverişinde bulunuyorsunuz... pay ayrıntılı bir açıklama için pay Performansın.
Rust için tam kod biraz büyük, bu yüzden burada ilgili işleyicilere bir göz atacağız:
// =====================================================================
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
)))
}
Bu Python/Node.js versiyonlarından çok daha karmaşıktır...
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
Ve çok daha performanslı!
Actix/deadpool_postgres kullanan Rust sunucumuz, önceki şampiyonumuz Starlette'i +125%, ExpressJS'i +362% ve saf PHP'yi +1366% oranında rahatlıkla geçiyor. (Laravel sürümüyle performans farkını okuyucunun egzersizi olarak bırakacağım.)
Rust dilinin kendisini öğrenmenin, 6502 Assembly dışında gördüğüm her şeyden daha fazla tuzak içerdiği için diğer dillerden daha zor olduğunu gördüm, ancak Rust sunucunuz PHP sunucunuzun 14 katı kadar kullanıcıyı kaldırabiliyorsa, o zaman belki de teknolojiler arasında geçiş yaparak kazanılacak bir şeyler vardır. Bu nedenle Pafera Framework'ün bir sonraki sürümü Rust'a dayalı olacak. Öğrenme eğrisi betik dillerinden çok daha yüksektir, ancak performans buna değecektir. Rust'ı öğrenmek için zaman ayıramıyorsanız, teknoloji yığınınızı Starlette veya Node.js'ye dayandırmak da kötü bir karar değildir.
Son yirmi yılda, ucuz statik barındırma sitelerinden LAMP yığınlarıyla paylaşımlı barındırmaya, AWS, Azure ve diğer bulut hizmetlerine VPS kiralamaya geçtik. Günümüzde, birçok şirket, uygun bulut hizmetlerinin ortaya çıkmasıyla yavaş sunuculara ve uygulamalara daha fazla donanım atmayı kolaylaştırdığından, bulabildikleri mevcut veya en ucuz olana göre tasarım kararları almaktan memnun. Bu, onlara uzun vadeli teknik borç pahasına büyük kısa vadeli kazançlar sağladı.
70 yıl önce, Sovyetler Birliği ile Amerika Birleşik Devletleri arasında büyük bir uzay yarışı vardı. Sovyetler erken dönüm noktalarının çoğunu kazandı. Sputnik'te ilk uyduya, Laika'da uzaya çıkan ilk köpeğe, Luna 2'de ilk ay uzay aracına, Yuri Gagarin ve Valentina Tereshkova'da uzaya çıkan ilk erkeğe ve kadına sahiplerdi, vb...
Ancak yavaş yavaş teknik borç birikmeye başladı.
Sovyetler bu başarıların her birine ilk ulaşanlar olsa da, mühendislik süreçleri ve hedefleri onları uzun vadeli uygulanabilirlikten ziyade kısa vadeli zorluklara odaklanmaya yöneltiyordu. Her sıçrayışta kazandılar, ancak rakipleri bitiş çizgisine doğru istikrarlı adımlar atmaya devam ederken onlar daha yorgun ve yavaş hale geliyorlardı.
Neil Armstrong canlı televizyonda Ay'a tarihi adımlarını attığında, Amerikalılar liderliği ele geçirdi ve ardından Sovyet programı tökezlediğinde orada kaldılar. Bu, uzun vadede uygun alışkanlıklar ve stratejiler geliştirmeyi başaramayan, bir sonraki büyük şeye, bir sonraki büyük getiriye veya bir sonraki büyük teknolojiye odaklanan günümüz şirketlerinden farklı değildir.
Pazara ilk giren olmak, o pazarda baskın oyuncu olacağınız anlamına gelmez. Alternatif olarak, işleri doğru yapmak için zaman ayırmak başarıyı garantilemez, ancak uzun vadeli başarı şansınızı kesinlikle artırır. Şirketinizin teknoloji lideriyseniz, iş yükünüz için doğru yönü ve araçları seçin. Popülerliğin performans ve verimliliğin yerini almasına izin vermeyin.
Rust, ExpressJS, Flask, Starlette ve Pure PHP scriptlerini içeren 7z dosyasını indirmek mi istiyorsunuz?
Yazar Hakkında |
|
![]() |
Jim, 90'larda bir IBM PS/2 aldığından beri programlama yapıyor. Bugüne kadar, hala HTML ve SQL'i elle yazmayı tercih ediyor ve işinde verimliliğe ve doğruluğa odaklanıyor. |