แคชเว็บไซต์แบรนด์ตัวเอง: ตั้งค่า page cache, object cache และ browser cache
1. แคชสามชั้นแต่ละชั้นดูแลอะไร
แคชของเว็บไซต์แบรนด์ตัวเองแบ่งได้เป็นสามชั้น แต่ละชั้นแก้ปัญหาต่างกัน อย่าเอามาปนกัน page cache เก็บผลลัพธ์ HTML ทั้งหน้าเป็นไฟล์สแตติก ครั้งต่อไปที่มีคนเข้า URL เดิมก็ส่งไฟล์กลับเลย ไม่ต้องรัน PHP และฐานข้อมูลอีก object cache เก็บผลลัพธ์การ query ฐานข้อมูลไว้ในหน่วยความจำ (เช่น Redis หรือ Memcached) ครั้งต่อไปที่ query เดิมก็ดึงจากหน่วยความจำเลย ไม่ต้อง query MySQL อีก browser cache ใช้ HTTP response header บอกเบราว์เซอร์ของผู้เข้าชมว่า “ไฟล์นี้เก็บไว้ในเครื่องคุณ” ครั้งต่อไปก็อ่านจากดิสก์ในเครื่องเลย
ผลตอบแทนด้านประสิทธิภาพของสามชั้นต่างกันมาก page cache ได้ผลมากที่สุด——ข้ามการรัน PHP และการ query ฐานข้อมูลทั้งหมด LCP ลดจาก 4 วินาทีขึ้นไปเหลือไม่ถึง 1 วินาที object cache ได้ผลปานกลาง——ประหยัดแค่เวล query ฐานข้อมูล ถ้า page cache ทำงานแล้ว object cache จะไม่ถูกเรียกเลย browser cache ได้ผลกับผู้เข้าชมที่กลับมา——ครั้งแรกต้องดาวน์โหลด ครั้งที่สองอ่านจากเครื่อง ความเร็วในการกลับมาเร็วขึ้นชัดเจน
| ชั้นแคช | เก็บอะไร | เก็บที่ไหน | ผลตอบแทนด้านประสิทธิภาพ | เครื่องมือทั่วไป |
|---|---|---|---|---|
| แคชหน้าเว็บ | หน้า HTML ทั้งหน้า | ดิสก์หรือหน่วยความจำเซิร์ฟเวอร์ | มากที่สุด, LCP ลด 60% ถึง 80% | WP Rocket、LiteSpeed Cache |
| object cache | ผลลัพธ์การ query ฐานข้อมูล, PHP object | Redis หรือ Memcached | ปานกลาง, เวลา query DB ลด 50% | Redis、Memcached |
| browser cache | CSS/JS/รูปภาพ | เบราว์เซอร์ของผู้เข้าชม | ผู้เข้าชมที่กลับมา, เข้าครั้งที่สองเร็วขึ้น | HTTP header Cache-Control |
| CDN cache | ไฟล์ static + HTML บางส่วน | edge node ทั่วโลก | ความหน่วงข้ามภูมิภาคลด 50% ขึ้นไป | Cloudflare、Akamai |
หลายคนเริ่มต้นด้วยการไปยุ่งกับ Redis และ Memcached ก่อน ทั้งที่จริงแล้วสำหรับเว็บไซต์ B2B แบบ standalone การตั้งค่า page cache ให้ดีก็แก้ปัญหาด้านประสิทธิภาพไปได้แล้ว 80% ส่วน object cache เป็นตัวเสริม เหมาะจะเปิดใช้เมื่อมี UV ต่อเดือนเกิน 5 หมื่น หรือเมื่อการ query ฐานข้อมูลเริ่มช้าลง ความสัมพันธ์ระหว่าง CDN กับแคชสามชั้นดูได้ที่CDN เร่งความเร็วทั่วโลกและการเลือกโหนด ส่วนเกณฑ์มาตรฐานของตัวชี้วัดประสิทธิภาพดูได้ที่การปรับแต่ง Core Web Vitals。
สอง page cache: จะเก็บ HTML อย่างไรไม่ให้พลาด
page cache เป็นชั้นที่คุ้มค่าที่สุด และเป็นชั้นที่ตั้งค่าผิดได้ง่ายที่สุด อาการผิดพลาดที่พบบ่อยคือ ผู้เข้าชมส่งแบบฟอร์มสอบถามแล้วกลับเห็นข้อมูลที่ผู้เข้าชมคนก่อนส่งไว้ หรือผู้ใช้ล็อกอินแล้วยังเห็นหน้าเว็บในสถานะที่ยังไม่ล็อกอิน สาเหตุคือ page cache เก็บ HTML ที่มีเนื้อหาแบบไดนามิกไว้เป็นไฟล์ static ด้วย
วิธีที่ถูกต้องคือแยกตาม URL และสถานะผู้ใช้เป็นสองประเภท หน้า static สาธารณะ (หน้าแรก หน้าสินค้า หน้าบทความบล็อก) ให้ใช้ page cache ส่วนหน้าไดนามิก (หลังส่งแบบฟอร์มสอบถาม หลังบ้าน wp-admin หน้าที่ล็อกอินแล้ว ตะกร้าสินค้า) ต้องข้ามแคช ปลั๊กอินแคชของ WordPress (WP Rocket, LiteSpeed Cache) รองรับกฎ “ยกเว้น URL” และ “ข้ามแคชตาม Cookie” ทั้งคู่
| ประเภทหน้า | แคชหรือไม่ | ระยะเวลาแคช | กฎยกเว้น |
|---|---|---|---|
| หน้าแรก หน้ารายการสินค้า | แคช | 1 ชั่วโมงถึง 4 ชั่วโมง | ยกเว้นผู้ใช้ที่ล็อกอิน |
| หน้าบทความบล็อก | แคช | 12 ชั่วโมงถึง 1 วัน | รีเฟรชเองเมื่ออัปเดตเนื้อหา |
| หน้าฟอร์มสอบถาม | ไม่แคช | ไม่ตั้ง | ดึงจากต้นทางทุกครั้ง |
| หน้าส่งแบบฟอร์มสำเร็จ | ไม่แคช | ไม่ตั้ง | มี session cookie |
| หลังบ้าน wp-admin | ไม่แคช | ไม่ตั้ง | ยกเว้นด้วย cookie ผู้ดูแล |
| หน้าผลการค้นหา | แคชสั้นหรือไม่แคช | 5 นาที | เลี่ยงการแคชคำค้นหาที่ต่างกัน |
ตัวอย่างสถานการณ์: หน้าอธิบายสินค้าของเว็บไซต์เครื่องจักรอุตสาหกรรม มี URL เป็น /products/cnc-machine-a/ เนื้อหาหน้านี้แก้ไขไม่กี่เดือนครั้ง จึงแคชไว้ 1 วันได้เลย แต่หน้า “ส่งแบบฟอร์มสำเร็จ” ของแบบฟอร์มสอบถามมี URL เป็น /inquiry-thank-you/ เนื้อหาที่ผู้เข้าชมแต่ละคนเห็นควรเป็นข้อความยืนยันที่ตัวเองส่งมา ห้ามแคชเด็ดขาด ใน WP Rocket ให้เพิ่ม /inquiry-thank-you/ และ /wp-admin/* ลงในรายการ Never Cache URL(s)
缓存失效也是页面缓存的关键。改了一篇博客文章,怎么让线上立刻生效?两种方式:一种是发布时手动Purge全部缓存,另一种是用”按URL刷新”只清那一篇。WP Rocket在文章更新时会自动刷新相关缓存,但很多自定义页面不会自动触发,需要养成发布后点一下Purge的习惯。环形山在出海站里把缓存失效和发布流程绑定,详见 เทคโนโลยีของ crater และ plugin ที่พัฒนเองสี่ตัว。
สาม object cache: จะนำผลลัพธ์การ query ฐานข้อมูลกลับมาใช้ซ้ำอย่างไร
เมื่อแคชหน้าเว็บทำงาน PHP และฐานข้อมูลจะไม่ถูกเรียกใช้ แคชออบเจ็กต์ก็ไม่ได้ใช้เช่นกัน แต่เมื่อแคชหน้าเว็บไม่ทำงาน เช่น คุณเพิ่งเผยแพร่บทความใหม่ หรือเข้าชมหน้าที่ไม่ควรถูกแคช PHP ต้องดึงข้อมูลจากฐานข้อมูล หน้า WordPress โดยเฉลี่ยต้องคิวรีฐานข้อมูล 12 ถึง 20 ครั้ง ทุกครั้งอ่านจากดิสก์ IO เมื่อสะสมมากเข้าจึงทำให้หน้าเว็บโหลดเกิน 1 วินาที
แคชออบเจ็กต์เก็บผลลัพธ์การคิวรีเหล่านี้ไว้ใน Redis หรือ Memcached ครั้งต่อไปที่คิวรีเดิมจะอ่านจากหน่วยความจำโดยตรง ติดตั้ง Redis บนเซิร์ฟเวอร์เครื่องเดียว แล้วเชื่อม WordPress ผ่านปลั๊กอิน Redis Object Cache ไม่ต้องแก้โค้ดเลย หากตั้งค่าถูกต้อง เวลาคิวรีฐานข้อมูลลดลงได้กว่า 50% และเวลาตอบสนองเมื่อแคชหน้าเว็บ miss ลดจาก 1.5 วินาทีเหลือไม่ถึง 0.5 วินาที
| ตัวชี้วัด | ไม่มีแคชออบเจ็กต์ | มีแคชออบเจ็กต์ Redis | อัตราที่ดีขึ้น |
|---|---|---|---|
| จำนวนครั้งคิวรีฐานข้อมูลต่อหน้า | 12 ถึง 20 ครั้ง/หน้า | 2 ถึง 5 ครั้ง/หน้า | ลดลง 70% ถึง 80% |
| เวลาคิวรีต่อครั้ง | 5 ถึง 20 มิลลิวินาที (ดิสก์ IO) | 0.1 ถึง 1 มิลลิวินาที (หน่วยความจำ) | เร็วขึ้น 10 ถึง 50 เท่า |
| เวลาตอบสนองเมื่อแคชหน้าเว็บ miss | 1 ถึง 1.5 วินาที | 0.3 ถึง 0.5 วินาที | ลดลงกว่า 50% |
| การใช้งาน CPU ของ MySQL | ช่วงพีกสูงกว่า 80% | ช่วงพีก 30% ถึง 40% | ลดลงกว่าครึ่ง |
แคชออบเจ็กต์ไม่ใช่สิ่งที่ต้องมีก็ได้ เว็บ B2B ที่มี UV ต่อเดือนต่ำกว่า 1 หมื่น ฐานข้อมูลไม่ได้มีภาระหนัก แคชหน้าเว็บรับมือคำขอไปแล้ว 95% ผลตอบแทนจากการใช้ Redis จึงไม่ชัดเจน แล้วเมื่อไหร่ควรใช้ มีสองสัญญาณ คือ หนึ่ง ใน slow query log ของ MySQL มีคิวรีที่ใช้เวลาเกิน 1 วินาทีมากกว่า 100 ครั้งต่อวัน สอง เมื่อแก้ไขบทความในหลังบ้าน บันทึกแล้วต้องรอหลายนาทีกว่าหน้าบ้านจะอัปเดต (แคชออบเจ็กต์ไม่หมดอายุอย่างถูกต้อง) เรื่อง slow query และการปรับแต่งดัชนีของฐานข้อมูลดูได้ที่slow query และการปรับแต่งดัชนีของฐานข้อมูลเว็บไซต์。
สี่ แคชเบราว์เซอร์: ตั้งค่าแคชเฮดเดอร์ของไฟล์ static อย่างไร
แคชเบราว์เซอร์เป็นชั้นที่ตั้งค่าง่ายที่สุดในสามชั้น ไม่เกี่ยวข้องกับโปรแกรมใด ๆ ฝั่งเซิร์ฟเวอร์ เพียงเพิ่มเฮดเดอร์ตอบกลับไม่กี่บรรทัดในเว็บเซิร์ฟเวอร์ (Nginx หรือ Apache) หลักการคือ เมื่อเซิร์ฟเวอร์ส่ง CSS, JS, รูปภาพกลับไป จะเพิ่ม Cache-Control: max-age=2592000 ใน HTTP response header บอกเบราว์เซอร์ว่า “ไฟล์นี้เก็บไว้ในเครื่องคุณ 30 วัน” ภายใน 30 วันถ้าเข้าอีกครั้งจะไม่ร้องขอจากเซิร์ฟเวอร์
แต่ตรงนี้มีความขัดแย้งคลาสสิกอยู่ ถ้าตั้งเวลาแคชเบราว์เซอร์นาน ผู้ใช้เข้าชมได้เร็ว แต่เมื่อคุณแก้ CSS หรือ JS เครื่องผู้ใช้ยังมีเวอร์ชันเก่า หน้าเว็บที่เห็นจึงจัดสไตล์ผิด วิธีแก้ไม่ใช่ลดเวลาแคชลง แต่ใส่เลขเวอร์ชันหรือ hash ต่อท้ายชื่อไฟล์ เช่น style.css?v=2 หรือ style.abc123.css เมื่อเนื้อหาไฟล์เปลี่ยน URL ก็เปลี่ยน เบราว์เซอร์จะดาวน์โหลดใหม่โดยอัตโนมัติ
| ประเภทไฟล์ | คำแนะนำ Cache-Control | max-age | กลยุทธ์การอัปเดต |
|---|---|---|---|
| รูปภาพ (jpg/png/webp) | public, max-age=31536000 | 1 ปี | ชื่อไฟล์มี hash เปลี่ยนรูปก็เปลี่ยนชื่อ |
| ไฟล์ฟอนต์ | public, max-age=31536000 | 1 ปี | ฟอนต์ไม่เปลี่ยนไม่ต้องแก้ |
| ไฟล์ CSS | public, max-age=604800 | 7 วัน | ชื่อไฟล์มีเลขเวอร์ชัน ?ver=2 |
| JavaScript | public, max-age=604800 | 7 วัน | ชื่อไฟล์มีเลขเวอร์ชัน ?ver=2 |
| หน้า HTML | no-cache, must-revalidate | ไม่แคช | ตรวจสอบกับต้นทางทุกครั้ง |
| favicon.ico | public, max-age=604800 | 7 วัน | แทบไม่แก้ |
ข้อสรุปที่ขัดกับสัญชาตญาณ: หลายคนกลัวว่าถ้าตั้งแคชไว้นานแล้วแก้เนื้อหา ผู้ใช้จะไม่เห็นการเปลี่ยนแปลง จึงตั้ง max-age ของทุกไฟล์เป็น 1 ชั่วโมง ผลคือผู้เข้าชมทุกคนต้องดาวน์โหลด CSS และ JS ใหม่ทั้งหมดทุกครั้งที่เข้าเว็บ ทำให้เวลาแสดงผลหน้าแรกช้าลงด้วยซ้ำ วิธีที่ถูกคือตั้งให้ยาวไปเลย (รูปภาพ 1 ปี, CSS/JS 7 วัน) แต่แก้ปัญหาการอัปเดตด้วย hash ของไฟล์หรือเลขเวอร์ชัน ฟังก์ชัน wp_enqueue_script ของ WordPress จะเติมเลขเวอร์ชันให้อัตโนมัติ และเลขเวอร์ชันจะเปลี่ยนเองเมื่อธีมหรือปลั๊กอินอัปเดต ไม่ต้องจัดการเอง
การปรับแต่งรูปภาพและการจัดการไฟล์สื่อ ดูได้ที่การจัดการไฟล์มีเดียและการบีบอัดรูปภาพ ส่วนแคชไฟล์ static ของเว็บหลายภาษาต้องระวังเรื่อง hreflang กับแคช CDN ทำงานร่วมกัน ดูได้ที่การติดตั้ง hreflang สำหรับหลายภาษา。
ห้า กลยุทธ์การล้างแคชและความขัดแย้งที่พบบ่อย
หลังตั้งค่าแคชเสร็จ ปัญหาใหญ่ที่สุดคือ “ทำอย่างไรให้แคชหมดอายุตอนที่ควรหมดอายุ” ถ้าแคชไม่หมดอายุตามเวลา ผู้ใช้จะเห็นเนื้อหาเก่า ถ้าแคชหมดอายุง่ายเกินไปก็ไม่ได้ประโยชน์เรื่องประสิทธิภาพ เว็บไซต์ของตัวเองมักเจอสถานการณ์ที่ต้องล้างแคช 4 แบบ คือ เผยแพร่บทความใหม่ อัปเดตข้อมูลสินค้า แก้ CSS/JS และส่งฟอร์มสอบถาม แต่ละแบบใช้กลยุทธ์การล้างแคชต่างกัน
| สถานการณ์ | ขอบเขตที่ล้าง | วิธีล้าง | ข้อควรระวัง |
|---|---|---|---|
| เผยแพร่บทความบล็อกใหม่ | หน้าแรก+หน้ารวมบทความ+หน้าบทความ | WP Rocket รีเฟรชอัตโนมัติ | หน้าหมวดหมู่และหน้าแท็กก็ต้องรีเฟรชด้วย |
| อัปเดตราคาสินค้า | หน้ารายละเอียดสินค้า+หน้ารวมสินค้า | Purge URL ที่เกี่ยวข้องด้วยตัวเอง | อย่า Purge ทั้งหมด เพราะกระทบประสิทธิภาพ |
| แก้ CSS/JS | ไฟล์ static ที่เกี่ยวข้อง | แก้เลขเวอร์ชันแล้วมีผลทันที | อย่าแก้ max-age ให้เปลี่ยนชื่อไฟล์ |
| ส่งฟอร์มสอบถาม | หน้าฟอร์มไม่แคชเลย | กฎยกเว้น URL | หน้าสำเร็จก็แคชไม่ได้ |
| ปรับปรุงเว็บทั้งไซต์ | แคชทั้งหมด | Purge All ในคลิกเดียว | เลือกทำช่วงที่คนเข้าน้อย |
| อัปเดตปลั๊กอิน/ธีม | แคชทั้งหมด | Purge อัตโนมัติหรือด้วยตัวเอง | สำรองข้อมูลก่อนอัปเดต |
ความขัดแย้งที่พบบ่อยมีสองอย่าง อย่างแรกคือติดตั้งปลั๊กอินแคชสองตัว (เช่นติดทั้ง WP Rocket และ LiteSpeed Cache) ทั้งสองตัวต่างแก้ .htaccess และ output buffer ผลคือหน้าจอขาวหรือสไตล์เพี้ยน กฎคือทั้งไซต์ให้ติดตั้งปลั๊กอินแคชตัวเดียว อย่างที่สองคือแคชหน้ากับสถานะล็อกอินขัดกัน ผู้ใช้ล็อกอินหลังบ้านแล้ว แต่หน้าเว็บยังเป็นหน้าแคชตอนยังไม่ล็อกอิน ทำให้แถบนำทางที่เห็นไม่ถูกต้อง วิธีแก้คือให้ปลั๊กอินแคชตรวจ cookie การล็อกอินแล้วข้ามแคชอัตโนมัติ
หลังนำกลยุทธ์แคชขึ้นใช้งานต้องติดตามต่อเนื่อง ตรวจอัตราการเข้าแคชและ LCP เดือนละครั้ง ถ้าอัตราการเข้าแคชลดลงกะทันหัน มักมีคนแก้โครงสร้าง URL หรือใส่ลิงก์ที่มีพารามิเตอร์สุ่ม ดูรายการติดตามและแจ้งเตือนได้ที่การมอนิเตอร์และแจ้งเตือนเว็บไซต์ ส่วนงานดูแลประจำวันดูได้ที่งาน 6 อย่างที่ต้องทำทุกเดือนในช่วงดูแลเว็บไซต์ และควรซ้อมสำรองและกู้คืนอีกครั้งหลังเปลี่ยนการตั้งค่าแคช ดูได้ที่การสำรองและกู้คืนเว็บไซต์。
FAQ คำถามที่พบบ่อย
เว็บไซต์ WordPress ของตัวเองจำเป็นต้องติดตั้งปลั๊กอินแคชไหม?
หน้าแบบฟอร์มสอบถามสามารถแคชได้ไหม
Redis object cache ควรเปิดใช้เมื่อไหร่
ควรตั้งเวลาของ browser cache นานเท่าไหร่
แคชแล้วแก้เนื้อหาผู้ใช้ไม่เห็นทำอย่างไร
page cache กับ CDN cache จะขัดกันไหม
ทิ้งท้าย
เรื่องแคชนี้ มือใหม่มักพลาดสองทางสุดขั้ว ทางแรกคือไม่ตั้งค่าเลย ทุกหน้าต้องรัน PHP และฐานข้อมูลสด ๆ ทำให้หน้าแรกโหลดเกิน 4 วินาที ทางที่สองคือตั้งค่าแรงเกินไป แคชแม้กระทั่งหน้าฟอร์ม จนผู้ใช้เห็นข้อมูลที่คนอื่นส่งมา วิธีคิดที่ถูกต้องคือ “จัดการเป็นชั้น” หน้า static แคชได้เต็มที่ หน้า dynamic ห้ามแคชเด็ดขาด ส่วนไฟล์ static ให้แคชยาวพร้อมใส่เลขเวอร์ชัน
ลงมือทำสามขั้น ขั้นแรก ติดตั้งปลั๊กอินแคชหน้าเว็บ (เลือกอย่างใดอย่างหนึ่งระหว่าง WP Rocket หรือ LiteSpeed Cache) แล้วยกเว้นหน้าฟอร์มกับ wp-admin ขั้นที่สอง ตั้งค่า Cache-Control header ใน Nginx รูปภาพ 1 ปี CSS/JS 7 วัน HTML ไม่แคช ขั้นที่สาม เมื่อ UV ต่อเดือนเกิน 5 หมื่น ค่อยเพิ่ม Redis object cache ทำสามขั้นนี้เสร็จ LCP จะลดลงเหลือไม่ถึง 1 วินาที และไม่เกิดอุบัติเหตุแคชปนเนื้อหากัน
ประโยคเดียวที่จำได้ขึ้นใจ แคชคือคันโยกของประสิทธิภาพเว็บไซต์ แคชหน้าช่วยประหยัดเวลา 80% object cache ช่วยประหยัดฐานข้อมูล browser cache ช่วยประหยัดการกลับมาเยี่ยมชม ทั้งสามชั้นดูแลคนละส่วน อย่าให้หน้าฟอร์มหลุดเข้าไปในแคช
