API ที่ทำงานได้อย่างราบรื่นใน staging อาจพังทลายลงทันทีเมื่อต้องเผชิญกับ traffic จริง ความล้มเหลวมักไม่เกิดขึ้นอย่างรุนแรงในทันที แต่มันคือการค่อยๆ ตายลงทีละน้อย (death by a thousand cuts) เธรดที่ถูกบล็อกตรงนี้ คิวรีที่ซ้ำซ้อนตรงนั้น ภายใต้โหลดที่เบาบาง ความไร้ประสิทธิภาพเล็กๆ น้อยๆ เหล่านี้จะถูกซ่อนไว้ แต่ภายใต้แรงกดดันในระดับ production สิ่งเหล่านี้จะสะสมจนกลายเป็นปัญหา thread pool starvation, ฐานข้อมูลทำงานหนักเกินไป (database choking) และ response times ที่ทำลายความเชื่อมั่นของผู้ใช้ การปรับแต่งประสิทธิภาพ (Performance tuning) ไม่ใช่การหาทางแก้ปัญหาแบบมหัศจรรย์เพียงจุดเดียว แต่มันคือการสร้างระบบที่ทุกเลเยอร์ตระหนักถึงความจำกัดของ threads, memory และ database connections เมื่อคุณมองว่าทรัพยากรเหล่านั้นมีจำกัด คุณจะเลิกแก้ปัญหาตามอาการเมื่อระบบล่ม และเริ่มป้องกันไม่ให้มันเกิดขึ้นแทน

กำจัด Sync-over-Async ก่อนที่มันจะทำลายคุณ

รูปแบบที่ทำลายล้างที่สุดในแอปพลิเคชัน ASP.NET Core ที่มี traffic สูงคือ sync-over-async คุณจะเห็นมันเมื่อมีคนเรียกใช้ .Result หรือ .Wait() บน asynchronous method เพราะต้องการค่าในทันทีและไม่อยาก refactor call stack การตัดสินใจนั้นจะบล็อก calling thread ทำให้เธรดนั้นต้องนั่งรอเฉยๆ เพื่อรอผลลัพธ์ที่กำลังทำงานอยู่ที่อื่น แต่ runtime ไม่สามารถนำเธรดนั้นกลับมาใช้ใหม่สำหรับ request อื่นได้

เมื่อมี request จำนวนมากทำเช่นนี้ thread pool จะเกิดอาการ starvation กราฟ CPU ของคุณอาจดูปกติเพราะตัวประมวลผลไม่ได้ทำงานหนัก แต่ latency ของคุณกลับพุ่งสูงขึ้นอย่างรุนแรง Request ต่างๆ จะเข้าคิวรอเธรดที่ไม่มีวันว่าง วิธีแก้ไขนั้นเป็นเรื่องทางเทคนิคแต่ต้องอาศัยวินัย นั่นคือการใช้ await ไปตลอดทั้ง call stack หาก method ใดเรียกใช้ async API ตัวมันเองก็ต้องเป็น async ด้วย ไม่มีทางลัดในเรื่องนี้ ทุกๆ การบล็อกแบบ synchronous ที่คุณกำจัดออกไป จะช่วยเพิ่ม headroom สำหรับ traffic จริงได้มากขึ้น

เลิกเสียทรัพยากรไปกับ Client ที่ตัดการเชื่อมต่อแล้ว

Client ตัดการเชื่อมต่อ เบราว์เซอร์ปิดแท็บ หรือแอปมือถือขาดสัญญาณ หากเซิร์ฟเวอร์ของคุณไม่รู้ว่า client จากไปแล้ว มันจะยังคงรัน database queries, parse JSON และใช้เธรดไปกับการสร้าง response ที่ไม่มีใครได้รับ ให้ส่ง CancellationToken เข้าไปในทุก async operation ที่รองรับ ซึ่งรวมถึง Entity Framework queries, การเรียก HTTP ด้วย HttpClient และงาน background ที่ทำงานต่อเนื่องยาวนาน

เมื่อการเชื่อมต่อหลุด token จะสั่งยกเลิกการทำงานและงานจะหยุดลงทันที สิ่งนี้ช่วยรักษา CPU cycles ของฐานข้อมูลและคืนเธรดกลับสู่ pool ได้เร็วขึ้น มันเป็นการเปลี่ยนแปลงเล็กน้อยใน method signatures ที่ให้ผลตอบแทนมหาศาลเมื่อต้องรับโหลดหนักๆ

แคชข้อมูลที่ไม่เปลี่ยนแปลง

แคตตาล็อกสินค้าของคุณคงไม่ได้เปลี่ยนไปในทุกๆ request และ configuration flags ก็ยิ่งไม่เปลี่ยน แต่ API จำนวนมากกลับเรียกใช้งานฐานข้อมูลซ้ำๆ เพื่อข้อมูล static ชุดเดิม Output caching ใน ASP.NET Core ช่วยให้คุณสามารถเก็บ rendered responses และส่งข้อมูลจาก memory ได้โดยตรง โดยไม่ต้องไปยุ่งกับ controller หรือฐานข้อมูลอีกครั้ง

ใช้การทำ tag-based invalidation อย่างระมัดระวัง เมื่อคุณอัปเดตสินค้า ให้ invalidate เฉพาะ tag ที่เกี่ยวข้องกับหมวดหมู่หรือสินค้านั้นๆ คุณไม่จำเป็นต้องล้าง cache ทั้งหมด วิธีนี้จะช่วยให้ cache hit ratio ของคุณสูงและจำนวนการ query ฐานข้อมูลต่ำลง

แก้ไขการเข้าถึงข้อมูลของคุณก่อนเป็นอันดับแรก

การเรียกใช้งานฐานข้อมูลคือส่วนที่ใช้เวลามากที่สุดใน request ของ API ส่วนใหญ่ ก่อนที่คุณจะปรับแต่งส่วนอื่น ให้เริ่มดูที่นี่ก่อน

หากคุณกำลัง query ข้อมูลเพียงเพื่อนำมาแสดงผลและไม่มีแผนที่จะอัปเดตมัน ให้เติม AsNoTracking() ต่อท้าย Entity Framework queries ของคุณ ซึ่งจะทำให้ EF Core ข้ามขั้นตอนการทำ change tracking และการสร้าง snapshot