Laravel 13.39 มีอะไรใหม่ ตั้งเวลาล้าง session ที่หมดอายุ และ model ที่ไม่มี primary key

Laravel 13.39 เพิ่มคำสั่ง session:garbage-collect ให้ scheduler ล้าง session ที่หมดอายุแทนการสุ่มทำใน request ของผู้ใช้ ประกาศ model ที่ไม่มี primary key ได้ด้วย #[Table(key: false)] ให้ JobAttempted บอกเวลาที่ job ใช้ และเขียนไฟล์ config:cache แบบไม่มีจังหวะที่ไฟล์ว่าง

เผยแพร่
เวลาอ่าน
4 นาที
สรุปจาก
Paul Redmond · laravel-news.com
ในโพสต์นี้ 6 หัวข้อ
  1. Laravel ล้าง session เก่าด้วยการสุ่มใน request ของผู้ใช้
  2. ให้ scheduler ล้าง session แทน
  3. ประกาศ model ที่ไม่มี primary key ด้วย #[Table(key: false)]
  4. JobAttempted บอกเวลาที่ job ใช้ แม้ job จะล้มเหลว
  5. config:cache เขียนไฟล์ใหม่ทั้งไฟล์แล้วค่อยสลับ
  6. ของที่แก้เพิ่มใน 13.39

Laravel ล้าง session เก่าด้วยการสุ่มใน request ของผู้ใช้

session ที่ผู้ใช้ทิ้งไว้ไม่ได้หายไปเอง ถ้าเก็บในตาราง sessions (ค่าเริ่มต้นตั้งแต่ Laravel 11) หรือเป็นไฟล์ใน storage/framework/sessions ต้องมีคนลบแถวหรือไฟล์ที่หมดอายุทิ้ง ส่วน Redis กับ cache driver อื่นตั้งอายุให้แต่ละ key อยู่แล้ว และ session แบบ cookie ไม่มีอะไรให้ลบ

ที่ผ่านมา Laravel ลบให้ด้วยการจับฉลาก ค่า lottery ใน config/session.php ตั้งไว้ [2, 100] แปลว่าทุก request มีโอกาส 2 ใน 100 ที่จะต้องลบ session ที่เก่ากว่า session.lifetime ทั้งหมดก่อนตอบผู้ใช้

เว็บเล็กแทบไม่รู้สึก แต่เว็บที่ตาราง sessions มีเป็นแสนแถวจะเจอเรื่องแบบนี้

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

ให้ scheduler ล้าง session แทน

Andrew Brown เพิ่มคำสั่ง session:garbage-collect ใน PR #62104 คำสั่งนี้เรียกตัวล้างของ session driver ที่ใช้อยู่ ใช้ session.lifetime ตัดสินว่า session ไหนหมดอายุ เหมือนที่การจับฉลากทำ แค่ย้ายมาทำนอก request

ตั้งให้ scheduler รันทุกชั่วโมง แล้วปิดการจับฉลากด้วย [0, 100]

use Illuminate\Support\Facades\Schedule;

Schedule::command('session:garbage-collect')->hourly();

ต้องตั้งทั้งสองที่ ถ้าตั้งแค่ scheduler การจับฉลากก็ยังทำงานต่อในทุก request ส่วนถ้าปิดการจับฉลากแต่ลืมตั้ง scheduler ตาราง sessions จะโตไปเรื่อย ๆ และเซิร์ฟเวอร์ต้องมี cron เรียก schedule:run ทุกนาทีอยู่แล้วด้วย เหมือนงานตั้งเวลาอื่น ๆ (เราเก็บทุก schedule ไว้ใน routes/console.php ตามที่เล่าไว้ในค่าที่ควรตั้งตั้งแต่วันแรกของโปรเจกต์)

ประกาศ model ที่ไม่มี primary key ด้วย #[Table(key: false)]

บางตารางไม่มีคอลัมน์ id อย่างตาราง log ที่เขียนอย่างเดียว ตารางที่ดึงมาจากระบบอื่น หรือ view ในฐานข้อมูล เดิมเราประกาศ model แบบนี้ด้วย protected $primaryKey = null; แต่ attribute #[Table] ที่มาใน Laravel 13 ยังบอกเรื่องนี้ไม่ได้ ใส่ key: null ก็เท่ากับไม่ใส่ Laravel จึงกลับไปใช้ id ตามเดิม แล้ว save() กับ delete() ก็หาแถวไม่เจอโดยไม่ฟ้องอะไร

PR #62113 ของ Caleb White ให้ key: false แปลว่าไม่มี primary key

// เดิม
#[Table(name: 'audit_logs')]
class AuditLog extends Model
{
    protected $primaryKey = null;
}

// 13.39
#[Table(name: 'audit_logs', key: false)]
class AuditLog extends Model {}

PR #62205 ตามมาแก้ให้ model แบบนี้ไม่นับเป็น auto increment ด้วย create() จึงไม่โยน exception หลัง insert อีก ใครที่ใช้ Rector ย้าย property ไปเป็น attribute ตอนนี้ model ที่ไม่มี primary key ก็ย้ายตามได้แล้ว

JobAttempted บอกเวลาที่ job ใช้ แม้ job จะล้มเหลว

เดิมมีแค่ event JobProcessed ที่มีเวลาที่ job ใช้ ซึ่งเกิดเฉพาะตอน job สำเร็จ job ที่ล้มเหลวหรือโดน release กลับเข้า queue จึงไม่มีตัวเลขให้ดู ทั้งที่ job ที่ช้าจนเกิน timeout มักเป็นตัวที่เราอยากรู้เวลามากที่สุด

Jack Bayliss เพิ่ม property duration เป็นมิลลิวินาทีให้ JobAttempted (PR #62118) ซึ่งเกิดทุกครั้งที่ job รันจบ ไม่ว่าผลจะเป็นอะไร และ job ที่สั่งด้วย dispatchSync() ก็มีเวลาทั้งสอง event แล้ว (PR #62155) ถ้าเราเก็บสถิติ job ไว้ดูเอง ย้ายไปฟัง JobAttempted ตัวเดียวก็ได้ตัวเลขครบทุก job

config:cache เขียนไฟล์ใหม่ทั้งไฟล์แล้วค่อยสลับ

เรื่องนี้เล็กแต่สำคัญกับคนที่ deploy เอง เดิม config:cache, route:cache และ event:cache เขียนทับไฟล์ใน bootstrap/cache ตรง ๆ ไฟล์จะว่างก่อนแล้วค่อยมีเนื้อหา request ที่เข้ามาในจังหวะนั้นจึงอ่านได้ไฟล์ว่าง

ถ้าเซิร์ฟเวอร์ตั้ง opcache.validate_timestamps=0 อย่างที่แนะนำไว้ในโพสต์ OPcache เรื่องจะหนักกว่านั้น OPcache เก็บไฟล์ว่างนั้นไว้ แล้วทุก request ก็พังต่อไปจนกว่าจะ reload PHP-FPM

PR #62117 ให้ทั้งสามคำสั่งเขียนลงไฟล์ชั่วคราวก่อน แล้ว rename ทับไฟล์เดิมทีเดียว คนที่อ่านจึงเห็นแค่ไฟล์เก่าทั้งไฟล์หรือไฟล์ใหม่ทั้งไฟล์ ไฟล์ services.php, packages.php และ view ที่ compile แล้วใช้วิธีนี้มาก่อนแล้ว รุ่นนี้ทำให้ครบ

แม้จะอัปเดตแล้ว ลำดับ deploy ก็ยังสำคัญอยู่ดี reload PHP-FPM หลัง cache config เสร็จ ตามที่เขียนไว้ในลำดับขั้นตอน deploy

ของที่แก้เพิ่มใน 13.39

  • Rule::array() มี min(), max(), between() และ exactly() เหมือน Rule::string() ไม่ต้องใส่ 'min:1' เป็น string ต่อท้ายอีก (PR #62179) ต่อจากที่ 13.36 เพิ่ม Rule:: ให้ครบทุกกฎ
  • assertSeeText('0') ตรวจจริงแล้ว เดิมผ่านทุกครั้งโดยไม่ได้ดู response เลย test ที่เช็คตัวเลขศูนย์บนหน้าอาจแดงหลังอัปเดต (PR #62126)
  • Mail::html() กับ Mail::plain() ไม่หลุดเป็นอีเมลจริงตอนใช้ Mail::fake() เดิมสองเมธอดนี้ข้าม fake ไปส่งจริงใน test (PR #62162)
  • forceCreate() บน BelongsToMany ผูก pivot ให้ด้วย เดิม model ถูกสร้างแต่ไม่ได้ผูกกับ parent (PR #62136) และ restoreOrCreate() กับ createOrRestore() บน relationship ใส่ foreign key ให้ถูกแล้ว (PR #62146)
  • Http::batch() ไม่นับ request ที่ retry() หรือ throw() แล้วล้มเหลวว่าสำเร็จ
  • Filesystem::exists() กับ isFile() ไม่รับ path แบบ phar:// ปิดทางที่ path จากผู้ใช้จะไปเปิดไฟล์ phar

รายการ PR ทั้งหมดราว 60 ตัวอยู่ในโพสต์ต้นฉบับของ Laravel News ครับ

จบซีรีส์ Laravel 13 มีอะไรใหม่ แล้ว

ดูทั้ง 4 ตอน →

อ่านต่อ

ล่าสุด

ดูทั้งหมด →