最近の就職面接の後、私が応募した会社がまだLaravelを使っていることに気づいて驚いた。Laravelは私が10年ほど前に試したPHPフレームワークだ。当時としてはまともなものだったが、テクノロジーとファッションに共通する不変のものがあるとすれば、それは絶え間ない変化とスタイルやコンセプトの再浮上だ。もしあなたがJavaScriptプログラマーなら、おそらく次のような古いジョークをご存知だろう。
プログラマー1: "この新しいJavaScriptフレームワークは気に入らない;
プログラマー2:"心配する必要はありません。半年待てば、また新しいのが出てくるから!";
興味本位で、新旧をテストしたらどうなるのか見てみることにした。もちろん、ウェブ上にはベンチマークやクレームがあふれている。 TechEmpower Webフレームワークベンチマークはこちら . しかし、今日は彼らほど複雑なことをするつもりはない。しかし、今日はそれほど複雑なことをするつもりはない。 戦争と平和 , そして、読み終わるころには目が覚めているかもしれない。あなたのマシンでは同じようには動かないかもしれないし、ソフトウェアのバージョンが違えばパフォーマンスに影響するかもしれない。
このテストでは、Manjaro Linuxが動作するi5で武装した私のラップトップを使用する。
╰─➤ 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
このコードでは、各リクエストに対して3つの単純なタスクがある:
どんなバカげたテストなんだ、とあなたは尋ねるかもしれない。さて、このページのネットワークリクエストを見てみると、全く同じことをするsessionvars.jsというものがあることに気づくだろう。
現代のウェブページは複雑な生き物であり、最も一般的なタスクのひとつは、データベース・サーバーへの過剰な負荷を避けるために複雑なページをキャッシュすることだ。
ユーザーがリクエストするたびに複雑なページを再レンダリングすると、1秒間に約600人のユーザーにしかサービスを提供できない。
╰─➤ 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
しかし、このページを静的なHTMLファイルとしてキャッシュし、Nginxに素早くユーザーに投げ出させれば、1秒間に32,000人のユーザーにサービスを提供することができ、パフォーマンスは50倍に向上する。
╰─➤ 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
静的なindex.en.htmlはすべての人に送られる部分であり、ユーザーによって異なる部分だけがsessionvars.jsで送られます。これにより、データベースの負荷が軽減され、ユーザーにとってより良いエクスペリエンスが生まれるだけでなく、クリンゴンが攻撃してきたときにサーバーがワープコアの裂け目で自然蒸発してしまう量子的な確率も減少する。
各フレームワークの返されるコードには、1つのシンプルな要件がある:ユーザーが何回ページをリフレッシュしたかを、"Count is x"と表示することだ。物事をシンプルに保つために、今のところRedisキュー、Kubernetesコンポーネント、AWS Lambdasは使わないことにする。
各ユーザのセッションデータはPostgreSQLデータベースに保存されます。
そして、このデータベース・テーブルは各テストの前に切り捨てられる。
シンプルでありながら効果的というのがパフェラのモットーだ。
さて、いよいよ手を動かしてみましょう。Laravelのセットアップは省略します。 コマンドを実行するだけなので、Laravelのセットアップは省略する。
まず、.envファイルでデータベースの設定を行います。
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest
次に、すべてのリクエストをコントローラに送るフォールバックルートを1つ設定します。
Route::fallback(SessionController::class);
そして、カウントを表示するコントローラーを設定します。Laravelはデフォルトで、セッションをデータベースに保存します。また session()
セッションデータとインターフェースを持つ関数があるので、ページをレンダリングするのに数行のコードで済みました。
class SessionController extends Controller
{
public function __invoke(Request $request)
{
$count = session('count', 0);
$count += 1;
session(['count' => $count]);
return 'Count is ' . $count;
}
}
php-fpmとNginxをセットアップした後、私たちのページはかなり良く見えます...
╰─➤ 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
少なくとも、実際にテスト結果を見るまでは...。
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
いいえ、タイプミスではありません。私たちのテストマシンは、複雑なページをレンダリングする毎秒600リクエストから、"Count is 1"をレンダリングする毎秒21リクエストになった。
何がいけなかったのでしょうか?PHPのインストールに問題があるのでしょうか?Nginxがphp-fpmと連動する際に何らかの原因で速度が低下しているのでしょうか?
このページを純粋なPHPコードで作り直してみましょう。
<?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で4行のコード(と設定作業)で行っていたことを、98行のコードで行っています。(もちろん、適切なエラー処理とユーザー向けメッセージを行えば、これは約2倍の行数になるでしょう)。もしかしたら、毎秒30リクエストにできるかも?
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
おっと!どうやらPHPのインストールに問題はなさそうだ。純粋なPHPバージョンは毎秒700リクエストを実行している。
PHPに問題がないのなら、Laravelの設定を間違えたのでは?
コンフィギュレーションに関する問題やパフォーマンスに関するヒントを求めてウェブを探し回った結果、最も人気のある2つのテクニックは、コンフィギュレーションとルートデータをキャッシュして、リクエストごとに処理するのを避けるというものでした。そこで、彼らのアドバイスに従って、これらのヒントを試してみることにする。
╰─➤ php artisan config:cache
INFO Configuration cached successfully.
╰─➤ php artisan route:cache
INFO Routes cached successfully.
コマンドライン上ではすべて順調のようだ。ベンチマークをやり直してみよう。
╰─➤ 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
1秒あたりのリクエスト数は21.04から28.80になり、約37%も劇的に向上しました!これは、どんなソフトウェア・パッケージにとっても非常に印象的なことでしょう......ただし、まだ純粋なPHPバージョンの1/24のリクエスト数しかしていないという事実を除けば。
もしあなたが、このテストは何か間違っているに違いないと考えているなら、Lucinda PHPフレームワークの作者と話すべきです。彼のテスト結果では ララベルを叩くルシンダ HTMLリクエストでは36倍、JSONリクエストでは90倍である。
自分のマシンでApacheとNginxの両方でテストした結果、彼を疑う理由はない。Laravelは本当に その 遅い!PHP自体はそれほど悪くはないのですが、Laravelが各リクエストに追加するすべての余分な処理を加えると、2023年の選択肢としてLaravelを勧めるのは非常に難しくなります。
PHP/Wordpressアカウント ウェブ上の全ウェブサイトの約40%。 という枠組みが圧倒的に多い。個人的には、人気が必ずしも品質に結びつくとは限らない。 世界で最も人気のあるレストラン ...McDonald's.純粋なPHPコードはすでにテストしたので、Wordpressそのものをテストするつもりはない。
Django も長い間人気のあるフレームワークです。過去に Django を使ったことがあるなら、その壮大なデータベース管理インタフェー スと、すべてを思い通りに設定するのがいかに面倒だったかを懐かしく思い出すことでしょう。2023 年、特にバージョン 4.0 で追加された新しい ASGI インタフェースで、 Django がどれだけうまく動くか見てみましょう。
Django のセットアップは Laravel のセットアップと驚くほど似ています。退屈な設定は飛ばして、ビューの設定に入りましょう。
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}")
4行のコードはLaravelバージョンと同じです。それでは、パフォーマンスを見てみましょう。
╰─➤ 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
秒間355リクエストと悪くない。純粋な PHP 版の半分のパフォーマンスですが、Laravel 版の 12 倍です。Django vs. Laravel は全く勝負にならないようです。
キッチンシンクのような大規模なフレームワークとは別に、基本的なセットアップだけを行い、あとはあなたに任せる小規模なフレームワークもある。FlaskとそのASGI版であるQuartは、その中でも最も優れたフレームワークのひとつだ。私自身の PaferaPyフレームワーク はFlaskの上に構築されているので、パフォーマンスを維持しながら物事を成し遂げるのがいかに簡単かということはよく知っている。
#!/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}'
見ての通り、Flaskスクリプトは純粋なPHPスクリプトよりも短い。私がこれまで使ってきた言語の中で、Pythonはタイプされたキーストロークが最も表現豊かな言語だと思う。中括弧や括弧がないこと、リストやディクショナリーを理解できること、セミコロンではなくインデントでブロックできることなど、Pythonはシンプルでありながら強力な機能を備えている。
残念ながら、Pythonは、どれだけ多くのソフトウェアがPythonで書かれているにもかかわらず、最も遅い汎用言語でもある。利用可能なPythonライブラリの数は類似言語の約4倍で、膨大な領域をカバーしているが、NumPyのようなニッチな分野以外では、Pythonが高速で高性能だとは誰も言わないだろう。
Flaskのバージョンと以前のフレームワークとの比較を見てみよう。
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スクリプトは、純粋なPHPバージョンよりも実際に高速です!
もしこれに驚かれたのなら、Flaskアプリはgunicornサーバーを立ち上げるときにすべての初期化と設定を行うのに対して、PHPは新しいリクエストが来るたびにスクリプトを再実行することを理解してください。これは、Flaskが若くて熱心なタクシー運転手で、すでに車を発進させて道路のそばで待っているのに対して、PHPは家にいて電話がかかってくるのを待ち、それから車で迎えに行く年寄りの運転手であることと同じです。PHPがプレーンなHTMLファイルやSHTMLファイルを見事に変えていた時代からの古い人間であるため、時の流れを実感するのは少し悲しいが、設計の違いによって、PHPがPython、Java、Node.jsサーバーに対抗するのは本当に難しくなっている。
Flaskはこれまでで最も速いフレームワークかもしれないが、実はかなり古いソフトウェアだ。Pythonコミュニティは数年前に新しいasychronous ASGIサーバーに切り替えた。
パフェーラ・フレームワークの最新バージョン、 PaferaPyAsync はStarletteをベースにしている。QuartというASGIバージョンのFlaskもあるが、QuartとStarletteのパフォーマンスの違いは、私のコードをStarletteに置き換えるのに十分だった。
非同期プログラミングは多くの人にとって恐ろしいものだが、Node.jsが10年以上前にこの概念を広めたおかげで、実は難しい概念ではない。
かつては、マルチスレッド、マルチプロセシング、分散コンピューティング、プロミス・チェイニングなど、多くのベテラン・プログラマーを早老化させ、乾燥させるような楽しい時間を使って、並行処理と戦っていた。今は async
私たちの機能の前で await
を実行に時間がかかりそうなコードの前に置く。確かに通常のコードよりは冗長だが、同期プリミティブやメッセージ・パッシング、約束の解決に対処するよりは、はるかに煩わしくない。
スターレットのファイルはこんな感じ:
#!/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",
)
見ての通り、これはFlaskスクリプトからコピー&ペーストしたもので、ルーティングの変更と async/await
をキーワードにしている。
コピー&ペーストしたコードは本当にどれだけの改善をもたらすことができるのでしょうか?
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
皆さん、新しいチャンピオンが誕生しました!これまでの最高値は、純粋なPHPバージョンの704リクエスト/秒でしたが、それをFlaskバージョンの1080リクエスト/秒が上回りました。私たちのStarletteスクリプトは、毎秒4562リクエストと、以前のすべての競合を粉砕し、純粋なPHPの6倍、Flaskの4倍の改善を意味します。
まだWSGIのPythonコードをASGIに変更していないのであれば、今が始める良い機会かもしれません。
これまで、PHPとPythonのフレームワークのみを取り上げてきました。しかし、世界の大部分は実際にJava、DotNet、Node.js、Ruby on Railsなどの技術をウェブサイトに使っている。そのため、プログラミングが有機化学に相当することを避けるために、コードをタイプするのが最も簡単なフレームワークだけを選ぶことにする。
K'R CやKnuth'sのコピーの下に隠れていたのでなければ。 コンピューター・プログラミングの極意 この15年間、Node.jsのことを耳にしたことがあるだろう。しかし、JavaScript がブラウザだけでなくサーバでも侮れない存在になったことは否定できない。結局のところ、現在では64ビットの整数がネイティブで使えるようになっている!これは64ビットの浮動小数点数で保存されるものよりもはるかに優れている!
ExpressJSは、おそらく最も使いやすいNode.jsサーバーなので、カウンターを提供するために簡単で汚いNode.js/ExpressJSアプリを作成します。
/**********************************************************************
* 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}`));
アプリケーションの規模が大きくなると、ネイティブのJavaScriptはかなり扱いにくくなり、TypeScriptのようなこれを修正しようとする試みは、すぐにPythonよりも冗長になる。
さて、どうなるか!
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'のスピードについての昔話を聞いたことがあるかもしれない。しかしこの場合、我々のクイック・アプリはFlaskスクリプトを凌駕するものの、そのシングル・スレッドの性質は、"Ni!"と言うStarlette Knightが振り回す4つの非同期プロセスに負けてしまう。
もっと助けてもらおう!
╰─➤ 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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
オーケー!4対4の互角の戦いだ!ベンチマークをしよう!
╰─➤ 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
まだStarletteのレベルには達していないが、5分でできるJavaScriptハックとしては悪くない。私自身のテストによると、このスクリプトはデータベースのインターフェイスレベルで少し抑制されている。データベースドライバをsqliteに切り替えると、同じExpressJSコードで毎秒3000以上のリクエストが発生する。
注意すべき点は、Pythonの実行速度は遅いものの、ASGIフレームワークは、特定のワークロードではNode.jsソリューションと実際に競合できるということだ。
山というのは、ネズミと人間によって記録されたベンチマークの最高得点のことだ。
ウェブ上で公開されているフレームワークのベンチマークのほとんどを見ると、上位を占める傾向がある2つの言語があることに気づくだろう:C++とRustだ。私は90年代からC++を扱ってきたし、MFC/ATLが登場する前には自分のWin32 C++フレームワークも持っていた。だから代わりにRustバージョンを作ろうと思っているんだ。)
Rustはプログラミング言語としては比較的新しいものだが、リーナス・トーバルズがRustをLinuxカーネルのプログラミング言語として認めると発表したときから、私にとって興味津々の対象となった。私たち年配のプログラマーにとっては、この新しい時代のヒッピーが合衆国憲法の修正条項となることを発表したのと同じようなものだ。
経験豊富なプログラマーになると、若い人たちほど流行に飛びつかない傾向がある。(AngularJSの最初のバージョンを使った人なら、私が何を言っているかわかるだろう。)Rustはまだ実験的な開発段階であり、ウェブ上の多くのコード例が、現在のバージョンのパッケージではコンパイルすらできなくなっているのはおかしなことだ。
しかし、ラスト・アプリケーションが示すパフォーマンスは否定できない。もし一度も リップグレップ または fd-検索 大規模なソース・コード・ツリーで使用する場合は、ぜひ試してみてほしい。ほとんどのLinuxディストリビューションでは、パッケージ・マネージャーから簡単に入手できる。Rustを使えば、冗長性とパフォーマンスを交換できる。 ロット に対する冗長性の ロット パフォーマンスの
Rustのコード全体は少し大きいので、ここでは関連するハンドラーだけを見ておこう:
// =====================================================================
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
)))
}
これは、Python/Node.jsバージョンよりもはるかに複雑だ...。
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
そして、はるかにパフォーマンスが高い!
Actix/deadpool_postgresを使用した私たちのRustサーバーは、前回のチャンピオンStarletteに+125%、ExpressJSに+362%、そして純粋なPHPに+1366%の差をつけました。(Laravelバージョンとのパフォーマンスの差は、読者のための練習として残しておきます)。
Rust言語そのものを習得するのは、他の言語よりも難しいことに気づきました。Rust言語には、6502アセンブリ以外で目にしたどの言語よりも多くのゴッチがあるからです。だからこそ、Paferaフレームワークの次のバージョンは、Rustをベースにするのです。学習曲線はスクリプト言語よりもはるかに高いが、パフォーマンスはそれに見合うものになるだろう。Rustを学ぶ時間が取れないのであれば、技術スタックをStarletteやNode.jsをベースにするのも悪くない決断だ。
この20年間で、私たちは安価な静的ホスティングサイトから、LAMPスタックによる共有ホスティング、VPSのレンタル、そしてAWSやAzureなどのクラウドサービスへと移行してきた。便利なクラウドサービスの登場により、低速なサーバーやアプリケーションに多くのハードウェアを投入することが容易になったためだ。これは、長期的な技術的負債を代償に、短期的には大きな利益をもたらしている。
70年前、ソビエト連邦とアメリカの間で大きな宇宙開発競争があった。ソ連は初期のマイルストーンのほとんどを制した。スプートニクによる初の人工衛星、ライカによる初の宇宙犬、ルナ2号による初の月宇宙船、ユーリ・ガガーリンとバレンティーナ・テレシコワによる初の男女宇宙飛行士などなど...。
しかし、徐々に技術的負債が蓄積されていった。
ソビエトはこれらの成果をいずれも最初に達成したが、彼らのエンジニアリング・プロセスと目標は、長期的な実現可能性よりも短期的な課題に集中することを引き起こしていた。彼らは跳躍するたびに勝利を収めたが、対戦相手がゴールに向かって一貫した前進を続ける一方で、彼らはより疲れ、より遅くなっていった。
ニール・アームストロングがテレビの生中継で月面の歴史的な一歩を踏み出すと、アメリカは主導権を握り、ソ連の計画が頓挫する中、そこにとどまった。これは、次の大きなもの、次の大きな報酬、あるいは次の大きな技術に焦点を当てながら、長期的な視野に立った適切な習慣や戦略を身につけることに失敗している今日の企業と何ら変わりはない。
最初に市場に参入したからといって、その市場で支配的なプレーヤーになれるわけではない。また、時間をかけて物事を正しく行うことは、成功を保証するものではないが、長期的な業績の可能性を高めることは間違いない。もしあなたが会社の技術責任者なら、自分の仕事量に適した方向性とツールを選びなさい。人気取りがパフォーマンスや効率に取って代わることのないように。
Rust、ExpressJS、Flask、Starlette、Pure PHPスクリプトを含む7zファイルをダウンロードしたいですか?
著者について |
|
![]() |
Jim は 90 年代に IBM PS/2 を入手して以来、プログラミングを続けています。現在でも、彼は HTML と SQL を手作業で記述することを好み、仕事の効率性と正確性を重視しています。 |