Μετά από μια από τις πιο πρόσφατες συνεντεύξεις μου για δουλειά, με έκπληξη συνειδητοποίησα ότι η εταιρεία για την οποία έκανα αίτηση εξακολουθούσε να χρησιμοποιεί το Laravel, ένα πλαίσιο PHP που δοκίμασα πριν από περίπου μια δεκαετία. Ήταν αξιοπρεπές για την εποχή, αλλά αν υπάρχει μια σταθερά τόσο στην τεχνολογία όσο και στη μόδα, αυτή είναι η συνεχής αλλαγή και η επανεμφάνιση στυλ και εννοιών. Εάν είστε προγραμματιστής JavaScript, πιθανότατα γνωρίζετε αυτό το παλιό αστείο
Προγραμματιστής 1: "Δεν μου αρέσει αυτό το νέο πλαίσιο JavaScript!"
Προγραμματιστής 2: "Δεν χρειάζεται να ανησυχείτε. Απλά περιμένετε έξι μήνες και θα υπάρξει άλλος ένας για να το αντικαταστήσει!"
Από περιέργεια, αποφάσισα να δω τι ακριβώς συμβαίνει όταν δοκιμάζουμε παλιά και νέα. Φυσικά, ο ιστός είναι γεμάτος με σημεία αναφοράς και αξιώσεις, από τα οποία το πιο δημοφιλές είναι ίσως το TechEmpower Web Framework Benchmarks εδώ . Ωστόσο, δεν πρόκειται να κάνουμε τίποτα τόσο περίπλοκο όσο αυτοί σήμερα. Θα κρατήσουμε τα πράγματα ωραία και απλά, έτσι ώστε αυτό το άρθρο να μην μετατραπεί σε Πόλεμος και Ειρήνη , και ότι θα έχετε μια μικρή πιθανότητα να μείνετε ξύπνιοι μέχρι τη στιγμή που θα ολοκληρώσετε το διάβασμα. Ισχύουν οι συνήθεις προειδοποιήσεις: αυτό μπορεί να μην λειτουργεί το ίδιο στο μηχάνημά σας, διαφορετικές εκδόσεις λογισμικού μπορεί να επηρεάσουν την απόδοση και η γάτα του Schrödinger έγινε πραγματικά μια γάτα ζόμπι που ήταν μισή ζωντανή και μισή νεκρή την ίδια ακριβώς στιγμή.
Για αυτήν τη δοκιμή, θα χρησιμοποιήσω τον φορητό υπολογιστή μου οπλισμένο με ένα μικρό i5 που τρέχει Manjaro Linux όπως φαίνεται εδώ.
╰─➤ 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
Ο κώδικάς μας θα έχει τρεις απλές εργασίες για κάθε αίτημα:
Τι είδους ηλίθιο τεστ είναι αυτό, θα ρωτήσετε; Λοιπόν, αν κοιτάξετε τα αιτήματα δικτύου για αυτήν τη σελίδα, θα παρατηρήσετε ένα που ονομάζεται sessionvars.js που κάνει ακριβώς το ίδιο.
Βλέπετε, οι σύγχρονες ιστοσελίδες είναι περίπλοκα πλάσματα και μία από τις πιο συνηθισμένες εργασίες είναι η αποθήκευση σύνθετων σελίδων στην κρυφή μνήμη για να αποφευχθεί η υπερβολική φόρτωση στον διακομιστή της βάσης δεδομένων.
Εάν αποδίδουμε ξανά μια σύνθετη σελίδα κάθε φορά που τη ζητά ένας χρήστης, τότε μπορούμε να εξυπηρετήσουμε μόνο περίπου 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 να την πετάξει γρήγορα από το παράθυρο στον χρήστη, τότε μπορούμε να εξυπηρετήσουμε 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. Αυτό όχι μόνο μειώνει το φόρτο της βάσης δεδομένων και δημιουργεί μια καλύτερη εμπειρία για τους χρήστες μας, αλλά επίσης μειώνει τις κβαντικές πιθανότητες ο διακομιστής μας να εξατμιστεί αυθόρμητα σε παραβίαση του πυρήνα στημόνι κατά την επίθεση των Klingons.
Ο επιστρεφόμενος κώδικας για κάθε πλαίσιο θα έχει μια απλή απαίτηση: να δείχνει στον χρήστη πόσες φορές έχει ανανεώσει τη σελίδα λέγοντας "Το πλήθος είναι x". Για να κρατήσουμε τα πράγματα απλά, θα μείνουμε μακριά από τις ουρές Redis, τα στοιχεία Kubernetes ή το AWS Lambdas προς το παρόν.
Τα δεδομένα περιόδου σύνδεσης κάθε χρήστη θα αποθηκευτούν σε μια βάση δεδομένων PostgreSQL.
Και αυτός ο πίνακας βάσης δεδομένων θα περικόπτεται πριν από κάθε δοκιμή.
Απλό αλλά αποτελεσματικό είναι το μότο της Pafera... έξω από το πιο σκοτεινό χρονοδιάγραμμα πάντως...
Εντάξει, τώρα μπορούμε επιτέλους να αρχίσουμε να λερώνουμε τα χέρια μας. Θα παραλείψουμε τη ρύθμιση για το Laravel, καθώς είναι απλώς ένα σωρό συνθέτες και τεχνίτες εντολές.
Αρχικά, θα ρυθμίσουμε τις ρυθμίσεις της βάσης δεδομένων μας στο αρχείο .env
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=sessiontest
DB_USERNAME=sessiontest
DB_PASSWORD=sessiontest
Στη συνέχεια, θα ορίσουμε μια ενιαία εναλλακτική διαδρομή που στέλνει κάθε αίτημα στον ελεγκτή μας.
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 αιτήματα ανά δευτερόλεπτο για απόδοση μιας σύνθετης σελίδας... σε 21 αιτήματα ανά δευτερόλεπτο απόδοσης "Το πλήθος είναι 1".
Τι πήγε στραβά λοιπόν; Μήπως κάτι δεν πάει καλά με την εγκατάσταση της 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'];
Τώρα έχουμε χρησιμοποιήσει 98 γραμμές κώδικα για να κάνουμε ό,τι έκαναν τέσσερις γραμμές κώδικα (και μια ολόκληρη σειρά εργασιών διαμόρφωσης) στο Laravel. (Φυσικά, εάν κάναμε σωστό χειρισμό σφαλμάτων και μηνύματα που αντιμετωπίζουν οι χρήστες, θα ήταν περίπου διπλάσιος ο αριθμός των γραμμών.) Ίσως μπορούμε να φτάσουμε σε 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;
Μετά από σάρωση στον ιστό για ζητήματα διαμόρφωσης και συμβουλές απόδοσης, δύο από τις πιο δημοφιλείς τεχνικές ήταν η προσωρινή αποθήκευση των δεδομένων διαμόρφωσης και δρομολόγησης για να αποφευχθεί η επεξεργασία τους για κάθε αίτημα. Επομένως, θα λάβουμε τις συμβουλές τους και θα δοκιμάσουμε αυτές τις συμβουλές.
╰─➤ 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
Λοιπόν, τώρα έχουμε αυξήσει την απόδοση από 21,04 σε 28,80 αιτήματα ανά δευτερόλεπτο, μια δραματική αύξηση σχεδόν 37%! Αυτό θα ήταν πολύ εντυπωσιακό για οποιοδήποτε πακέτο λογισμικού... εκτός από το γεγονός ότι εξακολουθούμε να κάνουμε μόνο το 1/24 του αριθμού των αιτημάτων της καθαρής έκδοσης PHP.
Εάν πιστεύετε ότι κάτι δεν πάει καλά με αυτό το τεστ, θα πρέπει να μιλήσετε με τον συγγραφέα του πλαισίου της Lucinda PHP. Στα αποτελέσματα των δοκιμών του, έχει Η Λουσίντα κερδίζει τη Laravel κατά 36x για αιτήματα HTML και 90x για αιτήματα JSON.
Μετά από δοκιμή στο δικό μου μηχάνημα τόσο με το Apache όσο και με το Nginx, δεν έχω κανένα λόγο να τον αμφιβάλλω. Η Laravel είναι πραγματικά δίκαιη ότι αργός! Η PHP από μόνη της δεν είναι τόσο κακή, αλλά μόλις προσθέσετε όλη την επιπλέον επεξεργασία που προσθέτει η Laravel σε κάθε αίτημα, τότε δυσκολεύομαι πολύ να προτείνω το Laravel ως επιλογή το 2023.
Λογαριασμοί PHP/Wordpress για περίπου το 40% όλων των ιστότοπων στον ιστό , καθιστώντας το μακράν το πιο κυρίαρχο πλαίσιο. Προσωπικά, ωστόσο, θεωρώ ότι η δημοτικότητα δεν μεταφράζεται απαραίτητα σε ποιότητα περισσότερο από ό,τι βρίσκω τον εαυτό μου να έχω μια ξαφνική ανεξέλεγκτη επιθυμία για αυτό το εξαιρετικό γκουρμέ φαγητό από το πιο δημοφιλές εστιατόριο στον κόσμο ... McDonald's. Δεδομένου ότι έχουμε ήδη δοκιμάσει καθαρό κώδικα PHP, δεν πρόκειται να δοκιμάσουμε το ίδιο το Wordpress, καθώς οτιδήποτε αφορά το Wordpress θα ήταν αναμφίβολα χαμηλότερο από τα 700 αιτήματα ανά δευτερόλεπτο που παρατηρήσαμε με την καθαρή PHP.
Το Django είναι ένα άλλο δημοφιλές πλαίσιο που υπάρχει εδώ και πολύ καιρό. Εάν το έχετε χρησιμοποιήσει στο παρελθόν, πιθανότατα θυμάστε με χαρά τη θεαματική διεπαφή διαχείρισης της βάσης δεδομένων καθώς και το πόσο ενοχλητικό ήταν να ρυθμίσετε τα πάντα όπως ακριβώς θέλατε. Ας δούμε πόσο καλά λειτουργεί το Django το 2023, ειδικά με τη νέα διεπαφή ASGI που έχει προσθέσει από την έκδοση 4.0.
Η εγκατάσταση του Django είναι εντυπωσιακά παρόμοια με τη δημιουργία του Laravel, καθώς ήταν και τα δύο από την εποχή όπου οι αρχιτεκτονικές MVC ήταν κομψές και σωστές. Θα παραλείψουμε τη βαρετή διαμόρφωση και θα πάμε κατευθείαν στη ρύθμιση της προβολής.
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}")
Τέσσερις γραμμές κώδικα είναι ίδιες με την έκδοση 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, αλλά είναι επίσης 12 φορές μεγαλύτερη από την έκδοση Laravel. Django εναντίον 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 είναι ταχύτατη ή αποτελεσματική εκτός θέσεων όπως το NumPy.
Ας δούμε πώς συγκρίνεται η έκδοση 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 άλλαξε στους νεότερους ασύγχρονους διακομιστές ASGI πριν από δύο χρόνια, και φυσικά, εγώ ο ίδιος έχω αλλάξει μαζί τους.
Η πιο πρόσφατη έκδοση του Pafera Framework, PaferaPyAsync , βασίζεται στο Starlette. Αν και υπάρχει μια έκδοση ASGI του Flask που ονομάζεται Quart, οι διαφορές απόδοσης μεταξύ Quart και Starlette ήταν αρκετές για να επαναφέρω τον κώδικά μου στο Starlette.
Ο ασύγχρονος προγραμματισμός μπορεί να είναι τρομακτικός για πολλούς ανθρώπους, αλλά στην πραγματικότητα δεν είναι μια δύσκολη ιδέα χάρη στα παιδιά του Node.js που διαδόθηκαν την ιδέα πριν από μια δεκαετία.
Καταπολεμούσαμε την ταυτόχρονη χρήση πολλαπλών νημάτων, πολλαπλών επεξεργασιών, κατανεμημένων υπολογιστών, αλυσίδων υποσχέσεων και όλους εκείνους τους διασκεδαστικούς χρόνους που γήρωσαν πρόωρα και αφαίρεσαν πολλούς βετεράνους προγραμματιστές. Τώρα, απλώς πληκτρολογούμε async
μπροστά στις λειτουργίες μας και await
μπροστά από οποιονδήποτε κώδικα που μπορεί να χρειαστεί λίγος χρόνος για να εκτελεστεί. Είναι πράγματι πιο περιεκτικός από τον κανονικό κώδικα, αλλά πολύ λιγότερο ενοχλητικός στη χρήση από ό,τι πρέπει να ασχοληθείς με τα πρωτόγονα συγχρονισμού, τη μετάδοση μηνυμάτων και την επίλυση υποσχέσεων.
Το αρχείο Starlette μας μοιάζει με αυτό:
#!/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 αιτήματα ανά δευτερόλεπτο, που σημαίνει 6 φορές βελτίωση σε σχέση με την καθαρή PHP και 4 φορές βελτίωση σε σχέση με το Flask.
Εάν δεν έχετε αλλάξει ακόμα τον κώδικα WSGI Python σε ASGI, ίσως είναι η κατάλληλη στιγμή να ξεκινήσετε.
Μέχρι στιγμής, έχουμε καλύψει μόνο πλαίσια PHP και Python. Ωστόσο, ένα μεγάλο μέρος του κόσμου χρησιμοποιεί πραγματικά Java, DotNet, Node.js, Ruby on Rails και άλλες τέτοιες τεχνολογίες για τους ιστότοπούς του. Αυτή δεν είναι σε καμία περίπτωση μια ολοκληρωμένη επισκόπηση όλων των οικοσυστημάτων και των βιοϊωμάτων του κόσμου, επομένως για να αποφύγουμε να κάνουμε το ισοδύναμο προγραμματισμού της οργανικής χημείας, θα επιλέξουμε μόνο τα πλαίσια που είναι πιο εύκολο να πληκτρολογήσουμε κώδικα για.. που σίγουρα δεν είναι η Java.
Εκτός και αν έχετε κρυφτεί κάτω από το αντίγραφο του K&R C ή του Knuth's Η Τέχνη του Προγραμματισμού Υπολογιστών τα τελευταία δεκαπέντε χρόνια, πιθανότατα έχετε ακούσει για το Node.js. Όσοι από εμάς υπάρχουν από την αρχή της JavaScript είτε είμαστε απίστευτα φοβισμένοι, είτε έκπληκτοι είτε και τα δύο με την κατάσταση της σύγχρονης JavaScript, αλλά δεν μπορούμε να αρνηθούμε ότι η JavaScript έχει γίνει μια υπολογίσιμη δύναμη και στους διακομιστές ως προγράμματα περιήγησης. Μετά από όλα, έχουμε ακόμη και εγγενείς ακέραιους αριθμούς 64 bit τώρα στη γλώσσα! Αυτό είναι πολύ καλύτερο από ό,τι είναι αποθηκευμένο σε πλωτήρες 64 bit μακράν!
Το 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}`));
Αυτός ο κώδικας ήταν πραγματικά ευκολότερος να γραφτεί από τις εκδόσεις Python, αν και η εγγενής 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' ταχύτητα και αυτές οι ιστορίες είναι ως επί το πλείστον αληθινές χάρη στη θεαματική δουλειά που έχει κάνει η Google με τη μηχανή JavaScript V8. Σε αυτήν την περίπτωση, ωστόσο, αν και η γρήγορη εφαρμογή μας ξεπερνά το σενάριο Flask, η φύση της με ένα σπείρωμα νικιέται από τις τέσσερις ασύγχρονες διεργασίες που χρησιμοποιεί ο Ιππότης Starlette που λέει «Ni!».
Ας λάβουμε περισσότερη βοήθεια!
╰─➤ 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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Καλά! Τώρα είναι μια ζυγή μάχη τέσσερα στα τέσσερα! Ας κάνουμε σημείο αναφοράς!
╰─➤ 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, αλλά δεν είναι κακό για ένα γρήγορο πεντάλεπτο χακάρισμα JavaScript. Από τη δική μου δοκιμή, αυτό το σενάριο στην πραγματικότητα κρατιέται λίγο πίσω στο επίπεδο διεπαφής της βάσης δεδομένων επειδή το node-postgres δεν είναι τόσο αποτελεσματικό όσο το psycopg για την Python. Η μετάβαση σε sqlite ως πρόγραμμα οδήγησης βάσης δεδομένων αποδίδει πάνω από 3000 αιτήματα ανά δευτερόλεπτο για τον ίδιο κώδικα ExpressJS.
Το κύριο πράγμα που πρέπει να σημειωθεί είναι ότι παρά την αργή ταχύτητα εκτέλεσης της Python, τα πλαίσια ASGI μπορούν πραγματικά να είναι ανταγωνιστικά με τις λύσεις Node.js για συγκεκριμένους φόρτους εργασίας.
Τώρα, λοιπόν, πλησιάζουμε πιο κοντά στην κορυφή του βουνού, και με τον όρο βουνό, εννοώ τις υψηλότερες βαθμολογίες αναφοράς που έχουν καταγραφεί από ποντίκια και άνδρες.
Αν κοιτάξετε τα περισσότερα σημεία αναφοράς πλαισίου που είναι διαθέσιμα στον ιστό, θα παρατηρήσετε ότι υπάρχουν δύο γλώσσες που τείνουν να κυριαρχούν στην κορυφή: η C++ και η Rust. Έχω δουλέψει με τη C++ από τη δεκαετία του '90, και είχα ακόμη και το δικό μου πλαίσιο Win32 C++ πριν από το MFC/ATL, οπότε έχω μεγάλη εμπειρία με τη γλώσσα. Δεν είναι πολύ διασκεδαστικό να δουλεύεις με κάτι όταν το ξέρεις ήδη, επομένως θα κάνουμε μια έκδοση Rust. ;)
Το Rust είναι σχετικά νέο όσον αφορά τις γλώσσες προγραμματισμού, αλλά έγινε αντικείμενο περιέργειας για μένα όταν ο Linus Torvalds ανακοίνωσε ότι θα δεχόταν τη Rust ως γλώσσα προγραμματισμού πυρήνα Linux. Για εμάς τους παλαιότερους προγραμματιστές, αυτό είναι περίπου το ίδιο με το να λέμε ότι αυτός ο νέος χίπης της νέας εποχής θα ήταν μια νέα τροποποίηση στο Σύνταγμα των ΗΠΑ.
Τώρα, όταν είστε έμπειρος προγραμματιστής, τείνετε να μην πηδάτε τόσο γρήγορα όσο κάνουν οι νεότεροι, διαφορετικά μπορεί να καείτε από τις γρήγορες αλλαγές στη γλώσσα ή τις βιβλιοθήκες. (Όποιος χρησιμοποίησε την πρώτη έκδοση του AngularJS θα ξέρει για τι πράγμα μιλάω.) Το Rust βρίσκεται ακόμα κάπως σε αυτό το στάδιο πειραματικής ανάπτυξης και μου φαίνεται αστείο το γεγονός ότι πολλά παραδείγματα κώδικα στον Ιστό δεν είναι καν μεταγλώττιση πλέον με τις τρέχουσες εκδόσεις πακέτων.
Ωστόσο, η απόδοση που δείχνουν οι εφαρμογές Rust δεν μπορεί να αρνηθεί. Εάν δεν έχετε δοκιμάσει ποτέ ripgrep ή 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
Και πολύ πιο επιδόσεις!
Ο διακομιστής μας Rust που χρησιμοποιεί Actix/deadpool_postgres κερδίζει εύκολα τον προηγούμενο πρωταθλητή μας Starlette κατά +125%, το ExpressJS κατά +362% και την καθαρή PHP κατά +1366%. (Θα αφήσω το δέλτα απόδοσης με την έκδοση Laravel ως άσκηση για τον αναγνώστη.)
Διαπίστωσα ότι η εκμάθηση της ίδιας της γλώσσας Rust ήταν πιο δύσκολη από άλλες γλώσσες, καθώς έχει πολλά περισσότερα από οτιδήποτε έχω δει εκτός του 6502 Assembly, αλλά αν ο διακομιστής Rust μπορεί να αναλάβει 14 φορές τον αριθμό των χρήστες ως διακομιστής PHP σας, τότε ίσως υπάρχει κάτι που μπορεί να κερδίσετε με την εναλλαγή τεχνολογιών τελικά. Αυτός είναι ο λόγος για τον οποίο η επόμενη έκδοση του Pafera Framework θα βασίζεται στο Rust. Η καμπύλη εκμάθησης είναι πολύ υψηλότερη από τις γλώσσες σεναρίου, αλλά η απόδοση θα αξίζει τον κόπο. Εάν δεν μπορείτε να αφιερώσετε χρόνο για να μάθετε το Rust, τότε η βάση της στοίβας τεχνολογίας σας στο Starlette ή στο Node.js δεν είναι επίσης κακή απόφαση.
Τα τελευταία είκοσι χρόνια, περάσαμε από φθηνούς στατικούς ιστότοπους φιλοξενίας σε κοινόχρηστη φιλοξενία με στοίβες LAMP στην ενοικίαση VPS σε AWS, Azure και άλλες υπηρεσίες cloud. Σήμερα, πολλές εταιρείες είναι ικανοποιημένες με τη λήψη αποφάσεων σχεδιασμού με βάση όποιον μπορούν να βρουν ότι είναι διαθέσιμο ή φθηνότερο, καθώς η εμφάνιση των βολικών υπηρεσιών cloud έχει καταστήσει εύκολη τη διάθεση περισσότερου υλικού σε αργούς διακομιστές και εφαρμογές. Αυτό τους έδωσε μεγάλα βραχυπρόθεσμα κέρδη σε βάρος του μακροπρόθεσμου τεχνικού χρέους.
Πριν από 70 χρόνια, υπήρξε ένας μεγάλος διαστημικός αγώνας μεταξύ της Σοβιετικής Ένωσης και των Ηνωμένων Πολιτειών. Οι Σοβιετικοί κέρδισαν τα περισσότερα από τα πρώτα ορόσημα. Είχαν τον πρώτο δορυφόρο στο Σπούτνικ, τον πρώτο σκύλο στο διάστημα στη Λάικα, το πρώτο διαστημόπλοιο σελήνης στο Luna 2, τον πρώτο άνδρα και γυναίκα στο διάστημα στον Γιούρι Γκαγκάριν και τη Βαλεντίνα Τερέσκοβα και ούτω καθεξής...
Όμως σιγά σιγά συσσώρευαν τεχνικό χρέος.
Αν και οι Σοβιετικοί ήταν πρώτοι σε καθένα από αυτά τα επιτεύγματα, οι μηχανικές διαδικασίες και οι στόχοι τους έκαναν να επικεντρωθούν σε βραχυπρόθεσμες προκλήσεις και όχι σε μακροπρόθεσμη σκοπιμότητα. Κέρδιζαν κάθε φορά που πηδούσαν, αλλά γίνονταν πιο κουρασμένοι και πιο αργοί ενώ οι αντίπαλοί τους συνέχιζαν να κάνουν σταθερά βήματα προς τη γραμμή του τερματισμού.
Μόλις ο Νιλ Άρμστρονγκ έκανε τα ιστορικά του βήματα στο φεγγάρι σε ζωντανή τηλεόραση, οι Αμερικανοί πήραν το προβάδισμα και μετά έμειναν εκεί καθώς το σοβιετικό πρόγραμμα παραπαίει. Αυτό δεν διαφέρει από τις εταιρείες σήμερα που έχουν επικεντρωθεί στο επόμενο μεγάλο πράγμα, στην επόμενη μεγάλη ανταμοιβή ή στην επόμενη μεγάλη τεχνολογία, ενώ αποτυγχάνουν να αναπτύξουν τις κατάλληλες συνήθειες και στρατηγικές για μεγάλο χρονικό διάστημα.
Το να είσαι πρώτος στην αγορά δεν σημαίνει ότι θα γίνεις ο κυρίαρχος παίκτης σε αυτήν την αγορά. Εναλλακτικά, το να αφιερώνετε χρόνο για να κάνετε τα πράγματα σωστά δεν εγγυάται την επιτυχία, αλλά σίγουρα αυξάνει τις πιθανότητές σας για μακροπρόθεσμα επιτεύγματα. Εάν είστε ο ηγέτης τεχνολογίας για την εταιρεία σας, επιλέξτε τη σωστή κατεύθυνση και εργαλεία για τον φόρτο εργασίας σας. Μην αφήσετε τη δημοτικότητα να αντικαταστήσει την απόδοση και την αποδοτικότητα.
Θέλετε να κατεβάσετε ένα αρχείο 7z που περιέχει τα σενάρια Rust, ExpressJS, Flask, Starlette και Pure PHP;
Σχετικά με τον συγγραφέα |
|
![]() |
Ο Jim ασχολείται με τον προγραμματισμό από τότε που απέκτησε ένα IBM PS/2 στη δεκαετία του '90. Μέχρι σήμερα, εξακολουθεί να προτιμά να γράφει HTML και SQL με το χέρι και εστιάζει στην αποτελεσματικότητα και την ορθότητα στη δουλειά του. |