วันพฤหัสบดีที่ 13 กันยายน พ.ศ. 2550

drupal+debian กับปัญหาตาราง sessions โตขึ้นเรื่อยๆ

ที่เว็บไซต์ opentle.org ใช้ drupal กับ community ขนาดใหญ่พอสมควร ส่วน OS เปลี่ยนจากเดิมใช้ FreeBSD 6.2 มาเป็น debian 4.0 (etch) ได้สัก 9 เดือนเศษละ มีข้อสังเกตว่า ตั้งแต่เปลี่ยนเป็น debian ตาราง sessions ใหญ่ขึ้นเรื่อยๆ จนเป็นนับล้านเรคอร์ดในเวลาไม่กี่เดือน ทั้งๆ ที่ session.gc_maxlifetime ก็ตั้งไว้ราวๆ 24 วัน ต้องคอยล้างทิ้งเป็นระยะๆ ทีแรกนึกไม่ออกว่าเกิดจากอะไร ดูที่เว็บไซต์อื่นๆ ก็มีทั้งเป็นบ้าง ไม่เป็นบ้าง แต่ส่วนใหญ่ไม่รู้ว่ามีปัญหานี้ เพราะ session มีน้อย จนไม่รู้สึกอะไร ขณะที่ opentle นั้น แค่เดือนเดียวก็เห็นได้ชัดว่าตารางมันใหญ่ขึ้น

เดิมที่ไล่แกะดูการทำงานของตารางนี้คือ ปกติ php จะจัดการ session ด้วยตัวเอง ซึ่งจะสร้างเป็นแฟ้ม session ไว้ใน /var/lib/php5/ แต่ใน drupal จะจัดการ session ด้วยฐานข้อมูล คือเก็บลงตาราง sessions แล้วเขียน function มา handle ซึ่งดูรายละเอียดฟังก์ชันเหล่านี้ได้จาก includes/session.inc และกำหนดให้ใช้ฟังก์ชันเหล่านี้ในการจัดการ session ใน includes/bootstrap.inc ซึ่งก็ดูปกติดี โดยเฉพาะตรงฟังก์ชัน sess_gc() ซึ่งมีไว้เคลียร์ session ที่อายุเกิน maxlifetime ทิ้ง โดยการลบ record ทิ้งด้วยเงื่อนไข ก็ดูเรียบร้อยดี

วันนี้อยากเคลียร์ปัญหานี้ให้จบเลยแกะดูการทำงานต่อ เริ่มจากตรวจล็อกของ mysql พบว่า ไม่เคยมีคำสั่ง "DELETE FROM sessions ...." ถูกรันเลย แปลว่า sess_gc() ไม่เคยถูกเรียกใช้จาก php เลย คำถามจึงมีอยู่ว่า เมื่อไหร่ที่ php จะเรียกใช้ sess_gc() ทำให้ต้องย้อนไปแกะดูใหม่

เปิดดู php.ini อีกครั้ง (ก่อนนี้ก็เคยแกะดูแล้ว แต่ไม่พบอะไรผิดปกติ) คราวนี้เน้นไปที่เรื่อง gc ก็พบตรงนี้เข้า

; Define the probability that the 'garbage collection' process is started
; on every session initialization.
; The probability is calculated by using gc_probability/gc_divisor,
; e.g. 1/100 means there is a 1% chance that the GC process starts
; on each request.

; This is disabled in the Debian packages, due to the strict permissions
; on /var/lib/php5. Instead of setting this here, see the cronjob at
; /etc/cron.d/php5, which uses the session.gc_maxlifetime setting below
;session.gc_probability = 0
session.gc_divisor = 100

นั่นคือ ปกติ session.gc_probability = 1 จะทำให้ทุกครั้งที่ php ทำงาน จะมีโอกาส 1/100 ที่ gc ทำงาน หรือพอจะตีความว่า รัน php 100 ครั้ง gc จะถูกเรียก 1 ครั้งก็พอได้ แต่ใน debian ไม่ใช่อย่างนั้น debian จะ comment ค่านี้ไว้ ทำให้มีค่าเป็น 0 เสมอ gc จะไม่ถูกเรียกใช้งาน แต่จะมี cron สำหรับลบ session ที่หมดอายุแทน เหตุผลก็อธิบายไว้ใน comment แล้ว

ตรงนี้แหละคือปัญหา เพราะนั่นเท่ากับว่า sess_gc() ที่ drupal เตรียมไว้ให้ php เรียกใช้ จะไม่ถูกเรียกใช้เลย นั่นคือสาเหตุที่ opentle.org มีปัญหาดังกล่าว

การแก้ปัญหา


การแก้ไฟล์ php.ini ไม่ใช่ทางออกที่ดี ควรแก้ใน drupal ในไฟล์ sites/default/settings.php แทน โดยเพิ่ม

ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 100);

เข้าไป เท่านี้ก็เรียบร้อย ทิ้งไว้สักพัก session ก็ถูกจัดการได้ถูกต้องตามปกติแล้ว