ใช่แล้ว เวอร์จิเนีย มี *อยู่* ซานต้าคลอส ความแตกต่างระหว่างเฟรมเวิร์กเว็บในปี 2023

การเดินทางของโปรแกรมเมอร์ผู้ท้าทายคนหนึ่งในการค้นหาโค้ดเซิร์ฟเวอร์เว็บที่ทำงานรวดเร็วก่อนที่เขาจะยอมจำนนต่อแรงกดดันทางการตลาดและหนี้ทางเทคนิค
2023-03-24 11:52:06
👁️ 797
💬 0

เนื้อหา

  1. การแนะนำ
  2. การทดสอบ
  3. PHP/ลาราเวล
  4. PHP บริสุทธิ์
  5. การเยี่ยมชม Laravel อีกครั้ง
  6. จังโก้
  7. กระติกน้ำ
  8. สตาร์เลตต์
  9. Node.js/เอ็กซ์เพรสเจเอส
  10. รัสต์/แอ็กทิกซ์
  11. หนี้ทางเทคนิค
  12. ทรัพยากร

การแนะนำ

หลังจากการสัมภาษณ์งานครั้งล่าสุดครั้งหนึ่ง ฉันรู้สึกประหลาดใจเมื่อพบว่าบริษัทที่ฉันสมัครไปยังคงใช้ Laravel ซึ่งเป็นเฟรมเวิร์ก PHP ที่ฉันเคยลองใช้เมื่อประมาณทศวรรษที่แล้ว เฟรมเวิร์กนี้ถือว่าดีทีเดียวในสมัยนั้น แต่ถ้ามีสิ่งหนึ่งที่ไม่เปลี่ยนแปลงทั้งในด้านเทคโนโลยีและแฟชั่น นั่นก็คือการเปลี่ยนแปลงและการนำรูปแบบและแนวคิดใหม่ๆ มาใช้อย่างต่อเนื่อง หากคุณเป็นโปรแกรมเมอร์ JavaScript คุณคงคุ้นเคยกับเรื่องตลกเก่าๆ นี้

โปรแกรมเมอร์ 1: "ฉันไม่ชอบเฟรมเวิร์ก JavaScript ใหม่นี้!"

โปรแกรมเมอร์ 2: “ไม่ต้องกังวล แค่รอสัก 6 เดือน ก็มีตัวใหม่มาแทนที่แล้ว!”

ด้วยความอยากรู้อยากเห็น ฉันจึงตัดสินใจลองดูว่าจะเกิดอะไรขึ้นเมื่อเรานำสิ่งเก่าและสิ่งใหม่มาทดสอบ แน่นอนว่าเว็บไซต์เต็มไปด้วยเกณฑ์มาตรฐานและข้อเรียกร้อง ซึ่งข้อเรียกร้องที่ได้รับความนิยมมากที่สุดน่าจะเป็น เกณฑ์มาตรฐานกรอบงานเว็บ TechEmpower ที่นี่ . เราจะไม่ทำอะไรที่ซับซ้อนเท่ากับวันนี้ แต่เราจะทำทุกอย่างให้เรียบง่ายและดี เพื่อที่บทความนี้จะได้ไม่กลายเป็น... สงครามและสันติภาพ , และคุณมีโอกาสเล็กน้อยที่จะตื่นอยู่เมื่ออ่านจบ ข้อควรระวังทั่วไปคือ วิธีนี้อาจไม่ได้ผลกับเครื่องของคุณ เวอร์ชันซอฟต์แวร์ที่แตกต่างกันอาจส่งผลต่อประสิทธิภาพการทำงาน และแมวของชเรอดิงเงอร์ก็กลายเป็นแมวซอมบี้ที่ครึ่งชีวิตครึ่งตายในเวลาเดียวกัน

การทดสอบ

สภาพแวดล้อมการทดสอบ

สำหรับการทดสอบนี้ ฉันจะใช้แล็ปท็อปของฉันซึ่งมีโปรเซสเซอร์ 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
Shell

ภารกิจที่อยู่ตรงหน้า

โค้ดของเราจะมีสามงานง่าย ๆ สำหรับแต่ละคำขอ:

  1. อ่าน ID เซสชันของผู้ใช้ปัจจุบันจากคุกกี้
  2. โหลดข้อมูลเพิ่มเติมจากฐานข้อมูล
  3. ส่งคืนข้อมูลดังกล่าวให้กับผู้ใช้

คุณอาจสงสัยว่าการทดสอบแบบนั้นเป็นการทดสอบที่โง่เขลาประเภทไหนกันแน่? หากคุณลองดูคำขอเครือข่ายสำหรับหน้านี้ คุณจะสังเกตเห็นว่ามีไฟล์ชื่อ sessionvars.js ที่ทำงานแบบเดียวกัน

เนื้อหาของ 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
Shell

แต่หากเราแคชเพจนี้เป็นไฟล์ 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
Shell

index.en.html แบบคงที่เป็นส่วนที่ทุกคนเข้าถึงได้ และจะส่งเฉพาะส่วนที่แตกต่างกันตามผู้ใช้เท่านั้นใน sessionvars.js ซึ่งไม่เพียงแต่ช่วยลดภาระของฐานข้อมูลและสร้างประสบการณ์ที่ดีขึ้นให้กับผู้ใช้ของเราเท่านั้น แต่ยังช่วยลดความน่าจะเป็นเชิงควอนตัมที่เซิร์ฟเวอร์ของเราจะหายไปเองเมื่อเกิดการโจมตีจากกลุ่มคลิงออนอีกด้วย

ข้อกำหนดของรหัส

โค้ดที่ส่งคืนสำหรับแต่ละเฟรมเวิร์กจะมีข้อกำหนดง่ายๆ หนึ่งข้อ: แสดงให้ผู้ใช้เห็นว่าพวกเขารีเฟรชหน้ากี่ครั้งโดยพูดว่า "จำนวนเท่ากับ x" เพื่อให้ทุกอย่างง่ายขึ้น เราจะหลีกเลี่ยงคิว Redis, คอมโพเนนต์ Kubernetes หรือ AWS Lambdas ไปก่อน

แสดงจำนวนครั้งที่คุณเยี่ยมชมเพจ

ข้อมูลเซสชันของผู้ใช้แต่ละรายจะถูกบันทึกลงในฐานข้อมูล PostgreSQL

ตารางผู้ใช้เซสชัน

และตารางฐานข้อมูลนี้จะถูกตัดทอนก่อนการทดสอบแต่ละครั้ง

ตารางหลังจากถูกตัดทอน

เรียบง่ายแต่มีประสิทธิภาพคือคติประจำใจของ Pafera... อยู่นอกช่วงเวลาที่มืดมนที่สุดอยู่แล้ว...

ผลการทดสอบจริง

PHP/ลาราเวล

โอเค ตอนนี้เราก็เริ่มลงมือทำได้แล้ว เราจะข้ามขั้นตอนการตั้งค่าสำหรับ Laravel ไป เนื่องจากเป็นเพียงคำสั่ง composer และ artisan เท่านั้น

ก่อนอื่นเราจะตั้งค่าฐานข้อมูลของเราในไฟล์ .env

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

จากนั้นเราจะกำหนดเส้นทางสำรองเพียงเส้นทางเดียวที่ส่งคำขอทั้งหมดไปยังตัวควบคุมของเรา

Route::fallback(SessionController::class);
PHP

และตั้งค่าตัวควบคุมให้แสดงจำนวน 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

หลังจากตั้งค่า 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
Shell

อย่างน้อยจนกว่าเราจะได้เห็นผลการทดสอบจริงๆ...

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
Shell

ไม่ใช่ว่าพิมพ์ผิด เครื่องทดสอบของเราได้เพิ่มความเร็วจาก 600 คำขอต่อวินาทีในการเรนเดอร์หน้าที่ซับซ้อน... เป็น 21 คำขอต่อวินาทีในการเรนเดอร์ "จำนวนคือ 1"

มีอะไรผิดพลาดเกิดขึ้นหรือเปล่า? การติดตั้ง PHP ของเรามีปัญหาหรือไม่? Nginx ทำงานช้าลงเมื่อเชื่อมต่อกับ php-fpm หรือไม่?

PHP บริสุทธิ์

มาทำหน้านี้ใหม่ด้วยโค้ด 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'];

PHP

ตอนนี้เราได้ใช้โค้ด 98 บรรทัดในการทำสิ่งที่โค้ด 4 บรรทัด (และงานกำหนดค่ามากมาย) ใน 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
Shell

ว้าว! ดูเหมือนว่าการติดตั้ง PHP ของเราจะไม่มีปัญหาอะไรเลยนะ เวอร์ชัน PHP ล้วนๆ จะส่งคำขอได้ 700 ครั้งต่อวินาที

หากไม่มีอะไรผิดปกติกับ PHP อาจเป็นเพราะเราตั้งค่า Laravel ผิดก็ได้

การเยี่ยมชม Laravel อีกครั้ง

หลังจากค้นหาปัญหาเกี่ยวกับการกำหนดค่าและเคล็ดลับประสิทธิภาพบนเว็บแล้ว เทคนิคยอดนิยมสองประการคือการแคชข้อมูลการกำหนดค่าและกำหนดเส้นทางเพื่อหลีกเลี่ยงการประมวลผลข้อมูลสำหรับทุกคำขอ ดังนั้น เราจะนำคำแนะนำเหล่านั้นไปใช้และลองเคล็ดลับเหล่านี้

╰─➤  php artisan config:cache

   INFO  Configuration cached successfully.  

╰─➤  php artisan route:cache

   INFO  Routes cached successfully.  
Shell

ทุกอย่างดูดีบนบรรทัดคำสั่ง มาทำการประเมินประสิทธิภาพใหม่กัน

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

ตอนนี้เราได้เพิ่มประสิทธิภาพการทำงานจาก 21.04 เป็น 28.80 คำขอต่อวินาที ซึ่งเพิ่มขึ้นอย่างมากเกือบ 37%! ซึ่งถือว่าน่าประทับใจมากสำหรับแพ็คเกจซอฟต์แวร์ใดๆ ยกเว้นข้อเท็จจริงที่ว่าเรายังทำคำขอได้เพียง 1/24 ของจำนวนคำขอในเวอร์ชัน PHP ล้วนๆ

หากคุณคิดว่าต้องมีบางอย่างผิดปกติกับการทดสอบนี้ คุณควรพูดคุยกับผู้เขียนเฟรมเวิร์ก Lucinda PHP ในผลการทดสอบของเขา เขาได้ Lucinda เอาชนะ Laravel เพิ่มขึ้น 36 เท่าสำหรับคำขอ HTML และ 90 เท่าสำหรับคำขอ 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}")
Python

โค้ดสี่บรรทัดนั้นเหมือนกับเวอร์ชัน 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
Shell

ไม่เลวเลยที่ 355 คำขอต่อวินาที มีประสิทธิภาพเพียงครึ่งเดียวของเวอร์ชัน PHP ล้วนๆ แต่ยังดีกว่าเวอร์ชัน Laravel ถึง 12 เท่า ดูเหมือนว่า Django กับ Laravel จะไม่มีอะไรเทียบได้เลย

กระติกน้ำ

นอกเหนือจากกรอบขนาดใหญ่ทั้งหมดรวมถึงอ่างล้างจานแล้ว ยังมีกรอบขนาดเล็กอีกเช่นกันที่เพียงแค่ตั้งค่าพื้นฐานบางส่วนในขณะที่ให้คุณจัดการส่วนที่เหลือได้ หนึ่งในกรอบที่ดีที่สุดที่จะใช้คือ Flask และ Quart ซึ่งเป็น ASGI กรอบงาน 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}'
Python

อย่างที่คุณเห็น สคริปต์ 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
Python

สคริปต์ Flask ของเรานั้นเร็วกว่าเวอร์ชัน PHP ล้วนของเราจริงๆ!

หากคุณรู้สึกแปลกใจกับเรื่องนี้ คุณควรตระหนักว่าแอป Flask ของเราดำเนินการเริ่มต้นและกำหนดค่าทั้งหมดเมื่อเราเริ่มต้นเซิร์ฟเวอร์ gunicorn ในขณะที่ PHP จะดำเนินการสคริปต์ใหม่ทุกครั้งที่มีคำขอใหม่เข้ามา ซึ่งเทียบได้กับ Flask ที่เป็นคนขับแท็กซี่หนุ่มที่กระตือรือร้นซึ่งสตาร์ทรถแล้วและกำลังรออยู่ข้างถนน ในขณะที่ PHP เป็นคนขับรถรุ่นเก่าที่อยู่ที่บ้านรอสายเรียกเข้าและขับรถมารับคุณในภายหลัง ในฐานะคนรุ่นเก่าและมาจากยุคที่ PHP เป็นการเปลี่ยนแปลงที่ยอดเยี่ยมสำหรับไฟล์ HTML และ SHTML ธรรมดา จึงค่อนข้างน่าเศร้าที่ต้องตระหนักว่าเวลาผ่านไปนานแค่ไหน แต่ความแตกต่างในการออกแบบทำให้ PHP แข่งขันกับเซิร์ฟเวอร์ Python, Java และ Node.js ได้ยาก เนื่องจากเซิร์ฟเวอร์เหล่านี้ยังคงอยู่ในหน่วยความจำและจัดการคำขอด้วยความคล่องตัวอย่างนักเล่นกล

สตาร์เลตต์

Flask อาจเป็นเฟรมเวิร์กที่เร็วที่สุดของเราจนถึงตอนนี้ แต่จริงๆ แล้วมันเป็นซอฟต์แวร์ที่ค่อนข้างเก่า ชุมชน Python เปลี่ยนมาใช้เซิร์ฟเวอร์ 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",
)
Python

ตามที่คุณเห็น มันค่อนข้างจะคัดลอกและวางจากสคริปต์ 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
Shell

เรามีแชมป์คนใหม่แล้ว สุภาพสตรีและสุภาพบุรุษ! สถิติสูงสุดก่อนหน้านี้ของเราคือเวอร์ชัน PHP ล้วนที่ 704 คำขอต่อวินาที ซึ่งต่อมาเวอร์ชัน Flask ก็แซงหน้าไปโดยที่ 1080 คำขอต่อวินาที สคริปต์ Starlette ของเราเอาชนะคู่แข่งก่อนหน้าทั้งหมดด้วย 4562 คำขอต่อวินาที ซึ่งหมายถึงการปรับปรุง 6 เท่าจาก PHP ล้วนและ 4 เท่าจาก Flask

หากคุณยังไม่ได้เปลี่ยนโค้ด WSGI Python ให้เป็น ASGI ตอนนี้อาจเป็นเวลาที่ดีที่จะเริ่มต้น

Node.js/เอ็กซ์เพรสเจเอส

จนถึงตอนนี้ เราได้กล่าวถึงเฉพาะเฟรมเวิร์ก PHP และ Python เท่านั้น อย่างไรก็ตาม คนส่วนใหญ่ในโลกใช้ Java, DotNet, Node.js, Ruby on Rails และเทคโนโลยีอื่นๆ ที่คล้ายกันสำหรับเว็บไซต์ของตนเอง นี่ไม่ใช่ภาพรวมที่ครอบคลุมของระบบนิเวศและไบโอมทั้งหมดของโลก ดังนั้นเพื่อหลีกเลี่ยงการเขียนโปรแกรมที่เทียบเท่ากับเคมีอินทรีย์ เราจะเลือกเฉพาะเฟรมเวิร์กที่พิมพ์โค้ดได้ง่ายที่สุดเท่านั้น ซึ่ง Java ไม่ใช่แน่นอน

เว้นแต่คุณจะซ่อนตัวอยู่ใต้สำเนา K&R C หรือ Knuth ของคุณ ศิลปะแห่งการเขียนโปรแกรมคอมพิวเตอร์ ในช่วงสิบห้าปีที่ผ่านมา คุณคงเคยได้ยินเกี่ยวกับ 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

จริงๆ แล้วโค้ดนี้เขียนได้ง่ายกว่าเวอร์ชัน 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
Shell

คุณอาจเคยได้ยินนิทานพื้นบ้านโบราณ (เก่าแก่ตามมาตรฐานอินเทอร์เน็ตอยู่แล้ว...) เกี่ยวกับความเร็วของ 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 │
└────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Shell

โอเค ตอนนี้การต่อสู้แบบสี่ต่อสี่ก็สูสีแล้ว มาทดสอบประสิทธิภาพกัน!

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

แม้จะยังไม่ถึงระดับ Starlette แต่ก็ไม่เลวสำหรับการแฮ็ค JavaScript อย่างรวดเร็วภายใน 5 นาที จากการทดสอบของฉันเอง สคริปต์นี้ถูกจำกัดไว้เล็กน้อยในระดับอินเทอร์เฟซฐานข้อมูล เนื่องจาก node-postgres ไม่ได้มีประสิทธิภาพใกล้เคียงกับ psycopg สำหรับ Python การเปลี่ยนไปใช้ sqlite เป็นไดรเวอร์ฐานข้อมูลจะให้ผลลัพธ์มากกว่า 3,000 คำขอต่อวินาทีสำหรับโค้ด ExpressJS เดียวกัน

สิ่งสำคัญที่ต้องทราบคือแม้ความเร็วในการดำเนินการของ Python จะช้า แต่เฟรมเวิร์ก ASGI ก็ยังสามารถแข่งขันกับโซลูชัน Node.js ได้สำหรับเวิร์กโหลดบางประเภท

รัสต์/แอ็กทิกซ์

ตอนนี้ เรากำลังใกล้ถึงยอดเขาแล้ว โดยที่ภูเขาที่ฉันหมายถึงคือยอดคะแนนสูงสุดที่บันทึกไว้โดยทั้งหนูและมนุษย์

หากคุณลองดูเกณฑ์มาตรฐานเฟรมเวิร์กส่วนใหญ่ที่มีบนเว็บ คุณจะสังเกตเห็นว่ามีสองภาษาที่มักจะครองตลาดสูงสุด นั่นคือ C++ และ Rust ฉันทำงานกับ C++ มาตั้งแต่ยุค 90 และฉันมีเฟรมเวิร์ก Win32 C++ ของตัวเองก่อนที่ MFC/ATL จะเข้ามามีบทบาท ดังนั้นฉันจึงมีประสบการณ์กับภาษานี้มากพอสมควร การทำงานกับบางสิ่งบางอย่างเมื่อคุณรู้จักมันอยู่แล้วนั้นไม่สนุกเท่าไหร่ ดังนั้นเราจะทำเวอร์ชัน Rust แทน ;)

Rust ถือเป็นภาษาโปรแกรมที่ค่อนข้างใหม่ แต่สำหรับฉันแล้ว มันกลายเป็นสิ่งที่น่าสงสัยเมื่อ Linus Torvalds ประกาศว่าเขายอมรับ Rust เป็นภาษาโปรแกรมสำหรับเคอร์เนล Linux สำหรับโปรแกรมเมอร์รุ่นเก่าอย่างเรา นั่นก็เหมือนกับการบอกว่าสิ่งใหม่ในยุคฮิปปี้นี้จะกลายมาเป็นการแก้ไขรัฐธรรมนูญของสหรัฐอเมริกา

ในปัจจุบัน เมื่อคุณเป็นโปรแกรมเมอร์ที่มีประสบการณ์แล้ว คุณมักจะไม่รีบร้อนทำตามกระแสเหมือนกับคนรุ่นใหม่ๆ ไม่เช่นนั้น คุณอาจประสบปัญหาจากการเปลี่ยนแปลงอย่างรวดเร็วของภาษาหรือไลบรารี (ใครก็ตามที่เคยใช้ AngularJS เวอร์ชันแรกจะรู้ว่าฉันกำลังพูดถึงอะไร) Rust ยังคงอยู่ในระยะพัฒนาแบบทดลอง และผมพบว่าเป็นเรื่องตลกที่ตัวอย่างโค้ดจำนวนมากบนเว็บไม่สามารถคอมไพล์ด้วยแพ็คเกจเวอร์ชันปัจจุบันได้อีกต่อไป

อย่างไรก็ตาม ประสิทธิภาพที่แสดงโดยแอปพลิเคชัน 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
  )))
}
Rust

มันซับซ้อนกว่าเวอร์ชัน 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
Shell

และมีประสิทธิภาพมากกว่าเดิมมาก!

เซิร์ฟเวอร์ Rust ของเราซึ่งใช้ Actix/deadpool_postgres เอาชนะแชมป์เก่าอย่าง Starlette ได้อย่างง่ายดายด้วยคะแนน +125%, ExpressJS 362% และ PHP ล้วนๆ 1366% (ผมจะทิ้งคะแนนเดลต้าของประสิทธิภาพไว้กับเวอร์ชัน Laravel เพื่อเป็นการฝึกฝนสำหรับผู้อ่าน)

ฉันพบว่าการเรียนรู้ภาษา Rust นั้นยากกว่าภาษาอื่น ๆ มาก เนื่องจากมีปัญหาต่าง ๆ มากมายมากกว่าภาษาอื่น ๆ ที่ฉันเคยเห็นนอกเหนือจาก 6502 Assembly แต่ถ้าเซิร์ฟเวอร์ Rust ของคุณสามารถรองรับผู้ใช้ได้มากกว่าเซิร์ฟเวอร์ PHP ถึง 14 เท่า ก็อาจมีบางอย่างที่คุณจะได้รับจากการเปลี่ยนเทคโนโลยีก็ได้ นั่นคือเหตุผลที่ Pafera Framework เวอร์ชันถัดไปจะใช้ Rust เป็นพื้นฐาน การเรียนรู้จะเร็วกว่าภาษาสคริปต์มาก แต่ประสิทธิภาพก็คุ้มค่า หากคุณไม่สามารถสละเวลาเพื่อเรียนรู้ Rust ได้ การใช้ Starlette หรือ Node.js เป็นพื้นฐานสำหรับเทคโนโลยีของคุณก็ไม่ใช่การตัดสินใจที่แย่เช่นกัน

หนี้ทางเทคนิค

ในช่วงยี่สิบปีที่ผ่านมา เราได้เปลี่ยนจากเว็บไซต์โฮสติ้งแบบคงที่ราคาถูกมาเป็นโฮสติ้งแบบแชร์พร้อมสแต็ก LAMP ไปจนถึงการเช่า VPS ให้กับ AWS, Azure และบริการคลาวด์อื่นๆ ปัจจุบัน บริษัทต่างๆ จำนวนมากพอใจกับการตัดสินใจออกแบบโดยพิจารณาจากผู้ที่สามารถหาได้ซึ่งมีให้บริการหรือราคาถูกที่สุด เนื่องจากการถือกำเนิดของบริการคลาวด์ที่สะดวกสบายทำให้การเพิ่มฮาร์ดแวร์ให้กับเซิร์ฟเวอร์และแอปพลิเคชันที่ทำงานช้าเป็นเรื่องง่าย ซึ่งทำให้บริษัทเหล่านี้ได้รับผลกำไรในระยะสั้นจำนวนมากโดยแลกมากับหนี้ทางเทคนิคในระยะยาว

คำเตือนของศัลยแพทย์ทั่วไปแห่งแคลิฟอร์เนีย: นี่ไม่ใช่สุนัขอวกาศจริง

70 ปีที่แล้ว มีการแข่งขันทางอวกาศครั้งใหญ่ระหว่างสหภาพโซเวียตและสหรัฐอเมริกา โซเวียตเป็นฝ่ายชนะในช่วงแรกๆ เกือบทั้งหมด พวกเขามีดาวเทียมดวงแรกในสปุตนิก สุนัขตัวแรกในอวกาศในไลก้า ยานอวกาศลำแรกบนดวงจันทร์ในลูน่า 2 ผู้ชายและผู้หญิงคนแรกในอวกาศในยูริ กาการินและวาเลนตินา เทเรชโควา และอื่นๆ อีกมากมาย...

แต่พวกเขาค่อยๆ สะสมหนี้ทางเทคนิค

แม้ว่าโซเวียตจะเป็นฝ่ายได้เปรียบในความสำเร็จเหล่านี้ แต่กระบวนการทางวิศวกรรมและเป้าหมายของพวกเขากลับทำให้พวกเขามุ่งเน้นไปที่ความท้าทายในระยะสั้นมากกว่าความเป็นไปได้ในระยะยาว พวกเขาชนะทุกครั้งที่กระโดด แต่พวกเขาก็เหนื่อยและช้าลงในขณะที่คู่ต่อสู้ยังคงก้าวเดินอย่างต่อเนื่องเพื่อไปสู่เส้นชัย

เมื่อนีล อาร์มสตรองก้าวขึ้นสู่ดวงจันทร์ในประวัติศาสตร์ทางโทรทัศน์ถ่ายทอดสด ชาวอเมริกันก็ก้าวขึ้นมาเป็นผู้นำ และรักษาตำแหน่งผู้นำไว้ได้ในขณะที่รายการของสหภาพโซเวียตล้มเหลว ซึ่งไม่ต่างจากบริษัทต่างๆ ในปัจจุบันที่มุ่งเน้นไปที่สิ่งที่ยิ่งใหญ่ต่อไป ผลตอบแทนครั้งใหญ่ครั้งต่อไป หรือเทคโนโลยีที่ยิ่งใหญ่ต่อไป แต่ล้มเหลวในการพัฒนาพฤติกรรมและกลยุทธ์ที่เหมาะสมในระยะยาว

การเป็นผู้นำในตลาดไม่ได้หมายความว่าคุณจะกลายเป็นผู้เล่นที่มีอำนาจเหนือตลาดนั้น ในทางกลับกัน การใช้เวลาทำสิ่งต่างๆ อย่างถูกต้องไม่ได้รับประกันความสำเร็จ แต่จะเพิ่มโอกาสในการประสบความสำเร็จในระยะยาวอย่างแน่นอน หากคุณเป็นผู้นำด้านเทคนิคของบริษัท ให้เลือกแนวทางและเครื่องมือที่เหมาะสมกับปริมาณงานของคุณ อย่าปล่อยให้ความนิยมมาแทนที่ประสิทธิภาพและประสิทธิผล

ทรัพยากร

ต้องการดาวน์โหลดไฟล์ 7z ที่มีสคริปต์ Rust, ExpressJS, Flask, Starlette และ Pure PHP หรือไม่

เกี่ยวกับผู้เขียน

จิมเริ่มเขียนโปรแกรมมาตั้งแต่ที่เขาได้รับ IBM PS/2 เมื่อช่วงยุค 90 จนถึงทุกวันนี้ เขายังคงชอบเขียน HTML และ SQL ด้วยมือ และมุ่งเน้นที่ประสิทธิภาพและความถูกต้องในการทำงานของเขา