Case 12 · Oracle Performance · เรื่องจริงจากลูกค้า
"ระบบเดี๋ยวก็ช้า เดี๋ยวก็เร็ว เป็นมาหลายเดือนแล้ว Programmer บอกไม่ได้แก้ Code อะไรเลย เอายังไงดี"
— ลูกค้าโทรมาถามทีมเรา

เมื่อเราเข้าไปดู ไม่พบปัญหาใน Code ของ Programmer เลยแม้แต่บรรทัดเดียว แต่พบว่ามีบางอย่างวิ่งอยู่เบื้องหลังทุก 30 นาที โดยที่ไม่มีใครในทีมรู้ และสิ่งนั้นสะสมหนักขึ้นมาเรื่อย ๆ นับตั้งแต่วันที่ติดตั้งระบบ

Oracle 11.2.0.1 · HP-UX IA 64-bit AWR Snap 20656–20660 · 240 นาที ระบบ Payment / POS ปกปิดชื่อองค์กร
Act 1 · สิ่งที่เกิดขึ้น

ระบบช้าแบบไม่สม่ำเสมอ — และทุกคนพูดถูกทั้งนั้น

ผู้ใช้แจ้งว่าระบบ POS ตอบสนองช้าลงเป็นช่วง ๆ ไม่ใช่ช้าตลอดเวลา บางครั้งช้าประมาณ 10–15 วินาทีแล้วก็กลับมาเร็วเอง เป็นแบบนี้มาหลายเดือนแล้ว

ฝ่าย IT โทรหา Programmer ที่รับผิดชอบระบบ Programmer เปิดดู Code — "ผมไม่ได้แก้อะไรเลยนะ ทดสอบแล้วก็ผ่าน"

Programmer พูดถูก Code ไม่มีปัญหาจริง ๆ ผู้ใช้ก็พูดถูก ระบบช้าจริง ๆ แต่ไม่มีใครอธิบายได้ว่าทำไม

ทำไมถึงหาสาเหตุยาก: ระบบช้าเป็นช่วงสั้น ๆ แล้วก็หาย พอทีมไปตรวจก็ปกติแล้ว พอ Programmer ทดสอบก็ผ่าน ไม่มีใครเชื่อมโยงได้ว่า "ช้าเป็นรอบ" กับ "มีอะไรรันซ้ำทุก 30 นาที"
Act 2 · สิ่งที่เราพบ

เข้าไปดู AWR — ตัวการไม่ใช่ Code ของ Programmer

เราดึง AWR Report ครอบคลุมช่วงเช้าที่ผู้ใช้แจ้งว่าช้า ส่วนแรกที่เปิดดูคือรายชื่อ Object ที่ถูกอ่านจาก Disk มากที่สุด

อันดับ 1 ไม่ใช่ตารางของ Application — แต่เป็น SYS.AUD$

33.72% ของ Disk ที่ถูกอ่านทั้งหมด มาจาก SYS.AUD$ ตารางเดียว
7,373,362 ครั้งที่อ่านจาก Disk ใน 4 ชั่วโมง
8 รอบ ที่มีการอ่านตารางนี้ ห่างกันพอดี 30 นาที
ขั้นตอนที่ 1 · ใครเป็นคนอ่าน?

SYS.AUD$ คือตาราง Audit Trail — แต่ Application ไม่ได้ยุ่งกับมันเลย

SYS.AUD$ เป็นตารางที่ Oracle ใช้เก็บบันทึกการ Login และการใช้งานระบบ ไม่มีส่วนไหนของ Code Application ที่ Programmer เขียนไปยุ่งกับตารางนี้เลย แล้วใครอ่านมัน?

เราเปิดส่วน SQL ordered by Physical Reads ใน AWR — และเจอคำตอบ:

SQL ordered by Physical Reads — Snap 20656–20660 (240.25 mins)
Phys. Readsรอบต่อรอบ%Module / SQL
7,373,3208921,66533.72% Oracle Enterprise Manager.Metric Engine
5,231,29216326,95623.93% Application (oracleCATPCU1@pbilld1)

ตัวการคือ Oracle Enterprise Manager — Monitoring Tool ที่ทีมติดตั้งไว้เพื่อดูแลระบบ ไม่ใช่ Application ของ Programmer แต่อย่างใด

ขั้นตอนที่ 2 · OEM ทำอะไรอยู่กันแน่?

เช็ค Failed Login ทุก 30 นาที แต่วิธีที่ใช้ทำให้ต้องอ่าน Disk ทั้งตาราง

เราเปิด SQL Text จริงของ OEM จาก AWR:

-- OEM ถามว่า: มี Login ผิดพลาดในช่วง 30 นาทีที่ผ่านมากี่ครั้ง? SELECT TO_CHAR(current_timestamp AT TIME ZONE 'GMT', 'YYYY-MM-DD HH24:MI:SS TZD') AS curr_timestamp, COUNT(username) AS failed_count FROM sys.dba_audit_session WHERE returncode != 0 AND TO_CHAR(timestamp, 'YYYY-MM-DD HH24:MI:SS') >= TO_CHAR(current_timestamp - TO_DSINTERVAL('0 0:30:00'), 'YYYY-MM-DD HH24:MI:SS')

ดูเหมือน Query ธรรมดา แต่ปัญหาอยู่ที่ TO_CHAR(timestamp, ...) ในบรรทัด WHERE — การครอบ Column ด้วย Function ทำให้ Oracle ไม่สามารถใช้ Index ได้ จึงต้องไล่อ่าน ทุก Row ในตาราง เพื่อหาข้อมูล 30 นาทีที่ต้องการ

ผลที่เกิดขึ้น: ทุก 30 นาที OEM วิ่ง Query นี้ 1 รอบ แต่ละรอบต้องอ่าน Disk 921,665 ครั้ง ใช้เวลาประมาณ 13 วินาที ในช่วง 13 วินาทีนั้น Disk I/O ถูกแย่งไป ทำให้ Application ที่รันอยู่พร้อมกันช้าลง แล้วก็กลับมาปกติเอง
Act 3 · ทำไมมาหลายเดือนแล้ว

ปัญหานี้มีอยู่ตั้งแต่วันแรก — แค่ยังเล็กเกินกว่าจะรู้สึก

ตอนที่ติดตั้งระบบครั้งแรก SYS.AUD$ ยังเล็กอยู่ OEM วิ่ง Query เดิมนี้ทุก 30 นาทีเหมือนกัน แต่ตารางเล็ก ไล่อ่านไวมาก ใช้เวลาไม่ถึงวินาที ไม่มีใครรู้สึก

ปีแรก
ตารางยังเล็ก — OEM Full Scan ใช้เวลา <1 วินาที ไม่มีใครสังเกตเห็น ระบบรู้สึกปกติดี
ผ่านไป...
Audit บันทึก Login ทุกวัน ตารางโตขึ้นเรื่อย ๆ Full Scan หนักขึ้นตามขนาดตาราง 2 วิ → 5 วิ → 8 วิ ไม่มีใครรู้สึก
หลายเดือน
ถึงจุดที่ผู้ใช้เริ่มรู้สึกได้ — 13 วินาทีต่อรอบ "ระบบเดี๋ยวก็ช้า เดี๋ยวก็เร็ว เป็นมาหลายเดือนแล้ว..."
ไม่มีวันที่ระบบ "พัง" ชัดเจน มันค่อย ๆ หนักขึ้นทุกเดือน จนวันหนึ่งผู้ใช้รู้สึกได้ — แต่ตอนนั้นก็ไม่มีใครเชื่อมโยงว่ามันสะสมมานานแค่ไหน
การแก้ไข · What We Did

แก้ได้โดยไม่ต้องแตะ Code ของ Programmer เลยแม้แต่บรรทัดเดียว

แก้ที่ 1 · สร้าง Index ให้ตรงกับวิธีที่ OEM ถาม

Function-Based Index — ทำให้ OEM หาข้อมูลได้โดยไม่ต้องอ่านทั้งตาราง

เนื่องจาก OEM ใช้ TO_CHAR(timestamp, ...) ในการถาม เราสร้าง Index ที่ตรงกับ Expression นั้นพอดี ทำให้ OEM สามารถ Seek ตรงไปยังข้อมูล 30 นาทีที่ต้องการได้เลย ไม่ต้องอ่านทั้งตารางอีกต่อไป

แก้ที่ 2 · ตั้ง Purge Job ให้ระบบล้างบันทึกเก่าออกเป็นรอบ

ตารางเล็กลง ปัญหาเดิมก็ไม่กลับมา

เราตั้ง DBMS_AUDIT_MGMT ให้ล้าง Audit Record ที่เก่าเกินกำหนดออกเป็นรอบ โดย Archive ไว้ก่อนตามข้อกำหนดขององค์กร เพื่อให้ตารางไม่โตจนหนักอีกในอนาคต

ผลหลังแก้: OEM ยังทำงานได้ตามปกติ ยังเช็ค Failed Login ทุก 30 นาที Audit Coverage ยังครบตาม Compliance แต่ Disk I/O ของ SYS.AUD$ หายไปจาก AWR Report แทบทันที ผู้ใช้ไม่รู้สึกว่าช้าเป็นช่วง ๆ อีกต่อไป

สุดท้ายแล้ว ไม่มีใครผิดคนเดียว

Programmer เขียน Code ถูกต้อง — ไม่มีส่วนไหนที่เป็นปัญหาเลย

Oracle ออกแบบ OEM ให้ทำงานอัตโนมัติ — แต่ Query ที่ OEM ใช้ ไม่ได้ออกแบบมาสำหรับตารางที่ใหญ่มากโดยไม่มีการดูแล

ไม่มีใครตั้งค่าให้ระบบล้างบันทึกเก่าออก — เพราะไม่มีคนดูแลฐานข้อมูลโดยตรง ตารางจึงโตขึ้นทุกวัน อย่างเงียบ ๆ

นี่คือสิ่งที่เกิดขึ้นเมื่อองค์กรไม่มีคนดูแลด้านฐานข้อมูล ไม่ใช่ระบบพังทันที — แต่ค่อย ๆ หนักขึ้นทุกเดือน จนวันหนึ่งผู้ใช้รู้สึกได้ แล้วโทรมาถามว่า "เอายังไงดี"

รายละเอียดสำหรับ DBA/IT · Technical Details

ข้อมูลดิบจาก AWR และแนวทางตรวจสอบ

ตาราง Segments by Physical Reads จาก AWR จริง (Snap 20656–20660)
OwnerTablespaceObjectPhysical Reads%Total
SYSSYSAUXAUD$7,373,36233.72%
(ปกปิด)(ปกปิด)RECEIPT7,104,51032.49%
Total Physical Reads21,865,229100%

Segment อันดับ 2 (RECEIPT) คือตาราง Application จริง ซึ่งทีมแยกวิเคราะห์ต่างหาก — มี 2 ปัญหาพร้อมกันใน AWR นี้

Function-Based Index คืออะไร และสร้างให้ตรงกับ OEM อย่างไร

ปัญหา: OEM ใช้ TO_CHAR(timestamp, 'YYYY-MM-DD HH24:MI:SS') ใน WHERE ทำให้ B-Tree Index บน timestamp ใช้ไม่ได้

วิธีแก้: สร้าง Index บน Expression เดียวกับที่ OEM ใช้:

-- สร้าง Index ที่ตรงกับ Expression ของ OEM CREATE INDEX i_aud_ts_char ON sys.aud$( TO_CHAR(timestamp, 'YYYY-MM-DD HH24:MI:SS') );

ผลที่ได้: OEM Query เดิมสามารถใช้ Index นี้ได้ทันที ไม่ต้องแก้ SQL ของ OEM เลย

ข้อควรระวัง: SYS.AUD$ เป็น System Object ควรประเมินผลกระทบกับ Write Performance ก่อน เพราะ Index จะเพิ่มภาระตอน INSERT ด้วยทุกครั้งที่ Audit เขียนบันทึก

ขั้นตอนตั้ง Audit Purge Job ด้วย DBMS_AUDIT_MGMT
  1. ตรวจ Audit Mode: SELECT name, value FROM v$parameter WHERE name = 'audit_trail'
  2. Initialize: EXEC DBMS_AUDIT_MGMT.INIT_CLEANUP(audit_trail_type => DBMS_AUDIT_MGMT.AUDIT_TRAIL_AUD_STD, default_cleanup_interval => 720);
  3. กำหนด Archive Timestamp ว่าข้อมูลก่อนเวลานี้ Archive แล้วและพร้อม Purge
  4. สร้าง Purge Job ให้รันในช่วง Off-Peak ตาม Retention Policy ขององค์กร
  5. วัดผลจาก AWR รอบถัดไป — ดูที่ Physical Reads ของ SYS.AUD$ ไม่ใช่แค่ขนาด Tablespace
ที่มาของข้อมูลในกรณีศึกษานี้
  • ตัวเลขทั้งหมด: มาจาก AWR Report จริง (Snap 20656–20660) ปกปิดชื่อองค์กร, Schema ธุรกิจ และ Hostname
  • SQL Text ของ OEM: มาจาก Complete List of SQL Text ใน AWR โดยตรง ไม่ได้แต่งหรือปรับแต่ง
  • Oracle 11g: DBMS_AUDIT_MGMT
  • Oracle 11g: Automatic Workload Repository

ระบบช้าแบบไม่สม่ำเสมอ หาสาเหตุไม่เจอ?

ทีมเราช่วยวิเคราะห์จาก AWR จนถึง Root Cause จริง ก่อนตัดสินใจแก้

ปรึกษาทีม VT Technology