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();
'lottery' => [0, 100],
ต้องตั้งทั้งสองที่ ถ้าตั้งแค่ 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 ครับ






