: / ฐานความรู้ / โครงสร้างระบบของ ERPNext

โครงสร้างระบบของ ERPNext


Created:3/3/2026


Frappe Framework ที่ใช้สร้าง ERPNext คือ full-stack application ที่รวม web server, background jobs worker, scheduler, WebSocket Support และอีกหลายเซอร์วิสไว้ด้วยกัน

การที่เราเข้าใจว่าเซอร์วิสเหล่านี้ทำงานด้วยกันอย่างไร เป็นสิ่งสำคัญที่เราควรจะรู้ก่อนที่จะ Deploy ใน Production (ไม่ว่าจะเป็น bare-metal, vm หรือ container)

บทความนี้จะพูดถึง 4 ประเด็นหลักๆของระบบ:

  1. พื้นที่จัดเก็บข้อมูลถาวร - ข้อมูลใดบ้างที่เราควรจัดเก็บและ Backup
  2. ภาพรวมเซอร์วิส - การทำงานร่วมกันของระบบต่างๆ
  3. Domain Name Routing - เส้นทาง / ขั้นตอนการเดินทางของ HTTP request ไปสู่เว็บไซต์ของเรา
  4. โครงสร้าง Folder ของ Frappe - โครงสร้างโฟลเดอร์ภายใน ~/bench/sites

พื้นที่จัดเก็บข้อมูลถาวร

Excalidraw diagram

Frappe มีโฟลเดอร์ทั้งหมด 3 โฟลเดอร์ที่เราควรจะต้องดูแลและ Backup เป็นพิเศษ

ป้ายชื่อพื้นที่จัดเก็บพาธเริ่มต้นสิ่งที่เก็บ
AFrappe Storage~/bench/sitesไฟล์ไซต์, ไฟล์อัปโหลด, ไฟล์ส่วนตัว, และการตั้งค่าต่อไซต์ (site_config.json)
BMariaDB Storage/var/lib/mysqlข้อมูลใน Database ทั้งหมด - DocTypes, ธุรกรรม, ข้อมูลผู้ใช้ ฯลฯ
CRedis Queue Storage/dataคิวของ Worker

ทำไมสิ่งนี้จึงสำคัญในทางปฏิบัติ

เมื่อคุณรัน Frappe บน Docker โฟลเดอร์ทั้งสามนี้จะต้องใช้ named volume หรือ bind mount รองรับ หากลืมโฟลเดอร์ใดโฟลเดอร์หนึ่งจะทำให้เกิดการสูญหายของข้อมูล:

  • การสูญหายของ A (~/bench/sites) จะลบไฟล์ที่อัปโหลดและการตั้งค่าไซต์ - แอปอาจบูตได้แต่จะกำหนดค่าผิดพลาดทั้งหมด
  • การสูญหายของ B (/var/lib/mysql) ข้อมูลใน Database จะหายทั้งหมด - เอกสารการเงิน การบัญชี และอื่นๆที่อยู่ใน Database
  • การสูญหายของ C (/data) จะทำให้งาน background ที่อยู่ในคิวแต่ยังไม่ได้รันหายไป

Redis Cache ไม่รวมอยู่ในรายการนี้โดยตั้งใจ - ถ้า Cache หายระบบจะดึงข้อมูลมาเก็บใหม่โดยอัตโนมัติ อยู่แล้ว จึงไม่ต้องกังวลอะไร


ภาพรวมเซอร์วิส

Excalidraw diagram

การ deploy Frappe ใน production ประกอบด้วยกระบวนการที่ทำงานร่วมกันหลายอย่าง แผนภาพข้างบนแบ่งพวกมันออกเป็นสามหมวด

เว็บเซอร์วิส (Web Services)

Nginx (พอร์ต 80)

Nginx ที่ที่ทุก request จากบราวเซอร์จะมาถึงที่นี่ก่อน โดยอิงจาก URL path, Nginx จะตัดสินใจว่าจะไปทางไหน:

Pathการกระทำ
/assets/*ส่งไฟล์ static โดยตรงจาก ~/bench/sites/assets/
/files/*ส่งไฟล์ที่อัปโหลดแบบสาธารณะจาก ~/bench/sites/$host/public/files/
/protected/*ส่งไฟล์ส่วนตัวจาก ~/bench/sites/$host/private/files/ (ผ่าน X-Accel-Redirect - Frappe web server อนุมัติ request ก่อน แล้ว Nginx จึงส่งไฟล์)
/socket.io/*Reverse-proxy ไปยัง WebSocket service
/* (ทุกอย่างอื่น)Reverse-proxy ไปยัง Gunicorn web server

WebSocket / Socket.IO (พอร์ต 9000)

คำสั่งที่ใช้ (โดยง่าย): node frappe-bench/apps/frappe/socketio.js

จัดการฟีเจอร์ real-time: การแจ้งเตือนบนเดสก์ท็อป, การอัปเดต form แบบสด, ความคืบหน้าการพิมพ์, และ push event อื่นๆ ไคลเอนต์เปิดการเชื่อมต่อถาวรที่นี่ โดย Frappe web server จะส่ง event ไปยังกระบวนการนี้เมื่อมีการเปลี่ยนแปลง

Frappe Web Server / Gunicorn (พอร์ต 8000)

คำสั่งที่ใช้ (โดยง่าย): frappe-bench/env/bin/gunicorn frappe.app:application

แอปพลิเคชัน Python WSGI หลัก การ render หน้าเว็บ, API call, และการส่ง form ทุกครั้งจะจบลงที่นี่ Gunicorn จะ spawn worker process หลายตัวเพื่อรองรับ request พร้อมกัน

บริการ Background (Background Services)

คำสั่งที่ใช้ (โดยง่าย): bench worker / bench scheduler

Frappe ใช้ คิวงานที่ขับเคลื่อนด้วย Redis เพื่อรันงานแบบ asynchronous มี worker process 4 ตัว:

Workerวัตถุประสงค์
Shortงานเร็วและเบา (ส่งอีเมลเดียว, อัปเดต cache entry)
Defaultงาน background มาตรฐาน
Longงานหนักหรืองานที่ใช้เวลานาน (bulk imports, การสร้างรายงาน, scheduled reports)
Schedulerอ่าน scheduled tasks แบบ cron ที่กำหนดไว้ในแต่ละแอปและส่งงานเข้าคิวที่เหมาะสม

Scheduler ไม่รันงานโดยตรง - มันเพียงแค่ enqueue งาน Workers Short/Default/Long จะดึงงานจากคิวและรัน

บริการสนับสนุน (Support Services)

คำสั่งที่ใช้ (โดยง่าย):

บริการพอร์ตบทบาท
MariaDB3306ฐานข้อมูลเชิงสัมพันธ์ - แหล่งความจริงสำหรับข้อมูลแอปพลิเคชันทั้งหมด
Redis Queue6379คิวงาน - workers อ่านจากที่นี่
Redis Cache6379Cache ระดับแอปพลิเคชัน - เพิ่มความเร็วในการโหลดหน้าเว็บและ query ซ้ำ

หมายเหตุ: Redis Queue และ Redis Cache มักรันเป็น Redis instance แยกกัน (พอร์ตต่างกันหรือ container service ต่างกัน) แม้ว่าทั้งคู่จะใช้พอร์ต 6379 โดยปกติ ตรวจสอบ common_site_config.json ของคุณเพื่อดูที่อยู่จริง

Bench CLI

bench คือเครื่องมือ command-line ที่ใช้จัดการการติดตั้ง Frappe ทั้งหมด - การสร้างไซต์, การติดตั้งแอป, การรัน migrations, และการเริ่ม/หยุดบริการ มันไม่ใช่ daemon ที่รันตลอดเวลา แต่ใช้โดย administrators แบบ interactive


ชื่อโดเมนไซต์: ส่วนประกอบสำคัญสำหรับ Production

Excalidraw diagram

หนึ่งในการตัดสินใจออกแบบที่โดดเด่นที่สุดของ Frappe คือ ชื่อโดเมนคือตัวระบุไซต์ Frappe bench เดียวสามารถโฮสต์หลายไซต์ได้ และชื่อโดเมนของ request ขาเข้าคือสิ่งที่กำหนดว่าจะใช้ไซต์ใด (และฐานข้อมูลใด)

วิธีการทำงาน

  1. Nginx ถูกกำหนดค่าด้วย server_name ที่ตรงกับโดเมนของไซต์:

    server {
      listen 80;
      server_name your-domain.com;
      root /home/frappe/frappe-bench/sites;
      ...
    }
  2. Nginx ฉีดชื่อโดเมนเข้า HTTP header ที่กำหนดเอง ก่อนส่งต่อ request:

    # Proxy pass ไปยัง bench-socketio
    location /socket.io/ {
      proxy_set_header X-Frappe-Site-Name $host;
      ...
      proxy_pass http://bench-socketio;
    }
    
    # Proxy pass ไปยัง bench-web
    location / {
      proxy_set_header X-Frappe-Site-Name $host;
      ...
      proxy_pass http://bench-web;
    }
  3. แอปพลิเคชัน Frappe อ่าน X-Frappe-Site-Name และค้นหาโฟลเดอร์ที่ตรงกันภายใต้ ~/bench/sites/ หากโฟลเดอร์ชื่อ your-domain.com มีอยู่ Frappe จะเปิด your-domain.com/site_config.json เพื่อรับข้อมูล credentials ของฐานข้อมูล:

    {
      "db_name": "_16d15c20aae1ed29",
      "db_password": "a78rFm5ToTLHxQfA",
      "db_type": "mariadb",
      "db_user": "_16d15c20aae1ed29",
      "encryption_key": "...="
    }

ผลกระทบในทางปฏิบัติ

  • ชื่อโฟลเดอร์ต้องตรงกับชื่อโดเมน ที่ส่งถึง Nginx หากไม่ตรงกัน (เช่น www.your-domain.com กับ your-domain.com) Frappe จะรายงานว่าไม่มีไซต์นั้น
  • เนื่องจากโดเมนมีความสำคัญเชิงโครงสร้าง (มันคือชื่อไดเรกทอรี) การเปลี่ยนชื่อไซต์ จึงต้องย้ายโฟลเดอร์และอัปเดต DNS และ Nginx config

โครงสร้างโฟลเดอร์ไซต์ Frappe

Excalidraw diagram

ทุกอย่างภายใต้ ~/bench/sites แบ่งออกเป็นสองหมวดกว้างๆ: ไฟล์แอปพลิเคชัน (artifacts ที่สร้างขึ้นส่วนใหญ่เมื่อ build/install) และ พื้นที่จัดเก็บไซต์ (ข้อมูลถาวรจริงที่ไม่ซ้ำกันสำหรับแต่ละไซต์)

~/bench/sites/
├── apps.json           # Metadata เกี่ยวกับแอปที่ติดตั้ง
├── apps.txt            # รายการแอปที่ติดตั้งตามลำดับ
├── assets/             # CSS, JS ที่ build แล้ว และ frontend artifacts อื่นๆ
├── common_site_config.json   # การตั้งค่า global ที่ใช้ร่วมกันโดยทุกไซต์
├── your-domain.com/    # หนึ่งโฟลเดอร์ต่อหนึ่งไซต์
│   ├── site_config.json
│   ├── public/
│   │   └── files/      # ไฟล์ที่อัปโหลดซึ่งเข้าถึงได้สาธารณะ
│   └── private/
│       └── files/      # ไฟล์ที่อัปโหลดแบบส่วนตัว (ต้องมีการยืนยันตัวตนในการดาวน์โหลด)
└── another-domain.com/ # อีกไซต์หนึ่ง

ไฟล์แอปพลิเคชัน (apps.json, apps.txt, assets/)

สิ่งเหล่านี้คือ artifacts จาก bench build และ bench install-app ไม่ใช่ข้อมูลผู้ใช้ แต่เป็นสิ่งที่ได้จาก source code ของแอป ในการตั้งค่าที่ใช้ Docker ไฟล์เหล่านี้ควร build เข้าไปใน image หรือ copy/link จาก image เมื่อ container เริ่มต้น - ไม่จำเป็นต้องอยู่ใน persistent volume เพราะสามารถสร้างซ้ำจาก source ได้

การตั้งค่า Global (common_site_config.json)

ไฟล์นี้เก็บการตั้งค่าที่ใช้กับทุกไซต์บน bench - ที่อยู่ Redis, ขีดจำกัดขนาดไฟล์, developer mode flags ฯลฯ คุณสามารถจัดการได้ด้วย:

bench set-config -g redis_queue redis://redis-queue:6379

แฟล็ก -g จะเขียนไปยัง common_site_config.json แทนที่จะเป็น config ของไซต์ใดไซต์หนึ่ง

คุณสามารถถือว่าไฟล์นี้เป็น persistent storage (mount เป็น volume) หรือ สร้างใหม่แบบ dynamic เมื่อเริ่มต้นโดยใช้ bench set-config วิธีหลังนี้เป็นเรื่องปกติในสภาพแวดล้อม container ที่ configuration ถูกฉีดผ่าน environment variables

พื้นที่จัดเก็บไซต์ (your-domain.com/)

นี่คือ พื้นที่จัดเก็บข้อมูลถาวรจริง สำหรับแต่ละไซต์ ประกอบด้วย:

  • site_config.json - credentials ของฐานข้อมูลและการตั้งค่าเฉพาะไซต์
  • public/files/ - ไฟล์ที่ผู้ใช้อัปโหลดซึ่งเข้าถึงได้สาธารณะ (เช่น รูปภาพสินค้า)
  • private/files/ - ไฟล์ที่ผู้ใช้อัปโหลดซึ่งต้องมีการยืนยันตัวตนในการเข้าถึง (เช่น สลิปเงินเดือน, เอกสารลับ)

โฟลเดอร์นี้ต้องอยู่ใน persistent volume หากสูญหาย ไฟล์ที่อัปโหลดจะหายไปและไซต์จะสูญเสียข้อมูล database connection


สรุป

ประเด็นสิ่งสำคัญ
Persistent Storageสาม volumes: ~/bench/sites, /var/lib/mysql, Redis /data
ServicesNginx → Gunicorn + Socket.IO, รองรับโดย Workers + Scheduler + MariaDB + สอง Redis instances
Domain Routingชื่อโดเมน = ชื่อโฟลเดอร์ไซต์; ส่งผ่าน header X-Frappe-Site-Name
Folder Layoutassets/ คือ build artifact; your-domain.com/ คือข้อมูลเดียวที่คุณต้องคงไว้

การเข้าใจสถาปัตยกรรมนี้ทำให้ง่ายต่อการออกแบบการ deploy ที่แข็งแกร่ง - ไม่ว่าจะเขียน docker-compose.yml, Helm chart, หรือ Ansible playbook แบบ bare-metal สิ่งไม่แปรผันหลักยังคงเหมือนเดิมเสมอ: รักษา three persistent volumes ให้ปลอดภัย, ทำให้ชื่อโดเมนสอดคล้องกัน, และให้บริการต่างๆ สื่อสารผ่านพอร์ตที่กำหนด


อยากได้คนช่วยคิดและลงมือทำ?เราพร้อมช่วยคุณวางแผนและลงมือทำให้ง่าย และรวดเร็ว
จ้างเรา