Archive for the 'วิธีแก้ปัญหาเขียนโปรแกรม' Category

ข้อดี Unique Index ใน MyIsam MySQL และวิธีทำสำหรับ Table ขนาดใหญ่

เหตุผลที่ต้องทำ

  • มีการขึ้น Waiting for Table lock level เป็นจำนวนมาก
  • ทำให้การดูดข้อมูลจาก Facebook API ช้า ฯลฯ

เข้าเรื่อง

เนื่องจากว่าผมมีข้อมูลจำนวน 20 กว่าล้าน Record แล้วพึงรู้ว่ามีการ SELECT ถามแล้ว INSERT โดย Scenario ที่ชัดเจนมากที่ทำให้ช้าเวลาจะ Insert ข้อมูลจำนวนมากๆๆๆ แล้วก็มีข้อมูลมากคือ “มีข้อมูลนี้รึยัง ถ้าไม่มี INSERT”

ต้นเหตุของความช้าคือ MyIsam นั้นจะมี Read Lock , Write Lock หรือว่าง่ายๆ จะไม่ทำสองอย่างพร้อมๆกัน ดังนั้นเมื่อเราถามก่อนแล้วค่อย Insert ด้วย Process เดียวมันจะไม่มีปัญหา แต่ถ้ามีการใช้งานพร้อมๆกันละช้าแน่ !! วิธีแก้ที่ทำได้ง่ายคือ

“การทำ Unique Index กับเปลี่ยน Query เวลาจะ Insert เป็น INSERT IGNORE INTO”

ทำให้ไม่มีการเกิด Read Lock น้อยลงทำให้ Insert ได้เร็วขึ้น และการทำ Unique Index ยังทำให้มั่นใจได้มากกว่าว่าไม่มีข้อมูลซ้ำแน่แท้กว่าการถามด้วย SELECT เป็นแน่แท้ แต่ก่อนอื่นการทำ Index ใน MySQL ปกตินั้นคือใส่ Index ได้ไม่เกิน 1000 bytes ดังนั้นถ้าจะทำ index บน varchar 255 , 2 fields ที่ผมเจอก็จะไม่พอ 255*2*3 = 1530 bytes (utf-8 3 bytes ต่อ 1 characters) ผมก็เลยต้องมีการเปลี่ยนเป็น latin1 ทั้ง 2 fields ด้วยคำสั่งนี้

ALTER TABLE `mytable`

CHANGE `field1` `field1` VARCHAR(255) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL,

CHANGE `field2` `field2 ` VARCHAR(255) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL

 

เพียงคำสั่งนี้จะเสียเวลาทำเพียงครั้งเดียว เร็วกว่าทำทีละ field ถ้าข้อมูลมี 20 ล้าน rows ก็ประหยัดเวลาไปได้มากครับ เอาละเข้าเรื่องวิธีทำ Unique Index

วิธีทำที่ดีและเร็วที่สุดกับ Table ขนาดใหญ่โดยใช้คำสั่งทำ Unique Index แบบ 2 field ดังนี้

ALTER IGNORE TABLE mytable ADD UNIQUE unique_field1_and_field2( field1, field2 )

เพียงเท่านี้มันจะทำการใส่ Unique Index ให้แถมลบข้อมูลซ้ำให้ด้วย !!

ปล. ถ้าคุณไม่มั่นใจที่จะเสียข้อมูลซ้ำไปให้ไปลองใช้วิธีอื่นครับ เช่น SELECT DISTINCT GROUP BY
ปล2. ผมมีการปรับอีก 2 ค่าใน my.cnf คือ max_write_lock_count = 50 แล้วก็  concurrent_insert = 2 ครับ

ผลลัพธ์ที่น่าพอใจ

  • ความเร็วในการดูดข้อมูลจาก Facebook API ผมเร็วขึ้นจาก 1500 วินาทีเหลือ 27 วินาทีเพราะ MySQL ไม่ขึ้น Waiting for Table จากการ SELECT แล้ว INSERT
  • การใช้ CPU ของ MySQL ลดลงอย่างมาก (ของผมมีการใช้งานหนักมีการ INSERT วินาทีหนึ่งประมาณ 10 – 100 records ดังนั้นถ้ามี SELECT มาขั้นแล้วค้นหาข้อมูลใน Table ขนาด 20gb มันเปลื้อง CPU ขนาดไหน !!)

 

 

 

ใช้ Redis แก้ปัญหาตอนที่ 1

เนื่องจากผมได้จับ Redis แบบจริงๆจังๆเป็นครั้งแรก โดยผมศึกษาจากความเป็นไปได้มาดูได้อย่างชัดเจนคือ Redis แก้ปัญหาเรื่อง Queue , Stack ผมได้เป็นอย่างดีกว่า Relational Database เป็นไหนๆ เพราะเรื่องเหล่านี้ครับ

  • ปัญหา SELECT และ INSERT ซึ่งเป็นไปได้ว่าถ้ามี Process มากกว่า 1 ที่ทำงานเหมือนกันตอนจังหวะเดียวกัน ดันจะ Save ID เดียวกันแล้วผ่านขั้นตอน SELECT มาแล้วรอ Insert (ปัญหาอาจจะมาจากการ Locked ของ Database) ทำให้มี ID อันเดียวกันซ่ำซ้อนอยู่ในระบบ ซึ่งใน Relational Database จะแก้ด้วยวิธีใส่ Unique Index แต่ Redis ทำได้ดีกว่ามาก เพราะ LPUSH LPOP ในเครื่อง R210 ของผมนั้นได้ 110,000 Req/s ซึ่งเร็วกว่า MySQL ประมาณ 50 กว่าเท่าได้
  • การเพิ่มข้อมูลไปใน Queue โดย Value จะต้องไม่ซ้ำกับอันเดิม Redis นั้นมี SADD ซึ่งเวลาเพิ่มไปแล้วมันจะ Return true กับ false มานั้นเพียงพอให้เรารู้ได้แล้วว่า ที่เราใส่ข้อมูลไปใน Queue นั้นสำเร็จไหมเวลาจะเอาข้อมูลก็ SPOP ออกมา ข้อดีที่ชัดเจนเลยคือถ้าเป็น MySQL เวลาเรา Pop ข้อมูลพร้อมกัน 30 Process ที่เกิดมาพร้อมกัน เรามีโอกาศได้ข้อมูลที่ SPOP ซ้ำมาจำนวนไม่น้อยทีเดียว แต่ถ้าเป็น SPOP นั้นคือจะลบแล้วเอาข้อมูลออกมา ทำให้ไม่มีการซ้ำของข้อมูลเวลาเอาออกมาซึ่ง ทำให้ทำงานไม่ซ้ำซ้อนกันครับ
  • เวลาทำ Config ระบบซึ่งเก็บเป็นแค่ Key Value Store ธรรมดา การใช้ MySQL ก็ไม่ใช่ทำไม่ได้ แต่ความเร็วอาจจะเป็นปัญหาเมื่อมีการเรียกใช้เป็นจำนวนมากๆ กับการหาค่า Config ของ Key ตัวหนึ่งใน Database ซึ่งประมาณ 2500 req/s แต่กับ Redis 115,000 req/s คนละเรื่อง !!
  • ถังพักข้อมูล พูดเฉยๆคงไม่เห็นภาพต้องอธิบาย Scenario เช่น ผมต้องการไปเก็บข้อมูลจาก Facebook API แต่ทว่าปัญหาไม่ได้อยู่ที่การ Request ไปหา Facebook แต่ปัญาหาคือการ Save ลงฐานข้อมูลช้าทำให้อยู่ใน Waiting State นานเกินไปแทนที่จะไป Request มาเก็บข้อมูลต่อได้ ก็เลยค้างแหง่กอยู่แบบนี้ โดยถ้าเอาถังพักเป็น Redis ก็บอกได้คำเดิมคือ 2500 req/s กับ 115,000 req/s ต่างกันเยอะขนาดนี้มีผลต่อการพักข้อมูลแน่นอนครับยิ่งเร็ว Process ยิ่งปล่อยเร็วแล้วไป Request เอาข้อมูลมาเพิ่มได้เร็วขึ้น ดังนั้นกรณีนี้ยิ่งเร็วยิ่งดี ! ดังนั้น Redis เลยเป็นทางเลือกที่เหมาะสม

จริงๆแล้ว Redis ยังแก้ปัญหาได้อีกมากมายแต่อันนี้คืออันที่ผมเอามาใช้จริงและได้ผลดีครับ เลยเอามาบอกเป็น idea เผื่อมีคนอยากลองใช้ โดยตัว Client ผมใช้เป็น PHP Redis ตัวนี้นะครับซึ่งเขียนด้วยภาษา C ครับ https://github.com/nicolasff/phpredis ส่วนถ้าผมได้ใช้อะไรเพิ่มเกี่ยวกับ Redis เดียวผมจะมาเล่าให้ฟังต่อนะครับ

วิธีทำให้ PHP สั่ง Kill Process ใครก็ได้ในระบบ

การตั้ง Title อันนี้ยากมากเพราะจริงๆคือการทำให้ Apache นั้นมีสิทธิเหมือน Root แล้วไม่ต้องใส่ Password ดังนั้นผมจะมาบอกวิธีกันครับเอาไปลองใช้กันได้โดยผมใช้ Centos เช่นเดิมครับส่วนใครใช้ distro อื่นต้องปรับกันเอานะครับ

  1. เข้า Shell เป็น root
  2. ใช้คำสั่ง visudo
  3. ค้นหาบรรทัดนี้ root    ALL=(ALL)       ALL
  4. พิมพ์บรรทัดถัดไปว่า apache ALL=(ALL) NOPASSWD: ALL
    หมายเหตุ all ด้านหลังสุดคือหมายถึงยอมให้ใช้ทุกคำสั่ง ถ้าอยากให้ใช้บางคำสั่งได้ก็เขียนแบบนี้ครับ /bin/kill *,/usr/bin/php * แบบนี้นะครับโดยเน้นว่าต้องมี * ครับ
  5. เสร็จแล้วก็ save ไฟล์ถ้าไม่มีอะไรผิดพลาดจะไม่ฟ้อง error แต่ถ้ามีผิดแล้วมันถามว่าจะทำไงต่อพิมพ์ตัวอักษร e แล้วก็ไปแก้ไขให้ถูกต้อง
  6. ไปปิด safe mode ใน php.ini และเอาพวกปิด disable function ออก
  7. เสร็จแล้ว restart apache สัก 1 ครั้งในกรณีผมใช้ service httpd restart
  8. code ใน php ให้ใช้ shell_exec ได้เลยครับ โดยผมเจอปัญหาว่าใช้ xargs ไม่ติดก็เลยใช้วิธีเอา output จาก exec ก่อนแล้วค่อยไปใช้ shell_exec ครับโดย ถ้าใช้คำสั่ง kill process ผมใช้แบบนี้ครับ “sudo kill -9 $pid” เน้นเลยว่าต้องมี sudo ครับไม่งั้นจะ kill process คนอื่นไม่ได้ครับ

เป็นอันเสร็จครับ โดมผมก็มั่วๆจนได้ลำดับการทำได้ประมาณนี้ครับ โดยสำคัญสุดคือการทำ apache เป็น sudoer ครับ

Next Page »