ჩემი ერთ-ერთი უახლესი სამუშაო ინტერვიუს შემდეგ, გამიკვირდა, რომ მივხვდი, რომ კომპანია, რომელზეც მე მივმართე, კვლავ იყენებდა Laravel-ს, PHP ჩარჩოს, რომელიც დაახლოებით ათი წლის წინ ვცადე. ეს იყო ღირსეული იმ დროისთვის, მაგრამ თუ არსებობს ერთი მუდმივი ტექნოლოგიაში და მოდაში, ეს არის სტილისა და კონცეფციების მუდმივი ცვლილება და ხელახალი გამოჩენა. თუ JavaScript პროგრამისტი ხართ, ალბათ იცნობთ ამ ძველ ხუმრობას
პროგრამისტი 1: "მე არ მომწონს ეს ახალი JavaScript ჩარჩო!"
პროგრამისტი 2: "არ არის საჭირო ინერვიულოთ. დაელოდე ექვს თვეს და კიდევ ერთი იქნება მის ჩანაცვლება!"
ცნობისმოყვარეობის გამო გადავწყვიტე მენახა ზუსტად რა ხდება, როცა ძველს და ახალს გამოცდას ვაძლევთ. რა თქმა უნდა, ვებ ივსება კრიტერიუმებითა და პრეტენზიებით, რომელთაგან ყველაზე პოპულარულია ალბათ TechEmpower Web Framework ბენჩმარკები აქ . თუმცა დღეს ჩვენ არ ვაპირებთ ისეთი რთული რამის გაკეთებას, როგორც მათ. ჩვენ ორივე ლამაზად და მარტივს შევინარჩუნებთ, რათა ეს სტატია არ იქცეს ომი და მშვიდობა , და რომ თქვენ გექნებათ მცირე შანსი, რომ ფხიზლად დარჩეთ იმ დროისთვის, როცა კითხვას დაასრულებთ. გამოიყენება ჩვეულებრივი გაფრთხილებები: ეს შეიძლება ერთნაირად არ იმუშაოს თქვენს აპარატზე, პროგრამული უზრუნველყოფის სხვადასხვა ვერსიამ შეიძლება გავლენა მოახდინოს შესრულებაზე და შრედინგერის კატა რეალურად გახდა ზომბი კატა, რომელიც ნახევრად ცოცხალი და ნახევრად მკვდარი იყო ზუსტად ამავე დროს.
ამ ტესტისთვის მე გამოვიყენებ ჩემს ლეპტოპს, რომელიც აღჭურვილია პატარა 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-ს მივცეთ საშუალება სწრაფად გადააგდოს იგი ფანჯრიდან მომხმარებლისთვის, მაშინ ჩვენ შეგვიძლია მოვემსახუროთ 32000 მომხმარებელს წამში, რაც გაზრდის ეფექტურობას 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-ში. ეს არა მხოლოდ ამცირებს მონაცემთა ბაზის დატვირთვას და ქმნის უკეთეს გამოცდილებას ჩვენი მომხმარებლებისთვის, არამედ ამცირებს კვანტურ ალბათობას, რომ ჩვენი სერვერი სპონტანურად ორთქლდება Warp core გარღვევის დროს კლინგონის თავდასხმის დროს.
თითოეული ფრეიმორისთვის დაბრუნებულ კოდს ექნება ერთი მარტივი მოთხოვნა: აჩვენეთ მომხმარებელს რამდენჯერ განაახლეს გვერდი თქვით „თვლა არის 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);
და დააყენეთ კონტროლერი, რომ აჩვენოს რაოდენობა. ლარაველი, ნაგულისხმევად, ინახავს სესიებს მონაცემთა ბაზაში. ის ასევე უზრუნველყოფს 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%-ით! ეს იქნება საკმაოდ შთამბეჭდავი ნებისმიერი პროგრამული პაკეტისთვის... გარდა იმისა, რომ ჩვენ კვლავ ვასრულებთ სუფთა PHP ვერსიის მოთხოვნების მხოლოდ 1/24-ს.
თუ ფიქრობთ, რომ რაღაც არასწორია ამ ტესტში, უნდა ესაუბროთ Lucinda PHP ჩარჩოს ავტორს. მისი ტესტის შედეგებში მას აქვს ლუსინდა ლარაველს სცემს 36x-ით HTML მოთხოვნებისთვის და 90x-ით JSON მოთხოვნებისთვის.
საკუთარ აპარატზე როგორც Apache-სთან, ასევე Nginx-თან ტესტირების შემდეგ, მასში ეჭვის შეტანის საფუძველი არ მაქვს. ლარაველი მართლაც სამართლიანია რომ ნელი! PHP თავისთავად არც ისე ცუდია, მაგრამ როგორც კი დაამატებთ დამატებით დამუშავებას, რომელსაც Laravel ამატებს თითოეულ მოთხოვნას, მაშინ ძალიან მიჭირს რეკომენდაცია Laravel-ის არჩევანს 2023 წელს.
PHP/Wordpress ანგარიშები ინტერნეტში არსებული ყველა ვებსაიტის დაახლოებით 40%. , რაც მას ყველაზე დომინანტურ ჩარჩოდ აქცევს. თუმცა, პირადად მე ვხვდები, რომ პოპულარობა სულაც არ ითარგმნება ხარისხში, ვიდრე მე მაქვს მოულოდნელი უკონტროლო სურვილი ამ არაჩვეულებრივი გურმანული საკვებისადმი. ყველაზე პოპულარული რესტორანი მსოფლიოში ... მაკდონალდსი. მას შემდეგ, რაც ჩვენ უკვე გამოვცადეთ სუფთა 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-ის ვერსიას. ჯანგო ლარაველის წინააღმდეგ, როგორც ჩანს, საერთოდ არ არის კონკურსი.
გარდა ყველაფრისა, მათ შორის, სამზარეულოს ნიჟარის ჩარჩოებისა, ასევე არის უფრო მცირე ჩარჩოები, რომლებიც უბრალოდ აკეთებენ ძირითად დაყენებას, ხოლო დანარჩენს საშუალებას გაძლევთ გაუმკლავდეთ. ერთ-ერთი საუკეთესო გამოსაყენებელია Flask და მისი ASGI კოლეგა Quart. ჩემი საკუთარი PaferaPy Framework აშენებულია 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 არის სწრაფი და მოქმედი ნიშების გარეთ, როგორიცაა 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 შეიძლება იყოს ჩვენი ყველაზე სწრაფი ფრეიმერი ჯერჯერობით, მაგრამ ის რეალურად საკმაოდ ძველი პროგრამული უზრუნველყოფაა. პითონის საზოგადოებამ გადაერთო ახალ ასიქრონულ ASGI სერვერებზე რამდენიმე წლის წინ და, რა თქმა უნდა, მეც გადავედი მათთან ერთად.
Pafera Framework-ის უახლესი ვერსია, PaferaPyAsync , დაფუძნებულია Starlette-ზე. მიუხედავად იმისა, რომ არსებობს Flask-ის ASGI ვერსია, სახელწოდებით 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 და სხვა ასეთ ტექნოლოგიებს მათი ვებსაიტებისთვის. ეს არავითარ შემთხვევაში არ არის მსოფლიოს ყველა ეკოსისტემისა და ბიომის ყოვლისმომცველი მიმოხილვა, ამიტომ ორგანული ქიმიის პროგრამირების ეკვივალენტის თავიდან ასაცილებლად, ჩვენ ავირჩევთ მხოლოდ იმ ჩარჩოებს, რომელთათვისაც ყველაზე ადვილია კოდის აკრეფა. რომელი ჯავა ნამდვილად არ არის.
თუ არ იმალებოდით K&R C-ის ან Knuth's-ის თქვენი ასლის ქვეშ კომპიუტერული პროგრამირების ხელოვნება ბოლო თხუთმეტი წლის განმავლობაში, თქვენ ალბათ გსმენიათ Node.js-ის შესახებ. ჩვენ, ვინც JavaScript-ის დაწყების დღიდან ვიმყოფებით, ან წარმოუდგენლად შეშინებული, გაოცებული ან ორივე ერთად ვართ თანამედროვე JavaScript-ის მდგომარეობით, მაგრამ არ შეიძლება უარვყოთ, რომ 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' სიჩქარე, და ეს ისტორიები ძირითადად ჭეშმარიტია იმ სანახაობრივი მუშაობის წყალობით, რომელიც Google-მა გააკეთა V8 JavaScript ძრავით. თუმცა, ამ შემთხვევაში, მიუხედავად იმისა, რომ ჩვენი სწრაფი აპლიკაცია აჯობებს Flask სკრიპტს, მისი ერთი ხრახნიანი ბუნება დამარცხებულია ოთხი ასინქრონული პროცესით, რომელსაც ახორციელებს Starlette Knight, რომელიც ამბობს „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 შედარებით ახალია პროგრამირების ენების თვალსაზრისით, მაგრამ ის ჩემთვის ცნობისმოყვარეობის ობიექტი გახდა, როდესაც ლინუს ტორვალდსმა გამოაცხადა, რომ ის მიიღებდა Rust-ს, როგორც Linux ბირთვის პროგრამირების ენას. ჩვენთვის ძველი პროგრამისტებისთვის, ეს დაახლოებით იგივეა, რაც იმის თქმა, რომ ეს ახალი დახვეწილი ახალი ეპოქის ჰიპი იყო აშშ-ს კონსტიტუციის ახალი შესწორება.
ახლა, როცა გამოცდილი პროგრამისტი ხარ, არ ახტები ბანდას ისე სწრაფად, როგორც ამას ახალგაზრდები აკეთებენ, წინააღმდეგ შემთხვევაში შეიძლება დაიწვა ენის ან ბიბლიოთეკების სწრაფი ცვლილებებით. (ვინც გამოიყენა AngularJS-ის პირველი ვერსია, გაიგებს, რაზეც ვსაუბრობ.) Rust ჯერ კიდევ გარკვეულწილად იმ ექსპერიმენტული განვითარების ეტაპზეა და მე სასაცილოდ მიმაჩნია, რომ ბევრი კოდის მაგალითი ინტერნეტში არც კი არის. შედგენა აღარ არის პაკეტების მიმდინარე ვერსიებით.
თუმცა, Rust აპლიკაციების მიერ ნაჩვენები შესრულება არ შეიძლება უარყო. თუ არასდროს გიცდიათ რიპგრეპი ან ფდ-იპოვე დიდი წყაროს კოდის ხეებზე, თქვენ აუცილებლად უნდა მისცეთ მათ დატრიალება. ისინი ხელმისაწვდომია 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 ასამბლეის გარეთ, მაგრამ თუ თქვენს Rust სერვერს შეუძლია მიიღოს 14-ჯერ მეტი მომხმარებლები, როგორც თქვენი PHP სერვერი, მაშინ შესაძლოა, ბოლოს და ბოლოს, არსებობს რაღაცის მოპოვება გადართვის ტექნოლოგიებით. სწორედ ამიტომ, Pafera Framework-ის შემდეგი ვერსია დაფუძნებული იქნება Rust-ზე. სწავლის მრუდი გაცილებით მაღალია, ვიდრე სკრიპტის ენები, მაგრამ შესრულება ღირს. თუ თქვენ არ შეგიძლიათ დრო დაუთმოთ Rust-ის სწავლას, მაშინ თქვენი ტექნიკური სტეკის დაფუძნება Starlette-ზე ან Node.js-ზე არც ისე ცუდი გადაწყვეტილებაა.
ბოლო ოცი წლის განმავლობაში, ჩვენ გადავედით იაფი სტატიკური ჰოსტინგის საიტებიდან საერთო ჰოსტინგზე LAMP სტეკებით VPS-ების დაქირავებამდე AWS, Azure და სხვა ღრუბლოვან სერვისებზე. დღესდღეობით, ბევრი კომპანია კმაყოფილია დიზაინის გადაწყვეტილების მიღებით, იმის მიხედვით, თუ ვისი პოვნაა ხელმისაწვდომი ან იაფი, ვინაიდან მოსახერხებელი ღრუბლოვანი სერვისების გამოჩენამ გააადვილა მეტი ტექნიკის გადაგდება ნელი სერვერებისა და აპლიკაციებისთვის. ამან მათ დიდი მოკლევადიანი მოგება მისცა გრძელვადიანი ტექნიკური დავალიანების ფასად.
70 წლის წინ საბჭოთა კავშირსა და შეერთებულ შტატებს შორის დიდი კოსმოსური რბოლა გაიმართა. საბჭოელებმა მოიგეს ადრეული ეტაპების უმეტესი ნაწილი. მათ ჰყავდათ პირველი თანამგზავრი Sputnik-ში, პირველი ძაღლი კოსმოსში ლაიკაში, პირველი მთვარის კოსმოსური ხომალდი ლუნა 2-ში, პირველი კაცი და ქალი კოსმოსში იური გაგარინსა და ვალენტინა ტერეშკოვაში და ა.შ.
მაგრამ ნელ-ნელა აგროვებდნენ ტექნიკურ ვალს.
მიუხედავად იმისა, რომ საბჭოთა კავშირი პირველი იყო თითოეულ ამ მიღწევაში, მათი საინჟინრო პროცესები და მიზნები იწვევდა მათ ფოკუსირებას მოკლევადიან გამოწვევებზე და არა გრძელვადიან მიზანშეწონილობაზე. ისინი იმარჯვებდნენ ყოველ ჯერზე, როდესაც ხტუნავდნენ, მაგრამ ისინი უფრო დაღლილები და ნელა ხდებოდნენ, ხოლო მათი ოპონენტები განაგრძობდნენ თანმიმდევრული ნაბიჯების გადადგმას ფინიშის ხაზისკენ.
მას შემდეგ, რაც ნილ არმსტრონგმა გადადგა თავისი ისტორიული ნაბიჯები მთვარეზე პირდაპირ ეთერში, ამერიკელებმა აიღეს ლიდერობა, შემდეგ კი იქ დარჩნენ, რადგან საბჭოთა პროგრამა შეფერხდა. ეს არაფრით განსხვავდება დღევანდელი კომპანიებისგან, რომლებიც ფოკუსირებულნი არიან მომავალ დიდ საქმეზე, მომავალ დიდ ანაზღაურებაზე ან შემდეგ დიდ ტექნოლოგიაზე, ხოლო გრძელვადიან პერსპექტივაში სათანადო ჩვევებისა და სტრატეგიების შემუშავებას ვერ ახერხებენ.
ბაზარზე პირველობა არ ნიშნავს, რომ ამ ბაზარზე დომინანტური მოთამაშე გახდებით. ალტერნატიულად, დროის დახარჯვა საქმის სწორად გასაკეთებლად არ იძლევა წარმატების გარანტიას, მაგრამ რა თქმა უნდა ზრდის თქვენს შანსებს გრძელვადიანი მიღწევებისთვის. თუ თქვენ ხართ თქვენი კომპანიის ტექნიკური ლიდერი, აირჩიეთ სწორი მიმართულება და ინსტრუმენტები თქვენი სამუშაო დატვირთვისთვის. ნუ მისცემთ უფლებას პოპულარობას შეცვალოს შესრულება და ეფექტურობა.
გსურთ ჩამოტვირთოთ 7z ფაილი, რომელიც შეიცავს Rust, ExpressJS, Flask, Starlette და Pure PHP სკრიპტებს?
ავტორის შესახებ |
|
![]() |
ჯიმი პროგრამირებას აგრძელებს მას შემდეგ, რაც მან მიიღო IBM PS/2 90-იან წლებში. დღემდე ურჩევნია HTML-ისა და SQL-ის ხელით წერა და სამუშაოს ეფექტურობასა და სისწორეზე აკეთებს აქცენტს. |