NestJSPostgreSQLDrizzle ORMCloudflare PagesVideo ProxyBullMQPuppeteerสถาปัตยกรรมระบบ

จาก Laravel + SQLite สู่ NestJS + PostgreSQL: บทเรียนจากการรีไรต์ระบบสตรีมมิ่งทั้งระบบ

ระบบสตรีมมิ่งที่รับผู้ใช้จริงทุกวัน ไม่ได้ล่มเพราะโค้ดพัง แต่ค่อย ๆ ชนเพดานทีละด้าน — สรุปการย้ายสถาปัตยกรรมจาก 439 commits พร้อม 5 กับดักที่กินเวลามากที่สุด

NS

ทีมวิศวกรรม NextSeries

Core Platform & DevOps Team

~11 นาที

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

บทความนี้คือบันทึกการย้าย NextSeries — เว็บดูหนังและซีรีส์ไทยแบบไม่มีโฆษณา — จาก Laravel 11 + SQLite ไปเป็น NestJS 11 + PostgreSQL + Drizzle ORM ทั้งระบบ ใช้เวลาประมาณ 2.5 เดือน (439 commits ระหว่าง 30 มิ.ย. – 18 ก.ย. 2026) และระบบรัน production อยู่จริง

เราจะไม่เล่าแบบ success story ที่ทุกอย่างราบรื่น แต่จะเล่าทั้งจุดที่ตัดสินใจถูก และกับดักที่กินเวลามากที่สุด เพราะสองอย่างนั้นคือส่วนที่เอาไปใช้ต่อได้จริง

1. บริบท: ระบบเดิมทำอะไรอยู่

NextSeries เป็นแพลตฟอร์มดูหนัง-ซีรีส์สำหรับผู้ชมไทย ที่ทำงานแบบ ad-free proxy:

  1. Crawler (Puppeteer/Chromium) ดึงข้อมูลและลิงก์สตรีมจากแหล่งต้นทาง
  2. Backend แปลงไฟล์ .m3u8 playlist และ rewrite URL ของ segment .ts ทั้งหมดให้ชี้กลับมาที่เซิร์ฟเวอร์เรา
  3. เซิร์ฟเวอร์ โหลด segment จากต้นทาง แล้วส่งต่อให้ผู้ชม — ตัดโฆษณาและ tracker ที่ฝังมาในสตรีมทิ้งไปในจังหวะนั้น

💡 จุดที่ 3 คือหัวใจ และเป็นจุดที่กำหนดว่า stack ไหนจะอยู่รอดในระยะยาว

2. ทำไม Laravel + SQLite ไปต่อไม่ได้

1) SQLite ไม่ใช่ฐานข้อมูลสำหรับงานนี้

SQLite เหมาะกับงาน read-heavy ที่ผู้เขียนมีคนเดียว ตอนออกแบบแรกมันตอบโจทย์ดีมาก — ไฟล์เดียว, backup ง่าย, ย้ายเครื่องง่าย

แต่พอระบบเริ่มมี: crawler เขียนข้อมูลพร้อมกันหลาย worker, admin panel เขียนระหว่างที่ผู้ใช้กำลังอ่าน, และมีรายงาน analytics ที่ query หนัก ๆ — ข้อจำกัดเรื่อง single-writer กลายเป็นคอขวดที่แก้ยังไงก็ไม่หมด

ยิ่งไปกว่านั้น โมเดล multi-tenant (1 backend → N frontends) ที่เราวางไว้ใน roadmap แทบเป็นไปไม่ได้เลยกับไฟล์ฐานข้อมูลที่อยู่เครื่องเดียว

2) PHP-FPM กับ video proxy ไม่เข้าขากัน

การ proxy วิดีโอหมายถึง request ที่ เปิดค้างไว้เป็นนาที ขณะที่โหลดและส่งต่อ segment ต่อเนื่อง

PHP-FPM ถูกออกแบบมาสำหรับ request ที่จบเร็ว จำนวน process จำกัด และแต่ละ process จอง memory ไว้เต็มที่ตลอดช่วงเวลาที่ทำงาน — ผลคือ proxy ไม่กี่สิบคนพร้อมกันก็กิน worker pool จนคำขอปกติของเว็บไม่ได้รับบริการ

Node.js ทำงานตรงข้าม: event loop เดียวจัดการ connection ที่เปิดค้างได้หลายพัน โดยไม่ต้องจอง thread ต่อ request — ตรงกับงาน streaming พอดี

3) งานหนักต้องแยกออกจากเครื่องที่ให้บริการผู้ชม

Crawler ด้วย Chromium กิน RAM และ CPU สูงมาก ถ้ารันบนเครื่องเดียวกับที่ส่งวิดีโอให้ผู้ชม ผู้ชมจะรู้สึกได้ทันทีตอน crawler ทำงาน เราต้องการแยก งานหนัก ออกจาก งานที่ต้องเสถียร อย่างชัดเจน ซึ่ง Laravel queue บนเครื่องเดียวตอบไม่ได้

3. สถาปัตยกรรมใหม่ (2 VPS + Edge)

[ผู้ชม]
   │ HTTPS
   ▼
[Cloudflare Pages — Next.js 16 (React 19) / OpenNext, Edge runtime]
   │ /api/v1
   ▼
[Nginx Reverse Proxy]
   │ :3000
   ▼
[Backend VPS — nextback (185.246.191.232)]
   ├── netflip-backend   NestJS 11          :3000
   ├── netflip-postgres  PostgreSQL 16      :5432 (localhost only)
   └── netflip-redis     Redis 7            :6379 (localhost only)
          ▲
          │ BullMQ jobs
          │
[Scraper VPS — nextfloki (185.246.191.231)]
   └── Puppeteer / Chromium workers
ชั้นเทคโนโลยีเหตุผลที่เลือก
BackendNestJS 11 (TypeScript)โครง module ชัดเจน, DI, เหมาะกับ streaming และ queue
ORMDrizzle ORMtype-safe, migration ชัดเจน, ไม่มี magic ซ่อนหลังฉาก
DatabasePostgreSQL 16multi-writer จริง, พร้อมสำหรับ multi-tenant
QueueBullMQ + Redis 7แยกงานหนักข้ามเครื่องระหว่าง VPS สองตัวได้ราบรื่น
CrawlerPuppeteer 25ทำงานบน Scraper VPS แยกเด็ดขาด ไม่กระทบผู้ชม
FrontendNext.js 16 + OpenNextรันบน Cloudflare Pages กระจายโหลดทั่วโลก
ObservabilityPino + Sentrylog มีโครงสร้าง, เห็น error ก่อนที่ผู้ใช้จะรายงาน

Backend ประกอบด้วย 10 modules หลัก: auth, movies, streams, import, admin, settings, tenant, chat, hermes, devops

Field Experience

4. กับดัก 5 ข้อที่กินเวลามากที่สุด

1Cloudflare Edge runtime crash ตอน deploy ครั้งแรก

อาการคือหน้าเว็บล่มทันทีด้วย error ที่อ่านไม่รู้เรื่อง:

TypeError: Cannot read properties of undefined (reading 'require')
TypeError: Cannot read properties of undefined (reading 'react-rsc')

สาเหตุคือ Next.js 16 ที่ build ด้วย OpenNext ต้องใช้ module.prototype ซึ่ง runtime ของ Cloudflare ไม่ได้จำลองไว้ สิ่งที่ต้องทำมี 3 อย่างพร้อมกัน และขาดอันใดอันหนึ่งไม่ได้:

  1. ตั้ง Compatibility date เป็น 2026-07-10 หรือใหม่กว่า ในหน้า Cloudflare Pages Settings → Runtime
  2. เปิด flag nodejs_compat ทั้งใน wrangler.jsonc และในแดชบอร์ด
  3. ฉีด shims ที่หัวไฟล์ dist/_worker.js ก่อน deploy ทุกครั้ง (ในโปรเจกต์เราทำผ่าน copy-pages.js ซึ่งจะเซ็ต process.env.NEXT_RUNTIME = 'edge' และจำลอง module.prototype)

⚠️ บทเรียน: ทั้งสามข้อเป็นเงื่อนไข AND ไม่ใช่ OR — ตั้ง compat date อย่างเดียว หรือเปิด flag อย่างเดียว ยัง crash อยู่ดี

2Video proxy ต้อง stream จริง ไม่ใช่ buffer

กับดักที่ทำให้ RAM พุ่งกระฉูดคือการเขียน proxy แบบ "ดาวน์โหลดให้ครบทั้งก้อนก่อน แล้วค่อยส่ง" ซึ่งฟังดูปกติแต่ผิดหลักการสตรีมมิ่งอย่างสิ้นเชิง

Segment เดียวอาจใหญ่หลาย MB และผู้ชมหนึ่งคนโหลดหลายร้อย segment ต่อตอน — ถ้า buffer ไว้ในหน่วยความจำก่อนส่ง RAM จะพุ่งตามจำนวนผู้ชมพร้อมกันแบบทบต้นจน Node process โดน OOM Kill ทันที

✅ ทางออก: pipe stream ตรงจาก upstream ไปยัง response โดยไม่เก็บลง memory และให้ backpressure ของ Node.js จัดการจังหวะเอง

3Bandwidth เป็นสองเท่าโดยธรรมชาติ

สถาปัตยกรรม proxy หมายความว่าเราทั้ง โหลด segment จากต้นทาง และ ส่ง ต่อให้ผู้ชม — ใช้ bandwidth สองเท่าต่อผู้ชมหนึ่งคน

เราแก้ด้วย segment cache บนดิสก์: segment ที่เพิ่งโหลดมาและมีโอกาสถูกเรียกซ้ำ (เช่น ตอนผู้ใช้ seek กลับ หรือมีคนดูตอนเดียวกันพร้อมกัน) ถูกเก็บลงดิสก์พร้อม LRU auto-eviction แทนการดึงซ้ำ

📌 อีกจุดสำคัญ: segment ต้องถูก bypass ออกจาก Cloudflare cache (ผ่าน Cache Rules) เพราะเป็นไฟล์วิดีโอขนาดใหญ่ที่ผิดเงื่อนไขการใช้งานแพลนฟรี และเสี่ยงเจอ 524 gateway timeout

4ย้ายข้อมูล SQLite → PostgreSQL แบบ 1:1

เราตัดสินใจ ไม่ redesign schema ในการย้ายรอบแรก แต่ยกโครงเดิม 22 ตารางมา 1:1 ก่อน

ผลคือ schema ยังมีคอลัมน์ legacy ติดมาด้วย เช่น file_main, file_openload, file_streamango ที่เป็นชื่อผู้ให้บริการสตรีมยุคเก่า — ดูไม่สวย แต่มีเหตุผลสำคัญ: ทำให้เทียบข้อมูลเก่า-ใหม่ได้ตรงบรรทัด และลดจำนวนตัวแปรที่จะพังพร้อมกันในวัน deploy

เราเก็บงาน "ทำ schema ให้สะอาด" ไว้เป็นงานแยกหลังระบบนิ่งแล้ว

5ความเข้ากันได้ย้อนหลังของ API

Frontend และ crawler เป็นคนละส่วนที่ deploy คนละจังหวะ จึงต้องมีช่วงที่ระบบเก่ากับใหม่คุยกันได้

เราแก้โดยให้ path เดิมอย่าง /api/movies ถูก rewrite ไปที่ /api/v1/movies ภายใน backend — ทำให้ client เก่ายังทำงานได้ระหว่างการเปลี่ยนผ่าน แล้วค่อยทยอยย้าย client ทีละตัว

5. ผลลัพธ์หลังการย้ายระบบ

ฐานข้อมูล Multi-Writer จริง

Crawler, admin panel และ analytics เขียนพร้อมกันได้โดยไม่ต้องจัดคิวแย่ง single lock แบบ SQLite

Proxy ทนโหลดสูงขึ้น

Request ที่เปิดค้างยาวหลายนาทีไม่กิน process pool แบบที่ PHP-FPM ทำ ใช้หน่วยความจำคงที่

งานหนักแยกเครื่องเด็ดขาด

Crawler ที่กิน CPU/RAM ทำงานบน Scraper VPS อีกเครื่อง ไม่กระทบคุณภาพการเล่นของผู้ชมแม้แต่น้อย

Multi-Tenant เป็นไปได้จริง

Backend เดียวรองรับหลาย frontend ผ่าน tenant module และ NEXT_PUBLIC_SITE_KEY

6. บทเรียน 5 ข้อสำหรับการย้าย Stack

1

ย้ายทีละชั้น ไม่ใช่ทีละ feature

ย้ายฐานข้อมูลให้เสร็จก่อน แล้วค่อยปรับโค้ด ดีกว่าทำพร้อมกันทั้งสองด้านจนไม่รู้ว่าจุดไหนพัง

2

คง schema เดิมไว้ก่อน

การ redesign schema พร้อมกับเปลี่ยน framework ทำให้ debug ยากขึ้นเป็นเท่าตัว ยอมรับ legacy ชั่วคราว

3

เลือก stack ตามรูปร่างของ workload

Video proxy เป็น long-lived connection ซึ่งเป็นจุดที่ Node ชนะ PHP อย่างชัดเจน ไม่ใช่เพราะ Node "ใหม่กว่า"

4

Runtime ที่ไม่ใช่ Node ต้องมี shim

ถ้า deploy บน Edge ให้ถือว่าต้องมีชั้นปรับความเข้ากันได้ และต้องทดสอบ deploy จริงตั้งแต่วันแรก

5

แยกงานหนักออกจากงานที่ต้องเสถียร

การแยก 2 VPS (Backend vs Scraper) เป็นการตัดสินใจเชิงสถาปัตยกรรมที่ให้ผลตอบแทนสูงที่สุดในโปรเจกต์นี้

สรุป

การย้ายจาก Laravel + SQLite มาเป็น NestJS + PostgreSQL ไม่ใช่เพราะ Laravel ไม่ดี — แต่มันถูกออกแบบมาสำหรับ workload คนละแบบกับที่ระบบ NextSeries เติบโตไปเป็น

ถ้าคุณกำลังเจออาการเดียวกัน — ฐานข้อมูลที่เขียนพร้อมกันไม่ได้, worker ที่กิน CPU จนเว็บช้า, หรือ request ที่เปิดค้างนานจน process pool หมด — นั่นคือสัญญาณว่า stack เดิมชนเพดานแล้ว ไม่ใช่แค่ต้อง tune ให้ดีขึ้น

NS

ทีมวิศวกรรม NextSeries

Core Platform & DevOps Team

ทีมวิศวกรรมผู้พัฒนา NextSeries ดูแลตั้งแต่การออกแบบระบบ Video Proxy, การจัดการโครงสร้างพื้นฐาน 2 VPS บน Flokinet ไปจนถึงการพัฒนาหน้าบ้าน Next.js บน Cloudflare Pages