นักพัฒนาแอปถ่ายภาพสำหรับงานอีเวนต์มีรายการตรวจสอบ (checklist) ที่ชัดเจนแล้วในการรักษาการอัปโหลดผ่านเบราว์เซอร์ให้ทำงานได้อย่างต่อเนื่องท่ามกลางสถานที่ที่มีคนหนาแน่น ผู้ใช้อาจสลับการเชื่อมต่อจาก Wi-Fi ไปเป็นข้อมูลมือถือ (cellular) ในขณะที่อุปกรณ์หลายสิบเครื่องกำลังแย่งชิงสัญญาณจาก hotspot เดียวกัน คู่มือนี้จะแสดงวิธีป้องกันไม่ให้รูปภาพหายไปหลังจากขึ้นข้อความแจ้งเตือน "upload complete" แม้ว่าแขกจะล็อกหน้าจอมือถือหรือเครือข่ายจะเกิดปัญหาขัดข้องก็ตาม
ทำไมการอัปโหลดแบบธรรมดาถึงล้มเหลวในงานแต่งงานและเทศกาลต่างๆ
ในออฟฟิศ แล็ปท็อปเชื่อมต่อผ่านสาย Ethernet ที่เสถียรและผู้ใช้เพียงคนเดียวคลิก "ส่ง" แต่ในงานเลี้ยงฉลองแต่งงานหรือเทศกาลดนตรี การกระทำเดียวกันนี้อาจก่อให้เกิดปัญหาตามมาเป็นทอดๆ เช่น แขกเดินจากหอประชุมไปยังลานจอดรถ, เราเตอร์รับภาระไม่ไหวเนื่องจากมีโทรศัพท์หลายร้อยเครื่องเชื่อมต่ออยู่ หรือโทรศัพท์หลุดจาก Wi-Fi แล้วสลับไปใช้ข้อมูลมือถือ เบราว์เซอร์อาจจะส่งข้อมูลทุกไบต์ไปยังเซิร์ฟเวอร์แล้ว แต่เซิร์ฟเวอร์ยังไม่ได้บันทึกไฟล์ลงในพื้นที่จัดเก็บ หาก UI ประกาศว่าสำเร็จทันทีที่แถบความคืบหน้า (progress bar) ถึง 100% แขกอาจลบรูปภาพนั้นทิ้งไป ทำให้ผู้จัดงานต้องเจอกับปัญหาไฟล์สูญหาย
ต้นทุนที่ซ่อนอยู่ของคำว่า “แค่กดอัปโหลด"
วิธีการแบบง่ายๆ (naïve approach) มักจะมองว่าการอัปโหลดคือการส่ง HTTP POST เพียงครั้งเดียว ซึ่งจะใช้งานได้ดีเมื่อการเชื่อมต่อเสถียร แต่ในเครือข่ายที่มีความหนาแน่นสูง ทุกครั้งที่มีการขัดข้องจะทำให้ต้องเริ่มส่งไฟล์ใหม่ทั้งหมด ผู้ใช้จะรู้สึกหงุดหงิดและเกิดการใช้แบนด์วิดท์พุ่งสูงขึ้นเมื่อโทรศัพท์หลายสิบเครื่องพยายามส่งใหม่พร้อมกัน การแบ่งไฟล์ออกเป็นส่วนๆ (chunks) และติดตามแต่ละส่วนอาจเพิ่มความซับซ้อน แต่ผลลัพธ์ที่ได้คือการรับส่งข้อมูลที่คาดการณ์ได้และใช้ทรัพยากรต่ำ ซึ่งสามารถทำงานต่อได้แม้มีการสลับเครือข่าย
การสร้างระบบอัปโหลดแบบแบ่งส่วนและสามารถอัปโหลดต่อได้ (resumable, chunked upload)
ด้านล่างนี้คือขั้นตอนการปฏิบัติที่เป็นรูปธรรม
1. สร้าง upload ID ก่อนที่จะมีการส่งข้อมูลออกจากเบราว์เซอร์
สร้าง Universally Unique Identifier (UUID) ขึ้นในเครื่องและส่งไปยังเซิร์ฟเวอร์ในการร้องขอครั้งแรก เซิร์ฟเวอร์จะบันทึกเซสชันภายใต้ ID นั้น หากเบราว์เซอร์ต้องลองใหม่ในภายหลังเนื่องจากหมดเวลา (timeout) มันจะส่ง UUID เดิมไปด้วย ทำให้เซิร์ฟเวอร์สามารถจดจำเซสชันและหลีกเลี่ยงการสร้างข้อมูลซ้ำซ้อน สิ่งนี้ทำให้เวิร์กโฟลว์เป็นแบบ idempotent ซึ่งหมายความว่าการส่งคำขอเดิมซ้ำๆ จะไม่มีผลกระทบในทางลบ
2. แบ่งไฟล์ออกเป็นส่วนๆ (chunks) ขนาด 5–10 MB
ขนาดของ chunk คือการแลกเปลี่ยน (trade-off) หาก chunk มีขนาดเล็กเกินไป (ต่ำกว่า 1 MB) จะทำให้จำนวน HTTP requests เพิ่มขึ้นและเพิ่มภาระของ header ที่เกี่ยวข้อง แต่ถ้า chunk มีขนาดใหญ่เกินไป การขัดข้องเพียงครั้งเดียวจะส่งผลเสียมากเพราะไคลเอนต์ต้องส่งข้อมูลชิ้นใหญ่ใหม่ สำหรับรูปภาพทั่วไปและวิดีโอสั้นๆ ขนาด 5–10 MB ถือเป็นจุดที่สมดุล: แต่ละคำขอจะเสร็จสิ้นเร็วพอที่จะทำให้ UI ตอบสนองได้ดี ในขณะที่จำนวนคำขอก็ยังอยู่ในระดับที่จัดการได้
3. จำกัดจำนวนการอัปโหลดแบบขนาน (parallel uploads)
เบราว์เซอร์บนมือถือสามารถเปิดการเชื่อมต่อได้หลายช่องทาง แต่ในเครือข่าย Wi-Fi ที่หนาแน่น แต่ละช่องทางที่เพิ่มขึ้นจะแย่งชิงแบนด์วิดท์ที่มีจำกัด การมี 2 ช่องทางที่เสถียรย่อมดีกว่าการมี 8 ช่องทางที่แย่งกันทำงาน ควรใช้ navigator.connection API เพื่อตรวจจับสภาวะแบนด์วิดท์ต่ำและลดจำนวนการทำงานแบบขนาน (concurrency) โดยอัตโนมัติ
4. บันทึกสถานะการอัปโหลดไว้ใน IndexedDB
เก็บ upload ID, รายการ chunk ที่ส่งไปแล้ว และตำแหน่ง (offsets) ที่เซิร์ฟเวอร์ยืนยันแล้วไว้ใน IndexedDB ของเบราว์เซอร์ หากหน้าเว็บถูกรีโหลดหรือผู้ใช้ปิดแท็บไป ไคลเอนต์จะสามารถกู้คืนสถานะได้ในการโหลดครั้งถัดไป เมื่อผู้ใช้เปิดหน้าเว็บขึ้นมาใหม่ ให้แจ้งเตือนให้พวกเขาเลือกไฟล์เดิม; เมทาดาตา (metadata) ที่เก็บไว้จะช่วยให้การอัปโหลดสามารถทำต่อจาก chunk ล่าสุดที่ได้รับการยืนยัน แทนที่จะต้องเริ่มใหม่ทั้งหมด
5. ตรวจสอบการเปลี่ยนแปลงของเครือข่ายจริงๆ ไม่ใช่แค่ใช้ navigator.onLine
ตัวบ่งชี้ navigator.onLine มักจะรายงานว่า "online" แม้ว่าการเชื่อมต่อจะใช้งานไม่ได้ก็ตาม ดังนั้น ควรตั้งค่า request timeout สั้นๆ (เช่น 5 วินาที) สำหรับแต่ละ chunk หากเกิด timeout ให้ถือว่าเครือข่ายขัดข้อง เมื่อการเชื่อมต่อกลับมา ให้สอบถามเซิร์ฟเวอร์เกี่ยวกับรายการ chunk ที่มีอยู่แล้ว จากนั้นจึงอัปโหลดเฉพาะส่วนที่ยังขาดอยู่ วิธีนี้จะช่วยหลีกเลี่ยงการส่งข้อมูลซ้ำซ้อนหลังจากเครือข่ายขัดข้องเพียงชั่วคราว
6. ใช้เทคนิค exponential backoff พร้อมกับ jitter สำหรับการลองใหม่ (retries)
เมื่ออุปกรณ์ของแขกจำนวนมากตรวจพบว่าเครือข่ายกลับมาใช้งานได้ ทุกเครื่องอาจจะส่งคำขอใหม่พร้อมกันจนทำให้เซิร์ฟเวอร์รับภาระหนักเกินไป เทคนิค exponential backoff จะทำให้การลองใหม่แต่ละครั้งใช้เวลารอนานขึ้นกว่าครั้งก่อนหน้า ในขณะที่ jitter จะเพิ่มค่าสุ่ม (random offset) เล็กน้อย การผสมผสานนี้จะช่วยกระจายปริมาณการส่งคำขอใหม่ในช่วงเวลาไม่กี่วินาที เพื่อป้องกันไม่ให้เกิดการพุ่งสูงขึ้นของข้อมูลอย่างกะทันหัน
7. แสดงสถานะแบบเป็นลำดับชั้นที่เข้าถึงง่าย
แถบสถานะแบบสามระดับจะช่วยสื่อสารสถานะที่แท้จริงของไฟล์:
- Received – เซิร์ฟเวอร์ได้จัดเก็บทุก chunk และทำเครื่องหมายว่าไฟล์เสร็จสมบูรณ์แล้ว
- Preparing – เซิร์ฟเวอร์กำลังสร้างรูปภาพตัวอย่าง (thumbnails) หรือแปลงไฟล์วิดีโอ (transcoding)
- Available – ผู้จัดงานสามารถดูหรือดาวน์โหลดไฟล์ได้
หลีกเลี่ยงการใช้เพียงสีเพียงอย่างเดียว ควรใช้ไอคอนควบคู่ไปกับข้อความสั้นๆ เพื่อให้ผู้ใช้งานที่ใช้เครื่องอ่านหน้าจอ (screen-reader) สามารถเข้าใจความคืบหน้าได้เช่นกัน
สิ่งที่อาจจะยังผิดพลาดได้มีอะไรบ้าง?
แม้แต่การทำ resumable upload ที่ออกแบบมาอย่างดี ก็อาจจะพบปัญหาในบาง edge cases ได้
สิ่งที่ควรจับตามองต่อไป
แพลตฟอร์มเว็บกำลังมีการพัฒนาอย่างต่อเนื่อง
บทสรุป
การอัปโหลดแบบ chunked upload ที่สามารถอัปโหลดต่อได้ ซึ่งมีการติดตามแต่ละส่วน เก็บสถานะไว้ในเครื่อง และมีการ retry อย่างชาญฉลาด จะเปลี่ยนเครือข่ายในงานอีเวนต์ที่ไม่เสถียรให้กลายเป็นช่องทางที่เชื่อถือได้สำหรับการส่งรูปภาพจากแขกในงาน โปรดนำรายการตรวจสอบ (checklist) ด้านบนไปปรับใช้
