กฏ 6 ข้อ ที่ควรทำตาม สำหรับนักพัฒนาเว็บแอพลิเคชั่น
< This article translated from a practical guide to building fast web applications in the cloud by Luciano Mammino>
สำหรับบทความนี้ แปลมาจากบล็อกของ Luciano ครับ เนื้อหาหลักนั้นอ้างอิงกับภาษา PHP แต่จริงๆ เป็นพื้นฐานที่สามารถนำไปใช้ได้ในทุกภาษาครับ
ในตอนนี้ผมจะเน้นปัญหาหลักๆ ที่พบเจอเวลาที่นักพัฒนาต้องการจะสร้างเว็บแอพลิเคชั่นที่ดีเยี่ยมออกมา (โดยเฉพาะในส่วนของหลังบ้าน) เพื่อที่นักพัฒนาจะได้ลองไตรตรองดูถึงความเหมาะสม
กฏขั้นพื้นฐานประกอบด้วย
- หนึ่ง เลิกยุ่งกับการ Optimization ก่อนเวลาอันควร
- สอง ทำงานเฉพาะสิ่งที่ควรจะทำ
- สาม จงทำมันทีหลัง
- สี่ มีแคชเสมอ
- ห้า อย่ามานั่ง Query ทีละอัน
- หก สเกลแบบ Holizontal
1 เลิกยุ่งกับการ Optimize ก่อนเวลา
นักพัฒนาจำนวนมากเสียเวลาไปกับการคิดโค้ดในส่วนที่ไม่ได้สำคัญอะไรเพื่อให้ประสิทธิภาพการทำงานออกมาดี พูดง่ายๆ ว่า นักพัฒนาไม่ได้รู้เรื่องอะไรว่าส่วนไหนเป็นส่วนที่มีความสำคัญแล้วจำเป็นจริงๆ ที่จะต้องปรับปรุงประสิทธิภาพการทำงาน กลับเสียเวลาไปกับการคิดว่า ควรใช้ ‘ (single qoute) มากกว่า ” (double qoute)
เพื่อป้องการการเสียเวลาไปโดยใช่เหตุ การเขียนโค้ดชุดแรก ควรจะเลิกพะวงเกี่ยวกับเรื่อง Performance ซะ จากนั้น ถ้าทำมันเสร็จแล้ว ค่อยใช้เครื่องมือประเภท Profiler มานั่งดู ว่าปัญหาคอขวดมันอยู่ที่ตรงไหน แล้วค่อยแก้เฉพาะส่วนที่จำเป็น
(หมายเหตุ: ผู้เขียนไม่ได้หมายความว่า การที่ไม่ต้องสนใจเรื่องของ Optimization แปลว่าคุณสามารถเขียนโค้ดแย่ๆ แล้วก็ลืมมันไปนะ แต่ว่าต้องการจะสื่อให้เห็นว่าจะ Optimize ให้ฉลาดขึ้นได้ยังไง)
ตัวอย่างเช่น ถ้าคุณทำงานกับโค้ด PHP คุณก็อาจจะใช้เครื่องมือเหล่านี้เข้ามาช่วย
XDebug เครื่องมือที่เป็นที่นิยมมากที่สุดในการดีบักและก็วัดประสิทธิภาพการทำงาน แค่ลง PHP Extension แล้วก็ผูกเข้ากับ IDE เท่านั้นเอง
xhphof เป็นไลบรารี่ที่ช่วยวัดประสิทธิภาพในการทำงานในระดับฟังก์ชั่น ซึ่งตัวของมันเองยังสามารถเปรียบเทียบประสิทธิภาพการทำงานของโค้ดสองเวอร์ชั่นได้ด้วย
Symfony Profiler เป็นเครื่องมือที่ดีที่สุดตัวนึงของ Symfony Framework ที่ช่วยให้คุณสามารถวัดระยะเวลาตั้งแต่ Request เข้ามาในระบบ ให้ข้อมูลเป็นเส้นเวลาจนกระทั่งออกจากระบบ เพื่อให้รู้ว่าส่วนไหนกินระยะเวลามากที่สุด สำหรับตัวนี้ ปกติมันเปิดใช้งานให้อยู่แล้วในโหมด Development ไม่ต้องลง Extension อะไรเพิ่มเติมเลย
The Stopwatch Component เป็นไลบรารี่อีกตัวของ Symfony ในกาประเมินระยะเวลาในการทำงาน (execution time) ในส่วนของโค้ด PHP ชุดหนึ่งๆ จะเอาเข้าไปรวมกับโปรเจคได้ง่ายๆ เหมือนกัน ไม่ต้องลง Extension เพิ่ม
Blackfire.io อันนี้เป็นเว็บนะครับ จะแสดงออกมาเป็นแผนภาพง่ายต่อความเข้าใจว่าโค้ดส่วนไหนใช้ระยะเวลาประมวลผลนานๆ
Tideway เครื่องมือคล้ายๆ กับ Blackfire.io ครับ
ถ้าหากอยากจะรู้เรื่องในหัวข้อนี้เพิ่มเติม ลองไปอ่านตามแหล่งต่อไปนี้
- On optimization in PHP by Anthony Ferrara
- The fallacy of premature optimization by Randall Hyde
- Premature optimization by Cunningham & Cunningham, Inc
2 ทำเฉพาะสิ่งที่ควรจะทำ
บ่อยครั้งที่โค้ดของคุณทำหน้าที่มากกว่าที่ควรจะทำเพื่อที่จะให้ได้ผลลัพธ์กลับออกมา เช่นเวลาที่ใช้ไลบรารี่ที่มีความซับซ้อน หรือว่าใช้งานเฟรมเวิร์ค เพื่อให้เข้าใจ ขอยกตัวอย่างให้เห็นภาพง่ายๆ เช่น การโหลดไฟล์ หรือโหลดคลาสที่ไม่เคยต้องใช้งานเลยใน Request นั้นๆ หรือว่าการเปิด Database connection ทั้งๆที่ไม่ได้ทำอะไรกับมันเลยก็ตาม
มันมีวิธีที่ช่วยลดและตัดทอนส่วนที่ไม่จำเป็นออก เช่น
- Autoloading อันนี้เป็น PHP Feature อยู่แล้วที่ช่วยให้นักพัฒนาสามารถที่จะอ่านไฟล์โค้ด และเรียกใช้งานเฉพาะเมื่อเวลาที่จำเป็นต้องใช้งาน ส่วนเวลาไม่จำเป็นต้องใช้งานก็ไม่ต้องโหลดไฟล์ขึ้นมา
- Dependency Injection ท่ามาตรฐานที่ Java Programmer ใช้กันมานาน แต่เพิ่งมีการใช้ใน PHP ไม่นานนัก (จริงๆ ผมว่าก็น่าจะเกิน 3 ปีแล้วนะ) ท่านี้จะช่วยลด Dependency ที่เกิดขึ้น ด้วยการยิง Object เข้าไปช่วง Constructor ซึ่งช่วยให้นักพัฒนาสามารถโฟกัสงานเป็นเฉพาะจุดได้ดีขึ้น (ในหัวข้อนี้ผมแนะนำให้หาหนังสือมาอ่านเพิ่มเติมครับ)
- Lazy Loading คือยังไม่ต้อง Initial Object จนกว่าถึงจุดจำเป็นที่จะต้องใช้ครับ เหมาะสำหรับจัดการพวกที่ต้องยุ่งกับฐานข้อมูลหรือว่าไฟล์จำนวนมาก (ผู้แปล: ตรงนี้เค้าไม่ได้หมายถึงพวก Image Lazy Loading ในเพจเพียงอย่างเดียวนะครับ ตัวอย่างเช่น ถ้าสมมุติว่าจะส่งเมล์ ก็ไม่ใช่ว่านั่งเปิด SMTP Connection รอไว้เลย แต่จะเป็นลักษณะของ พอจะใช้ ก็ค่อยไปเปิด หรือพวก Kernel ใน Framework ที่จะมีขั้นตอนในการโหลดเซอร์วิสที่เกี่ยวข้องมา ก็ตอนที่จะเริ่มใช้แล้ว)
สำหรับเรื่อง Dependency Injection ถ้าสนใจ อาจลองศึกษา Symfony Service Container ดูก็ได้ครับ ถ้าพูดให้ง่ายก็เหมือนหมอผ่าตัด หมอผ่าตัดไม่ต้องมีกรรไกรติดตัวทุกชิ้น จะใช้อันไหน ก็บอกพยาบาลโยนมา ขั้นตอนการโยนอยู่ในส่วนของ Setter/Getter นี่แหละครับ หมอมีหน้าที่ใช้อย่างเดียว ตามของที่ส่งเข้ามา
3 ไว้เดี๋ยวค่อยทำ
บางที นักพัฒนาก็จะเขียนอะไรที่ไม่จำเป็นต่อการส่งผลลัพธ์กลับไปหาผู้ใช้ ตัวอย่างเช่น เวลา Register account ก็เขียนให้ระบบส่งเมล์ทันที ก่อนที่ระบบจะส่งผลลัพท์กลับไปหาผู้ใช้ซะด้วยซ้ำไป หรืองานประเภท อัพโหลดรูปภาพ พออัพโหลดเสร็จ ก็จับมา Resize ภาพซะทันทีทันใด งานพวกนี้เป็นงานหนัก ในขณะที่ผู้ใช้อยากจะเห็นผลลัพท์บนหน้าจอเร็วที่สุดเท่าที่จะเร็วได้ นักพัฒนากลับทำอะไรอยู่ก็ไม่รู้ ในการเพิ่มฟังก์ชั่นนั่นนี่เข้ามา
งานพวกนี้ควรนำไปจัดการในสิ่งที่เรียกว่าระบบคิว ซึ่งตอนนี้ก็มีซอฟแวร์อยู่มากมายหลายตัวเช่น RabbitMQ, ActiveMQ, ZeroMQ แล้วก็ถ้าเป็น PHP Programmer ก็สามารถนำไลบรารี่เหล่านี้มาเรียกใช้งานได้
- Resque ไลบรารี่ทำคิวโดยใช้ Redis เป็นที่เก็บข้อมูล
- Laravel Queues
- Gearman
- Beanstalkd
4 แคชเข้าไว้สหาย
ทุกวันนี้ เว็บมีความซับซ้อนมากขึ้น ในแต่ละ Request ที่เข้ามา เว็บแอพลิเคชั่นต้องทำงานมากมายหลายอย่าง นอกจากคุยกับดาต้าเบสแล้ว อาจจะต้องคุยกับ API ข้างนอก อ่าน Configuration รวมข้อมูล ประมวลผล แล้วค่อยเอาออกมาแสดงผลในรูปแบบฟอร์แมทที่เลือกอีก (JSON, XML, HTML, …) ในขั้นตอน Render ก็ยังมี Template Engine มาช่วยในการจัดการเพื่อให้เราสามารถเขียนโค้ดลง Template ได้แบบสวยๆ อีก แล้วทุก Request จะต้องทำแบบเดิมๆ ซ้ำๆ ทุกๆ ครั้ง
สิ่งที่จะช่วยลดระยะเวลาของการทำงานซ้ำๆ นั่นก็คือแคชไว้
- Byte Code Cache แคชประเภทนี้คือ ช่วยอ่านไฟล์ซอสโค้ดของเรามาอ่านเก็บทิ้งไว้ในหน่วยความจำ เสร็จแล้วเวลาเรียกใช้โค้ดชุดเดิม จะได้ไม่ต้องไปอ่านโค้ดมาใหม่ เพราะขั้นตอนการอ่านโค้ดนั้น นอกจากอ่านโค้ดขึ้นมาแล้ว ยังต้องแปลเป็นภาษาที่คอมพิวเตอร์เข้าใจด้วย ถ้าใน PHP ก็จะมี Extension อยู่หลายตัวที่ช่วยในเรื่องนี้ เช่น APC, eAccelerator, Xcache, Opcache ซึ่งในส่วนของ Opcache เอง ก็ถูกผนวกรวมเป็นส่วนหนึ่งของ PHP Engine ตั้งแต่ PHP 5.5
- Application Cache อันนี้อย่าสับสันกับ HTML5 Application cache นะ เพราะเรากะลังจะพูดถึง Cache ในโปรเจคที่เราควรสร้างขึ้น เพื่อเก็บค่าผลลัพท์เอาไว้เวลาที่ต้องประมวลผลอะไรซ้ำๆ บางที อาจจะเป็นแคชที่เก็บข้อมูลที่เราดึงมาจากดาต้าเบสเป็นต้น พวกนี้ลองดู Memcached, Redis, Gibson
- HTTP Cache อย่าเพิ่งมองว่าตอนนี้เรามี Fiber Optic ถึงบ้าน มี ADSL ถึงบ้านแล้ว ความเร็วจะไม่ได้เป็นปัญหานะครับ เพราะสุดท้ายแล้ว การดาวโหลดข้อมูลยังถือเป็นขั้นตอนที่ช้าอยู่ ตรงนี้ HTTP Cache ซึ่งเป็นมาตรฐานส่วนนึงใน HTTP Protocol สามารถเข้ามาช่วยตรงนี้ได้ เพื่อบอกว่า เนื้อหาไหนที่ซ้ำๆ กันก็ไม่ต้องดาวโหลดซ้ำทุกรอบไป เอาของเดิมนั่นแหละไปใช้ได้เลย ซึ่งก็จะลดระยะเวลา Browser ในการ Render page ครับ
- Proxy Cache อันนี้สำหรับเว็บไซต์ที่ใหญ่ๆ ละ ถ้าส่ง Traffic เข้า Server เครื่องเดียว อาจจะประมวลผลไม่ทัน วิธีการเราสามารถใช้ Reverse Proxy ช่วยได้ คือนอกจากสามารถช่วยกระจายโหลดไปยังหลายๆ เครื่อง (ทำตัวเป็น Load Balance) ได้ ยังสามารถแคชไว้ที่ตัวมันเอง เพื่อลดจำนวนครั้งในการส่ง Traffic ไปที่ Server ได้ด้วยครับ พวกนี้สามารถใช้เครื่องมือเช่น Vanish, Nginx, Squid, หรือแม้แต่ Apache ก็สามารถปรับแต่งค่าให้ใช้งานได้เช่นกัน
อย่างไรก็ตาม อย่าลืมว่า การนำแคชไปใช้นั้นมันง่ายครับ แต่ปัญหาอยู่ที่ว่า ตัวแคชเองจะรู้ได้ยังไงว่า เนื้อหาที่แท้จริงได้มีการเปลี่ยนแปลงแล้ว ตรงนี้เป็นปัญหาระดับโลกครับ มีคนเสนอวิธีแก้ออกมามากมาย แต่หลักๆ ก็คือจะมีโปรโตคอลในการจัดการเรียกว่า Cache Invalidation เพื่อบอกให้รู้ว่าแคชที่มีอยู่นั้น มันใช้งานไม่ได้แล้วหละ
There are only two hard things in Computer Science: cache invalidation and naming things.
— Phil Karlton
5 อย่ามานั่ง Query ทีละอัน
ปัญหา N+1 Query Problem เป็นปัญหาที่จะต้องเจอ เวลาที่เราต้องการ Query ข้อมูลออกมาจากดาต้าเบส พูดง่ายๆ คือ ถ้าต้องการจะอ่าน ข้อมูล N record จะมีการสร้าง Query เข้าไป query ข้อมูลในดาต้าเบสทั้งหมด N+1 ครั้ง (1 ครั้งที่เพิ่มขึ้นมา คือ query เพื่อดูว่าทั้งหมดมีกี่ records)
<?php function getUsers() { //... retrieve the users from the database (1 query) return $users; } function loadLastLoginsForUsers($users) { foreach ($users as $user) { $lastLogins = ... // load the last logins for the user (1 query, executed n times) $user->setLastLogins($lastLogins); } return $users; } $users = getUsers(); loadLastLoginsForUsers($users);
ตัวอย่างโค้ดภาษา PHP ที่เมื่อรันแล้ว จะได้ Query ออกมาตามข้างล่าง
SELECT id FROM Users; -- ids: 1, 2, 3, 4, 5, 6... SELECT * FROM Logins WHERE user_id = 1; SELECT * FROM Logins WHERE user_id = 2; SELECT * FROM Logins WHERE user_id = 3; SELECT * FROM Logins WHERE user_id = 4; SELECT * FROM Logins WHERE user_id = 5; SELECT * FROM Logins WHERE user_id = 6; -- ...
ซึ่งจริงๆ อาจจะแปลงเป็น Query ง่ายๆ ได้ในลักษณะนี้
SELECT id FROM Users; -- ids: 1, 2, 3, 4, 5, 6... SELECT * FROM Logins WHERE user_id IN (1, 2, 3, 4, 5, 6, ...);
หรืออาจจะใช้ JOIN ก็ได้ กรณีที่สร้าง Query เอง หรือถ้าใช้ผ่าน ORM ก็คงต้องไปดูฟังก์ชั่นของ ORM ตัวนั้นๆ ว่าจะสามารถช่วยจัดการกับเรื่องเหล่านี้ได้อย่างไร
โดยทั่วไปควรหลีกเลี่ยงการ Query แบบแรกให้มากที่สุดครับ
6 Holizontal Scaling
ข้อสุดท้ายแล้ว สำหรับเรื่องของ Scalability ซึ่งมันแตกต่างจากเรื่องของ Performance นะ Scalability จะมองในภาพที่ว่า ถ้ามี User เพิ่มเข้ามาในระบบ ระบบจะสามารถปรับตัวเพื่อรองรับการใช้งานได้โดยที่ฟังก์ชั่นการใช้งานจะยังใช้งานได้ดังเดิม
- Session เรื่องของ User Session ถ้าสมมุติว่าแอพลิเคชั่นเราถูกทำงานด้วยเซิร์ฟเวอร์เครื่องเดียวคงไม่เป็นปัญหาอะไร แต่ใน PHP Configuration นั้น จะมีการเก็บ User Session เป็นไฟล์ ผลก็คือว่า ถ้าสมมุติว่าเรามีเซิร์ฟเวอร์มากกว่าหนึ่งเครื่อง ก็จะมีปัญหาว่า Session มันผูกแค่อยู่กับเครื่องใดเครื่องหนึ่งเท่านั้น แนะนำให้ใช้ database ในการเก็บ session แทนก็จะดีกว่า
- User file consistency ปัญหาเป็นเรื่องคล้ายๆ กับ Session ก็คือ หลีกเลี่ยงการเก็บข้อมูลไปไว้ที่ Local Storage หรือเซิร์ฟเวอร์ที่ให้บริการ User นั้นแหละ แต่ควรจะเอาไปกองรวมกัน ใน Network Storage มากกว่า อาจจะเป็น NFS หรือ Amazon S3 เป็นต้น
ก็จบแล้วนะครับสำหรับ กฏ 6 ข้อ (หลังๆ ผมแปลสั้นลง ตามความหิวครับ) จริงๆ หลักๆ แล้วผู้เขียนก็ต้องการจะสื่อว่า อย่าไปสนใจเรื่องของ Performance ทั้งๆ ที่โค้ดยังใช้งานไม่ได้มากนัก ทำงานให้มันใช้ได้ก่อนแล้วก็ค่อยมาปรับแต่งเอาก็ยังได้
คุณเป็น Web Developer ที่มีประสบการณ์หรือเปล่าครับ ถ้ามี คุณเห็นด้วยมั๊ยเกี่ยวกับหัวข้อข้างบน หรือจริงๆ แล้วมันควรจะมีอะไรอีก หรือควรเสริมเพิ่มเติมตรงไหน เขียนคอมเมนต์ไว้ด้านล่างนะครับ
(Cover Photo : Flickr – xavier33300 )
เนื้อหาแนะนำ
- บัญญัติ 10 ประการที่ PHP Developer ควรเลิกทำได้แล้ว
- เทรน Web Technology ที่ควรจะรู้ในปี 2015
- โชว์แผล : ความเจ็บปวดของเส้นทางเดินกับ Git