KoderKoder.ai
ราคาองค์กรการศึกษาสำหรับนักลงทุน
เข้าสู่ระบบเริ่มต้นใช้งาน

ผลิตภัณฑ์

ราคาองค์กรสำหรับนักลงทุน

ทรัพยากร

ติดต่อเราสนับสนุนการศึกษาบล็อก

กฎหมาย

นโยบายความเป็นส่วนตัวข้อกำหนดการใช้งานความปลอดภัยนโยบายการใช้งานที่ยอมรับได้แจ้งการละเมิด

โซเชียล

LinkedInTwitter
Koder.ai
ภาษา

© 2026 Koder.ai สงวนลิขสิทธิ์

หน้าแรก›บล็อก›Object storage vs database blobs สำหรับการอัปโหลดที่เร็วและประหยัด
28 ธ.ค. 2568·2 นาที

Object storage vs database blobs สำหรับการอัปโหลดที่เร็วและประหยัด

ที่เก็บแบบวัตถุกับบล็อบส์ในฐานข้อมูล: เก็บเมตาดาต้าไฟล์ใน Postgres เก็บไบต์ใน object storage และรักษาการดาวน์โหลดให้เร็วพร้อมค่าใช้จ่ายที่คาดการณ์ได้

Object storage vs database blobs สำหรับการอัปโหลดที่เร็วและประหยัด

ปัญหาจริง ๆ ของการอัปโหลดโดยผู้ใช้

การอัปโหลดที่ผู้ใช้ทำดูเรียบง่าย: รับไฟล์ เก็บไว้ แล้วแสดงภายหลัง นั่นใช้ได้เมื่อมีผู้ใช้น้อยและไฟล์เล็ก แต่เมื่อปริมาณเพิ่มขึ้น ไฟล์ใหญ่ขึ้น ปัญหาจะโผล่ในจุดที่ไม่เกี่ยวกับปุ่มอัปโหลดโดยตรง

การดาวน์โหลดช้าลงเพราะเซิร์ฟเวอร์แอปหรือฐานข้อมูลต้องทำงานหนัก การสำรองข้อมูลกลายเป็นขนาดใหญ่และช้า การกู้คืนใช้เวลานานเมื่อคุณต้องการที่สุด ค่าเก็บข้อมูลและแบนด์วิดท์ (egress) อาจพุ่งเพราะไฟล์ถูกเสิร์ฟอย่างไม่มีประสิทธิภาพ ถูกคัดลอกซ้ำ หรือไม่เคยล้างทิ้ง

สิ่งที่คุณมักต้องการคือความน่าเชื่อถือและน่าเบื่อ: การถ่ายโอนที่เร็วภายใต้ภาระ กฎการเข้าถึงที่ชัดเจน ปฏิบัติการที่เรียบง่าย (แบ็กอัพ กู้คืน ล้างข้อมูล) และค่าใช้จ่ายที่คงที่เมื่อการใช้งานเพิ่มขึ้น

เพื่อไปถึงจุดนั้น ให้แยกสองสิ่งที่มักถูกผสมกัน:

เมตาดาต้า คือข้อมูลเล็ก ๆ เกี่ยวกับไฟล์: ใครเป็นเจ้าของ ชื่อ ขนาด ประเภท เมื่ออัปโหลด และอยู่ที่ไหน ข้อมูลนี้ควรอยู่ในฐานข้อมูลของคุณ (เช่น Postgres) เพราะต้อง query, filter และ join กับผู้ใช้ โปรเจกต์ และสิทธิ์

ไบต์ไฟล์ คือเนื้อหาไฟล์จริง (รูป ภาพ PDF วิดีโอ) เก็บไบต์ในบล็อบส์ฐานข้อมูลแม้จะทำงานได้ แต่จะทำให้ฐานข้อมูลหนักขึ้น การแบ็กอัพใหญ่ขึ้น และคาดการณ์ประสิทธิภาพได้ยาก การเก็บไบต์ใน object storage ช่วยให้ฐานข้อมูลโฟกัสที่งานของมัน ในขณะที่ไฟล์ถูกเสิร์ฟอย่างรวดเร็วและประหยัดโดยระบบที่สร้างมาสำหรับงานนี้

ที่เก็บแบบวัตถุ vs บล็อบส์ในฐานข้อมูล พูดกันง่าย ๆ

เมื่อคนพูดว่า "เก็บอัปโหลดในฐานข้อมูล" พวกเขามักหมายถึงบล็อบส์ฐานข้อมูล: เช่นคอลัมน์ BYTEA (ไบต์ดิบในแถว) หรือ Postgres "large objects" (ฟีเจอร์ที่เก็บค่าขนาดใหญ่แยกต่างหาก) ทั้งคู่ทำงานได้ แต่ทั้งคู่จะทำให้ฐานข้อมูลของคุณรับผิดชอบในการเสิร์ฟไบต์ไฟล์

Object storage เป็นแนวคิดต่างออกไป: ไฟล์อยู่ในบัคเก็ตเป็นออบเจกต์ ถูกอ้างอิงด้วยคีย์ (เช่น uploads/2026/01/file.pdf) มันถูกออกแบบมาสำหรับไฟล์ขนาดใหญ่ พื้นที่เก็บราคาถูก และการสตรีมดาวน์โหลด อีกทั้งรองรับการอ่านพร้อมกันจำนวนมากได้ดีโดยไม่ผูกการเชื่อมต่อฐานข้อมูลของคุณ

Postgres เด่นเรื่องการ query เงื่อนไข และธุรกรรม มันเหมาะสำหรับเมตาดาต้าว่าใครเป็นเจ้าของไฟล์ มันคืออะไร เมื่ออัปโหลด และดาวน์โหลดได้หรือไม่ เมตาดาต้าเล็ก ๆ เหล่านี้จัดทำดัชนีได้ง่ายและรักษาความสอดคล้องได้ง่าย

กฎปฏิบัติที่ได้ผล:

  • ใช้ Postgres สำหรับเมตาดาต้าไฟล์ สิทธิ์ และความสัมพันธ์
  • ใช้ object storage สำหรับไบต์เมื่อไฟล์มีขนาดเกินไม่กี่เมกะไบต์ หรือเมื่อมีการดาวน์โหลดบ่อย
  • พิจารณาใช้ DB blobs เฉพาะทรัพย์สินจิ๋วที่ต้องผูกเชิงธุรกรรมกับเรคคอร์ด (เช่นไอคอนขนาดเล็ก) และคุณมั่นใจว่าการเติบโตของฐานข้อมูลจะไม่มากนัก

การเช็กสติปัญญาอย่างรวดเร็ว: ถ้าการแบ็กอัพ รีพลิกา และมิเกรชันจะลำบากเมื่อรวมไบต์ไฟล์ไว้ ให้เก็บไบต์ไว้ข้างนอก Postgres

สถาปัตยกรรมง่าย ๆ ที่จัดการได้

การตั้งค่าที่ทีมส่วนใหญ่มักใช้ตรงไปตรงมาคือ: เก็บไบต์ใน object storage และเก็บเรคคอร์ดไฟล์ (ใครเป็นเจ้าของ มันคืออะไร อยู่ที่ไหน) ใน Postgres API ของคุณประสานงานและอนุญาต แต่ไม่พร็อกซีการอัปโหลดและดาวน์โหลดขนาดใหญ่นั้น

นั่นทำให้คุณมีสามความรับผิดชอบชัดเจน:

  • Postgres เก็บแถวขนาดเล็กต่อไฟล์: file_id เสถียร เจ้าของ ขนาด content type และตัวชี้ออบเจกต์
  • Object storage เก็บไบต์จริง ปรับแต่งมาสำหรับไฟล์ใหญ่และพื้นที่เก็บราคาถูก
  • API ของคุณ สร้างและอนุญาตเรคคอร์ดไฟล์ และออกสิทธิ์ชั่วคราวให้ที่เก็บ

file_id ที่เสถียรนี้จะเป็นกุญแจหลักสำหรับทุกอย่าง: ความคิดเห็นที่อ้างถึงไฟล์ ใบแจ้งหนี้ที่ชี้ไปยัง PDF บันทึก audit และเครื่องมือซัพพอร์ต ผู้ใช้อาจเปลี่ยนชื่อไฟล์ คุณอาจย้ายระหว่างบัคเก็ต และ file_id ยังคงเหมือนเดิม

เมื่อเป็นไปได้ ให้ถือว่าออบเจกต์ที่เก็บไว้เป็นแบบ immutable หากผู้ใช้เปลี่ยนเอกสาร ให้สร้างออบเจกต์ใหม่ (และมักเป็นแถวใหม่หรือแถวเวอร์ชันใหม่) แทนการเขียนทับไบต์ในที่เดิม มันทำให้แคชง่ายขึ้น หลีกเลี่ยงสถานการณ์ "ลิงก์เก่าได้ไฟล์ใหม่" และให้เรื่องการคืนสถานะสะอาด

ตัดสินใจเรื่องความเป็นส่วนตัวตั้งแต่ต้น: เป็นส่วนตัวโดยดีฟอลต์ และเป็นสาธารณะเฉพาะกรณียกเว้น กฎง่าย ๆ คือ: ฐานข้อมูลเป็นแหล่งข้อมูลจริงว่าผู้ใดเข้าถึงไฟล์ได้ Object storage จะบังคับใช้นโยบายสิทธิ์ชั่วคราวที่ API ของคุณออกให้

การออกแบบเมตาดาต้าไฟล์ใน Postgres

ด้วยการแยกที่ชัดเจน Postgres เก็บข้อเท็จจริงเกี่ยวกับไฟล์ ขณะที่ object storage เก็บไบต์ นั่นทำให้ฐานข้อมูลคุณเล็กลง การแบ็กอัพเร็วขึ้น และการค้นหาง่ายขึ้น

ตาราง uploads แบบปฏิบัติการต้องการเพียงไม่กี่ฟิลด์เพื่อให้ตอบคำถามจริง ๆ ได้ เช่น "ใครเป็นเจ้าของนี้?" "เก็บไว้ที่ไหน?" และ "ดาวน์โหลดได้หรือไม่?"

CREATE TABLE uploads (
  id               uuid PRIMARY KEY,
  owner_id         uuid NOT NULL,
  bucket           text NOT NULL,
  object_key       text NOT NULL,
  size_bytes       bigint NOT NULL,
  content_type     text,
  original_filename text,
  checksum         text,
  state            text NOT NULL CHECK (state IN ('pending','uploaded','failed','deleted')),
  created_at       timestamptz NOT NULL DEFAULT now()
);

CREATE INDEX uploads_owner_created_idx ON uploads (owner_id, created_at DESC);
CREATE INDEX uploads_checksum_idx ON uploads (checksum);

การตัดสินใจบางอย่างที่จะช่วยลดปัญหาในภายหลัง:

  • ใช้ bucket + object_key เป็นตัวชี้จัดเก็บ เก็บให้ไม่เปลี่ยนแปลงหลังอัปโหลด
  • ติดตาม state เมื่อผู้ใช้เริ่มอัปโหลด ให้ insert แถว pending พลิกเป็น uploaded เมื่อระบบยืนยันว่าออบเจกต์มีจริงและขนาด (และถ้าเป็นไปได้ checksum) ตรงกัน
  • เก็บ original_filename เพื่อแสดงเท่านั้น อย่าเชื่อถือมันสำหรับการตัดสินใจด้านชนิดหรือความปลอดภัย

ถ้ารองรับการแทนที่ (เช่นผู้ใช้ฟรีอัปโหลดใบแจ้งหนี้ใหม่) เพิ่มตาราง upload_versions แยกต่างหากที่มี upload_id, version, object_key, และ created_at วิธีนี้คุณเก็บประวัติ คืนสถานะได้ และหลีกเลี่ยงการทำลิงก์เก่าพัง

โฟลว์การอัปโหลดทีละขั้น (โดยไม่บล็อก API)

รักษาความเร็วการอัปโหลดโดยทำให้ API ของคุณจัดการการประสานงาน ไม่ใช่ไบต์ไฟล์ ฐานข้อมูลของคุณจะยังตอบสนองได้ ในขณะที่ object storage รับภาระแบนด์วิดท์

เริ่มด้วยการสร้างเรคคอร์ดอัปโหลดก่อนจะส่งอะไรไปเลย API ของคุณส่งคืน upload_id, ตำแหน่งที่จะเก็บไฟล์ (object_key), และสิทธิ์อัปโหลดชั่วคราว

โฟลว์ทั่วไป:

  1. ไคลเอนต์ขออัปโหลด: API สร้างแถวด้วย pending พร้อมขนาดที่คาดไว้และ content type ที่ตั้งใจ
  2. API คืน presigned URL: สำหรับไฟล์ใหญ่ สร้าง presigned upload URL สำหรับไฟล์จิ๋ว (เช่น avatar) คุณอาจพร็อกซีผ่านแบ็กเอนด์เพื่อความเรียบง่ายของโค้ดฝั่งไคลเอนต์
  3. ไคลเอนต์อัปโหลดตรงไปที่ object storage: เบราว์เซอร์หรือแอปส่งไบต์ไปที่ที่เก็บ ไม่ใช่ผ่าน API ของคุณ
  4. Finalize: ไคลเอนต์เรียก API ของคุณด้วย upload_id และฟิลด์การตอบกลับจากสตอเรจ (เช่น ETag) เซิร์ฟเวอร์ของคุณตรวจสอบขนาด checksum (ถ้ามี) และ content type แล้วทำเครื่องหมายแถวเป็น uploaded
  5. ล้มเหลวอย่างปลอดภัย: ถ้าการตรวจสอบล้มเหลว ให้มาร์กเป็น failed และลบออบเจ็กต์ถ้าต้องการ

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

เพื่อ ลดสำเนาซ้ำเมื่อ retry และ re-upload ให้เก็บ checksum และถือว่า "same owner + same checksum + same size" เป็นไฟล์เดียวกัน

โฟลว์การดาวน์โหลดทีละขั้น (เร็วและเป็นมิตรกับแคช)

อัตโนมัติงานล้างข้อมูล
สร้างงานแบ็กกราวด์สำหรับล้างข้อมูลร้าง การลบแบบ soft และจัดการ lifecycle
สร้าง

โฟลว์ดาวน์โหลดที่ดีเริ่มจาก URL คงที่ในแอปของคุณ แม้ว่าจะเก็บไบต์ไว้อีกที่ก็ตาม คิดว่า: /files/{file_id} API ของคุณใช้ file_id เพื่อค้นหาเมตาดาต้าใน Postgres ตรวจสอบสิทธิ์ แล้วตัดสินใจว่าจะส่งมอบไฟล์อย่างไร

  1. ไคลเอนต์ร้องขอ URL คงที่ของคุณด้วย file_id
  2. API ตรวจสอบว่าไคลเอนต์เข้าถึงได้และไฟล์เป็น uploaded
  3. API คืนค่า redirect ไปยัง object storage (มักเป็นตัวเลือกที่ดีที่สุด) หรือตัว presigned GET แบบอายุสั้นสำหรับไฟล์ส่วนตัว
  4. ไคลเอนต์ดาวน์โหลดตรงจาก object storage โดยที่ API และแอปเซิร์ฟเวอร์ของคุณไม่อยู่ในเส้นทางร้อน

การ redirect ง่ายและเร็วสำหรับไฟล์สาธารณะหรือกึ่งสาธารณะ สำหรับไฟล์ส่วนตัว ให้ใช้ presigned GET ที่มีอายุสั้นเพื่อให้ที่เก็บยังคงเป็นส่วนตัวแต่ไคลเอนต์ดาวน์โหลดได้โดยตรง

สำหรับวิดีโอและการดาวน์โหลดขนาดใหญ่ ให้แน่ใจว่า object storage (และเลเยอร์พร็อกซีใด ๆ) รองรับ range requests (Range headers) สิ่งนี้จะช่วยให้การขอส่วนและดาวน์โหลดต่อได้ หากคุณพร็อกซีไบต์ผ่าน API ของคุณ การรองรับ range มักจะพังหรือมีราคาแพง

แคชช่วยให้เร็วได้ จุดเข้า /files/{file_id} ของคุณควรมักจะไม่สามารถแคชได้ (มันเป็นเกตตรวจสิทธิ์) ในขณะที่การตอบจาก object storage มักจะสามารถแคชตามเนื้อหาได้ ถ้าไฟล์เป็น immutable (อัปโหลดใหม่ = คีย์ใหม่) คุณสามารถตั้งเวลาแคชยาวได้ หากคุณเขียนทับไฟล์ ให้ลดเวลาแคชหรือใช้คีย์ที่มีเวอร์ชัน

CDN ช่วยเมื่อคุณมีผู้ใช้จำนวนมากทั่วโลกหรือไฟล์ขนาดใหญ่ ถ้าผู้ชมของคุณเล็กหรืออยู่ภูมิภาคเดียวกัน object storage เพียว ๆ มักพอและถูกกว่าเริ่มต้น

รักษาค่าใช้จ่ายให้คาดการณ์ได้เมื่อเวลาผ่านไป

บิลที่ทำให้ประหลาดใจมักมาจากการดาวน์โหลดและการ churn ไม่ใช่แค่ไบต์ที่เก็บอยู่เฉย ๆ

ประเมินสี่ตัวขับเคลื่อนที่เปลี่ยนค่าใช้จ่าย: จำนวนที่เก็บ, ความถี่การอ่าน/เขียน (requests), ปริมาณข้อมูลที่ออกจากผู้ให้บริการ (egress), และการใช้ CDN เพื่อลดการดาวน์โหลดจากต้นทาง ไฟล์ขนาดเล็กที่ดาวน์โหลด 10,000 ครั้งอาจแพงกว่าไฟล์ใหญ่ที่ไม่มีใครแตะ

ตัวควบคุมที่ช่วยให้ค่าใช้จ่ายคงที่:

  • จำกัดขนาดไฟล์ต่อการอัปโหลดและตั้งโควตาต่อผู้ใช้ตามแผน
  • จำกัดอัตราการอัปโหลดและดาวน์โหลดเพื่อป้องกันการใช้งานผิดหรือวงจรซ้ำโดยไม่ตั้งใจ
  • ใช้นโยบาย lifecycle ให้ไฟล์เก่าถูกย้ายไปชั้นที่ถูกลง หรือหมดอายุเมื่อไม่จำเป็น
  • ลดซ้ำด้วย checksum เพื่อให้การรีไตรและอัปโหลดซ้ำไม่สร้างสำเนาเพิ่ม
  • เก็บตัวนับการใช้งานใน Postgres เพื่อให้การเรียกเก็บเงินและการแจ้งเตือนอิงจากข้อเท็จจริง ไม่ใช่การประมาณ

กฎ lifecycle มักเป็นชัยชนะง่าย ๆ ตัวอย่าง: เก็บรูปภาพต้นฉบับ "ร้อน" 30 วัน แล้วย้ายไปชั้นเก็บที่ถูกกว่า; เก็บใบแจ้งหนี้ 7 ปี แต่ลบชิ้นส่วนการอัปโหลดที่ล้มเหลวหลัง 7 วัน นโยบายการรักษาพื้นฐานจะหยุดการเพิ่มขึ้นของพื้นที่เก็บ

การลดซ้ำทำได้เรียบง่าย: เก็บ hash ของเนื้อหา (เช่น SHA-256) ในตารางเมตาดาต้าแล้วบังคับความไม่ซ้ำต่อเจ้าของ เมื่อผู้ใช้ฟรีอัปโหลด PDF เดิมสองครั้ง คุณสามารถใช้วัตถุเดิมและสร้างแถวเมตาดาต้าใหม่แทนการเก็บออบเจกต์ซ้ำ

สุดท้าย ให้ติดตามการใช้งานในที่ที่คุณทำบัญชีผู้ใช้แล้ว: Postgres เก็บ bytes_uploaded, bytes_downloaded, object_count, และ last_activity_at ต่อผู้ใช้หรือ workspace จะทำให้แสดงขีดจำกัดใน UI และแจ้งเตือนก่อนบิลมาถึงได้ง่าย

ความปลอดภัยและการปฏิบัติตามข้อกำหนดพื้นฐานในการอัปโหลด

สร้างการอัปโหลดจากพรอมพท์
อธิบายโฟลว์การอัปโหลดของคุณในแชท แล้วสร้างโครงงาน React, Go, Postgres ให้ใช้งานได้จริง
เริ่มฟรี

ความปลอดภัยของการอัปโหลดสรุปได้สองเรื่อง: ใครเข้าถึงไฟล์ได้ และคุณพิสูจน์อะไรได้เมื่อเกิดปัญหา

การควบคุมการเข้าถึงที่สอดคล้องกับการใช้งานจริง

เริ่มจากโมเดลการเข้าถึงที่ชัดเจนและเข้ารหัสไว้ในเมตาดาต้า Postgres ไม่ใช่กฎกระจัดกระจายทั่วบริการ

โมเดลง่าย ๆ ที่ครอบคลุมแอปส่วนใหญ่:

  • Owner-only: เฉพาะผู้ที่อัปโหลด (และแอดมิน) เท่านั้นที่เข้าถึงได้
  • Shared: เข้าถึงได้กับผู้ใช้บางรายหรือทีม/workspace
  • Public: เข้าถึงโดยไม่ต้องล็อกอิน (ใช้ระมัดระวัง และยังควรติดตาม)

สำหรับไฟล์ส่วนตัว หลีกเลี่ยงการเปิดเผยคีย์ออบเจกต์ดิบ ออก presigned upload/download ที่มีเวลาและขอบเขตจำกัด และหมุนบ่อยๆ

การตรวจสอบการปฏิบัติตามข้อกำหนดที่จะช่วยคุณในภายหลัง

ยืนยันการเข้ารหัสทั้งระหว่างส่งและเมื่อพักอยู่ ในการส่งให้ใช้ HTTPS ตลอดทาง รวมถึงการอัปโหลดตรงไปที่ที่เก็บ เมื่อพักหมายถึงการเข้ารหัสฝั่งเซิร์ฟเวอร์ในผู้ให้บริการที่เก็บ และให้แน่ใจว่าแบ็กอัพและรีพลิกาเข้ารหัสด้วย

เพิ่มจุดตรวจสอบเพื่อความปลอดภัยและคุณภาพข้อมูล: ตรวจชนิดและขนาดก่อนออก URL ในการอัปโหลด แล้วตรวจอีกครั้งหลังอัปโหลด (อิงจากไบต์ที่เก็บจริง ไม่ใช่แค่ชื่อไฟล์) ถ้าความเสี่ยงของคุณสูง ให้รันสแกนมัลแวร์แบบอะซิงโครนัสและกักไฟล์จนกว่าจะผ่าน

เก็บฟิลด์ audit เพื่อให้สืบสวนเหตุการณ์และตอบข้อกำหนดพื้นฐาน: uploaded_by, ip, user_agent, และ last_accessed_at เป็นฐานปฏิบัติที่เหมาะสม

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

ความผิดพลาดที่พบบ่อยซึ่งทำให้ระบบช้าหรือเกิดเหตุการณ์

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

  • เก็บไบต์ไฟล์ใน Postgres: มันใช้ได้สำหรับแอปเล็ก ๆ แต่แบ็กอัพจะบวม การกู้คืนช้า งานบำรุงรักษากลายเป็นความเสี่ยง ตารางขนาดใหญ่เดียวสามารถชะลอ vacuum replication และแม้แต่ query ง่าย ๆ
  • ใช้ชื่อไฟล์จากผู้ใช้เป็น object key: จะเกิดการชนกัน (ผู้ใช้สองคนอัปโหลด "invoice.pdf") และตัวอักษรแปลก ๆ ก่อปัญหา เก็บชื่อไฟล์เพื่อแสดง แต่สร้างคีย์จัดเก็บไม่ซ้ำ
  • ข้ามการตรวจสอบเมื่อ finalize: แม้จะตรวจฝั่งไคลเอนต์แล้ว คุณยังต้องมีการตรวจสอบฝั่งเซิร์ฟเวอร์สำหรับขนาด ชนิด และความเป็นเจ้าของเมื่อมาร์กเป็น complete
  • ทำให้ออบเจกต์เป็นสาธารณะโดยไม่ได้ตั้งใจและไม่หมุนการเข้าถึง: นโยบายบัคเก็ตสาธารณะชั่วคราวหรือ URL อายุยาวมักกลายเป็นถาวร ให้ชอบลิงก์ดาวน์โหลดอายุสั้นและมีวิธีเพิกถอนการเข้าถึงอย่างรวดเร็ว
  • ลบแค่ด้านเดียว (เมตาดาต้าหรือไบต์): ลบแถวใน Postgres แต่ปล่อยออบเจกต์ไว้จะสร้างค่าใช้จ่ายเงียบ ๆ ลบออบเจกต์แต่เก็บเมตาดาต้าจะทำให้ดาวน์โหลดเสียและเพิ่มงานซัพพอร์ต

ตัวอย่างที่จับต้องได้: ถ้าผู้ใช้เปลี่ยนภาพโปรไฟล์สามครั้ง คุณอาจจ่ายค่าที่เก็บสำหรับออบเจกต์เก่า 3 ชุดตลอดไปหากไม่มีงานล้างทิ้ง รูปแบบปลอดภัยคือ soft delete ใน Postgres แล้วงานแบ็กกราวด์ลบออบเจกต์และบันทึกผล

เช็คลิสต์ก่อนเปิดใช้งาน

ปัญหาส่วนใหญ่ปรากฏเมื่อไฟล์ใหญ่แรกมาถึง ผู้ใช้รีเฟรชกลางอัปโหลด หรือใครสักคนลบบัญชีแต่ไบต์ยังคงอยู่

ให้แน่ใจว่าตาราง Postgres บันทึกขนาดไฟล์ checksum (เพื่อตรวจความสมบูรณ์) และเส้นทางสถานะชัดเจน (เช่น: pending, uploaded, failed, deleted)

เช็คลิสต์สุดท้าย:

  • ยืนยันว่าการลองซ้ำปลอดภัย: พยายามซ้ำหลายครั้งต้องไม่สร้างออบเจกต์เพิ่มหรือแถว uploaded ที่ขาดไบต์
  • ทำให้อัปโหลดสามารถกลับมาได้หรืออย่างน้อยรีสตาร์ทได้โดยไม่ต้องเปิดตั๋วซัพพอร์ต (เครือข่ายมือถือจะหลุดบ่อย)
  • ตรวจสอบว่าการดาวน์โหลดรองรับ range requests เพื่อให้ไฟล์ใหญ่เริ่มเร็วและสามารถ resume ได้หลังหยุดชั่วคราว
  • กำหนดการลบให้ชัดเจนตั้งแต่ต้น: ทำ tombstone เมตาดาต้า ลบไบต์ออบเจกต์ และจัดการการล้างทิ้งล่าช้าถ้างานล้ม
  • เพิ่มการมอนิเตอร์พื้นฐาน: อัตราข้อผิดพลาดอัปโหลด/ดาวน์โหลด, การเติบโตของพื้นที่เก็บ, และการพุ่งของ egress

การทดสอบหนึ่งอย่างที่จับต้องได้: อัปโหลดไฟล์ 2 GB รีเฟรชหน้าเมื่อถึง 30% แล้ว resume จากนั้นดาวน์โหลดบนการเชื่อมต่อช้าและขอไปกึ่งกลาง ถ้าโฟลว์ใดโฟลว์หนึ่งยังไม่ราบรื่น แก้ไขก่อนเปิดใช้งาน

สถานการณ์ตัวอย่าง: รูปภาพและใบแจ้งหนี้ในแอปเดียว

ส่งมอบการดาวน์โหลดแบบส่วนตัว
เพิ่มจุดเข้าใช้งานด้วย file_id และออกสิทธิ์ดาวน์โหลดแบบใช้ครั้งสั้น
สร้างเลย

แอป SaaS แบบง่ายมักมีการอัปโหลดสองประเภทที่ต่างกันมาก: รูปโปรไฟล์ (บ่อย เล็ก และปลอดให้แคชได้) และ PDF ใบแจ้งหนี้ (ละเอียดอ่อน ต้องเป็นส่วนตัว) นี่คือที่การแยกเมตาดาต้าใน Postgres กับไบต์ใน object storage คุ้มค่า

นี่คือลักษณะเมตาดาต้าในตาราง files หนึ่งตาราง พร้อมฟิลด์สองสามอย่างที่กำหนดพฤติกรรม:

fieldตัวอย่างรูปโปรไฟล์ตัวอย่าง PDF ใบแจ้งหนี้
kindavatarinvoice_pdf
visibilityprivate (เสิร์ฟผ่าน signed URL)private
cache_controlpublic, max-age=31536000, immutableno-store
object_keyusers/42/avatars/2026-01-17T120102Z.webporgs/7/invoices/INV-1049.pdf
statusuploadeduploaded
size_bytes184233982341

เมื่อผู้ใช้แทนที่รูป ให้ถือเป็นไฟล์ใหม่ ไม่ใช่การเขียนทับ สร้างแถวใหม่และ object_key ใหม่ แล้วอัปเดตโปรไฟล์ผู้ใช้ให้ชี้ไปยัง file_id ใหม่ ทำเครื่องหมายแถวเก่าเป็น replaced_by=\u003cnew_id\u003e (หรือ deleted_at) และลบออบเจกต์เก่าทีหลังด้วยงานแบ็กกราวด์ วิธีนี้เก็บประวัติ คืนสถานะง่ายขึ้น และหลีกเลี่ยง race condition

การซัพพอร์ตและการดีบักง่ายขึ้นเพราะเมตาดาต้าบอกเรื่อง เมื่อใครสักคนบอกว่า "การอัปโหลดของฉันล้มเหลว" ซัพพอร์ตสามารถตรวจ status, ข้อความความผิดพลาดที่อ่านได้ (last_error), storage_request_id หรือ etag (เพื่อตรวจสตอเรจล็อก), เวลาที่เกิดเหตุ (เกิดการค้างไหม?), และ owner_id กับ kind (นโยบายการเข้าถึงถูกต้องไหม?)

ขั้นตอนถัดไปโดยไม่สร้างของเกินความจำเป็น

เริ่มเล็กและทำให้เส้นทางปกติน่าเบื่อ: ไฟล์อัปโหลด เมตาดาต้าถูกบันทึก การดาวน์โหลดเร็ว และไม่มีอะไรหาย

เป้าหมายเริ่มต้นที่ดีคือ ตารางเมตาดาต้า Postgres ขั้นต่ำ พร้อมโฟลว์อัปโหลดตรงไปยังที่เก็บ และโฟลว์ดาวน์โหลดเดียวที่คุณอธิบายบนไวท์บอร์ดได้ เมื่อนั่นทำงานครบ ให้เพิ่มเวอร์ชัน โควต้า และนโยบาย lifecycle

เลือกนโยบายการเก็บที่ชัดเจนต่อประเภทไฟล์และจดไว้ ตัวอย่าง: รูปโปรไฟล์อาจแคชได้ ส่วนใบแจ้งหนี้ควรเป็นส่วนตัวและเข้าถึงได้ผ่าน URL ดาวน์โหลดสั้น ๆ การผสมหลายๆ นโยบายในพาธบัคเก็ตเดียวโดยไม่มีแผนคือจุดที่การเปิดเผยโดยไม่ได้ตั้งใจเกิดขึ้น

เพิ่มการติดตามข้อมูลตั้งแต่ต้น ตัวเลขที่คุณต้องการตั้งแต่วันแรกคือ อัตราการ finalize ล้มเหลว อัตราออบเจกต์ร้าง (ออบเจกต์ที่ไม่มีแถว DB ตรงกัน และในทางกลับกัน), ปริมาณ egress ตามประเภทไฟล์, P95 latency การดาวน์โหลด และขนาดออบเจกต์เฉลี่ย

ถ้าต้องการทางลัดในการต้นแบบรูปแบบนี้ Koder.ai (koder.ai) ถูกสร้างขึ้นรอบการสร้างแอปจากแชท และมันตรงกับสแตกทั่วไปที่ใช้ที่นี่ (React, Go, Postgres) มันช่วยให้วนรอบสคีมา endpoint และงานล้างแบ็กกราวด์ได้เร็วโดยไม่ต้องเขียนโครงสร้างซ้ำ

หลังจากนั้น เพิ่มเท่าที่คุณอธิบายได้ในประโยคเดียว: "เราเก็บเวอร์ชันเก่าไว้ 30 วัน" หรือ "แต่ละ workspace ได้ 10 GB" รักษาความเรียบง่ายจนกว่าจะมีการใช้งานจริงบังคับให้ปรับ

คำถามที่พบบ่อย

Should I store uploaded files in Postgres or in object storage?

ใช้ Postgres สำหรับเมตาดาต้าที่คุณต้องการค้นหาและปกป้อง (เจ้าของ สิทธิ์ สถานะ checksum ตัวชี้ที่เก็บ) แล้วเก็บไบต์จริงใน object storage เพื่อให้การดาวน์โหลดและการถ่ายโอนขนาดใหญ่ไม่กินการเชื่อมต่อฐานข้อมูลหรือทำให้การสำรองข้อมูลบวม

What’s the main downside of storing file bytes in Postgres blobs?

มันทำให้ฐานข้อมูลของคุณต้องทำหน้าที่เป็นทั้งตัวจัดการข้อมูลและไฟล์เซิร์ฟเวอร์ ซึ่งเพิ่มขนาดตาราง ชะลอการแบ็กอัพและการกู้คืน เพิ่มภาระการทำสำเนา และทำให้ประสิทธิภาพไม่คงที่เมื่อลูกค้าหลายคนดาวน์โหลดพร้อมกัน

What’s the simplest upload architecture that scales without getting complicated?

ใช่: เก็บ file_id ที่เสถียรในแอปของคุณ เก็บเมตาดาต้าใน Postgres และเก็บไบต์ใน object storage โดยระบุด้วย bucket และ object_key API ของคุณควรอนุญาตการเข้าถึงและออกสิทธิ์ชั่วคราวแทนการพร็อกซีไบต์

How should the upload flow work if I don’t want my API to handle file bytes?

สร้างแถว pending ก่อน สร้าง object_key แบบไม่ซ้ำ แล้วให้ลูกค้าอัปโหลดตรงไปยังที่เก็บโดยใช้สิทธิ์ชั่วคราว หลังอัปโหลดให้เรียก endpoint finalize เพื่อให้เซิร์ฟเวอร์ตรวจสอบขนาดและ checksum (ถ้ามี) ก่อนเปลี่ยนเป็น uploaded

Why do I need an upload “state” like pending/uploaded/failed/deleted?

เพราะการอัปโหลดจริงมักล้มเหลวหรือร้องขอซ้ำ ฟิลด์สถานะช่วยแยกแยะไฟล์ที่คาดว่าจะมีแต่ายังไม่ปรากฏ (pending), เสร็จสมบูรณ์ (uploaded), เสีย (failed) และถูกลบ (deleted) เพื่อให้ UI งานล้างข้อมูล และเครื่องมือช่วยเหลือทำงานถูกต้อง

Can I use the user’s filename as the object key in storage?

เก็บ original_filename ไว้เพื่อแสดงเท่านั้น ให้สร้างคีย์จัดเก็บที่ไม่ซ้ำ (มักเป็น UUID-based path) เพื่อหลีกเลี่ยงการชนกัน ตัวอักษรแปลก ๆ และปัญหาด้านความปลอดภัย คุณยังสามารถแสดงชื่อเดิมใน UI ได้โดยไม่ใช้เป็นคีย์จัดเก็บ

What’s the recommended download pattern for private files?

ใช้ URL คงที่ในแอป เช่น /files/{file_id} เป็นเกตสำหรับตรวจสิทธิ์ หลังจากตรวจ Postgres แล้วส่ง redirect หรือ presigned GET แบบอายุสั้นเพื่อให้ไคลเอนต์ดาวน์โหลดจาก object storage โดยตรง ซึ่งจะช่วยให้ API ของคุณไม่ต้องผ่านเส้นทางร้อน

What usually causes surprise costs with user uploads?

โดยปกติคือการดาวน์โหลดออก (egress) และการดาวน์โหลดซ้ำที่เกิดขึ้นบ่อย ไม่ใช่แค่ขนาดไฟล์ตั้งอยู่ จำกัดขนาดไฟล์และโควต้า ใช้นโยบายการเก็บถาวร/ย้ายชั้นเก็บ ลดซ้ำด้วย checksum เมื่อเหมาะสม และเก็บตัวนับการใช้งานเพื่อแจ้งเตือนก่อนบิลพุ่ง

What security checks are worth doing for uploads on day one?

เก็บสิทธิ์และการมองเห็นใน Postgres เป็นแหล่งข้อมูลจริง และเก็บที่เก็บเป็นแบบส่วนตัวโดยดีฟอลต์ ตรวจสอบชนิดและขนาดทั้งก่อนและหลังอัปโหลด ใช้ HTTPS ตลอดทาง เข้ารหัสเมื่อพักข้อมูล และบันทึกฟิลด์ audit เพื่อสืบสวนเหตุการณ์ในภายหลัง

How can I implement this quickly without overbuilding?

เริ่มจากตารางเมตาดาต้าเดียว โฟลว์อัปโหลดตรงไปยังที่เก็บ และ endpoint ดาวน์โหลดที่เป็นเกต จากนั้นเพิ่มงานล้างข้อมูลสำหรับออบเจกต์ร้างและแถว soft-deleted หากต้องการต้นแบบเร็วบนสแตก React/Go/Postgres ลองใช้ Koder.ai (koder.ai) เพื่อสร้าง endpoint ตาราง และงานแบ็กกราวด์จากแชทและวนซ้ำโดยไม่ต้องเขียนโครงสร้างพื้นฐานซ้ำ

สารบัญ
ปัญหาจริง ๆ ของการอัปโหลดโดยผู้ใช้ที่เก็บแบบวัตถุ vs บล็อบส์ในฐานข้อมูล พูดกันง่าย ๆสถาปัตยกรรมง่าย ๆ ที่จัดการได้การออกแบบเมตาดาต้าไฟล์ใน Postgresโฟลว์การอัปโหลดทีละขั้น (โดยไม่บล็อก API)โฟลว์การดาวน์โหลดทีละขั้น (เร็วและเป็นมิตรกับแคช)รักษาค่าใช้จ่ายให้คาดการณ์ได้เมื่อเวลาผ่านไปความปลอดภัยและการปฏิบัติตามข้อกำหนดพื้นฐานในการอัปโหลดความผิดพลาดที่พบบ่อยซึ่งทำให้ระบบช้าหรือเกิดเหตุการณ์เช็คลิสต์ก่อนเปิดใช้งานสถานการณ์ตัวอย่าง: รูปภาพและใบแจ้งหนี้ในแอปเดียวขั้นตอนถัดไปโดยไม่สร้างของเกินความจำเป็นคำถามที่พบบ่อย
แชร์
Koder.ai
Build your own app with Koder today!

The best way to understand the power of Koder is to see it for yourself.

Start FreeBook a Demo