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

การอัปโหลดที่ผู้ใช้ทำดูเรียบง่าย: รับไฟล์ เก็บไว้ แล้วแสดงภายหลัง นั่นใช้ได้เมื่อมีผู้ใช้น้อยและไฟล์เล็ก แต่เมื่อปริมาณเพิ่มขึ้น ไฟล์ใหญ่ขึ้น ปัญหาจะโผล่ในจุดที่ไม่เกี่ยวกับปุ่มอัปโหลดโดยตรง
การดาวน์โหลดช้าลงเพราะเซิร์ฟเวอร์แอปหรือฐานข้อมูลต้องทำงานหนัก การสำรองข้อมูลกลายเป็นขนาดใหญ่และช้า การกู้คืนใช้เวลานานเมื่อคุณต้องการที่สุด ค่าเก็บข้อมูลและแบนด์วิดท์ (egress) อาจพุ่งเพราะไฟล์ถูกเสิร์ฟอย่างไม่มีประสิทธิภาพ ถูกคัดลอกซ้ำ หรือไม่เคยล้างทิ้ง
สิ่งที่คุณมักต้องการคือความน่าเชื่อถือและน่าเบื่อ: การถ่ายโอนที่เร็วภายใต้ภาระ กฎการเข้าถึงที่ชัดเจน ปฏิบัติการที่เรียบง่าย (แบ็กอัพ กู้คืน ล้างข้อมูล) และค่าใช้จ่ายที่คงที่เมื่อการใช้งานเพิ่มขึ้น
เพื่อไปถึงจุดนั้น ให้แยกสองสิ่งที่มักถูกผสมกัน:
เมตาดาต้า คือข้อมูลเล็ก ๆ เกี่ยวกับไฟล์: ใครเป็นเจ้าของ ชื่อ ขนาด ประเภท เมื่ออัปโหลด และอยู่ที่ไหน ข้อมูลนี้ควรอยู่ในฐานข้อมูลของคุณ (เช่น Postgres) เพราะต้อง query, filter และ join กับผู้ใช้ โปรเจกต์ และสิทธิ์
ไบต์ไฟล์ คือเนื้อหาไฟล์จริง (รูป ภาพ PDF วิดีโอ) เก็บไบต์ในบล็อบส์ฐานข้อมูลแม้จะทำงานได้ แต่จะทำให้ฐานข้อมูลหนักขึ้น การแบ็กอัพใหญ่ขึ้น และคาดการณ์ประสิทธิภาพได้ยาก การเก็บไบต์ใน object storage ช่วยให้ฐานข้อมูลโฟกัสที่งานของมัน ในขณะที่ไฟล์ถูกเสิร์ฟอย่างรวดเร็วและประหยัดโดยระบบที่สร้างมาสำหรับงานนี้
เมื่อคนพูดว่า "เก็บอัปโหลดในฐานข้อมูล" พวกเขามักหมายถึงบล็อบส์ฐานข้อมูล: เช่นคอลัมน์ BYTEA (ไบต์ดิบในแถว) หรือ Postgres "large objects" (ฟีเจอร์ที่เก็บค่าขนาดใหญ่แยกต่างหาก) ทั้งคู่ทำงานได้ แต่ทั้งคู่จะทำให้ฐานข้อมูลของคุณรับผิดชอบในการเสิร์ฟไบต์ไฟล์
Object storage เป็นแนวคิดต่างออกไป: ไฟล์อยู่ในบัคเก็ตเป็นออบเจกต์ ถูกอ้างอิงด้วยคีย์ (เช่น uploads/2026/01/file.pdf) มันถูกออกแบบมาสำหรับไฟล์ขนาดใหญ่ พื้นที่เก็บราคาถูก และการสตรีมดาวน์โหลด อีกทั้งรองรับการอ่านพร้อมกันจำนวนมากได้ดีโดยไม่ผูกการเชื่อมต่อฐานข้อมูลของคุณ
Postgres เด่นเรื่องการ query เงื่อนไข และธุรกรรม มันเหมาะสำหรับเมตาดาต้าว่าใครเป็นเจ้าของไฟล์ มันคืออะไร เมื่ออัปโหลด และดาวน์โหลดได้หรือไม่ เมตาดาต้าเล็ก ๆ เหล่านี้จัดทำดัชนีได้ง่ายและรักษาความสอดคล้องได้ง่าย
กฎปฏิบัติที่ได้ผล:
การเช็กสติปัญญาอย่างรวดเร็ว: ถ้าการแบ็กอัพ รีพลิกา และมิเกรชันจะลำบากเมื่อรวมไบต์ไฟล์ไว้ ให้เก็บไบต์ไว้ข้างนอก Postgres
การตั้งค่าที่ทีมส่วนใหญ่มักใช้ตรงไปตรงมาคือ: เก็บไบต์ใน object storage และเก็บเรคคอร์ดไฟล์ (ใครเป็นเจ้าของ มันคืออะไร อยู่ที่ไหน) ใน Postgres API ของคุณประสานงานและอนุญาต แต่ไม่พร็อกซีการอัปโหลดและดาวน์โหลดขนาดใหญ่นั้น
นั่นทำให้คุณมีสามความรับผิดชอบชัดเจน:
file_id เสถียร เจ้าของ ขนาด content type และตัวชี้ออบเจกต์file_id ที่เสถียรนี้จะเป็นกุญแจหลักสำหรับทุกอย่าง: ความคิดเห็นที่อ้างถึงไฟล์ ใบแจ้งหนี้ที่ชี้ไปยัง PDF บันทึก audit และเครื่องมือซัพพอร์ต ผู้ใช้อาจเปลี่ยนชื่อไฟล์ คุณอาจย้ายระหว่างบัคเก็ต และ file_id ยังคงเหมือนเดิม
เมื่อเป็นไปได้ ให้ถือว่าออบเจกต์ที่เก็บไว้เป็นแบบ immutable หากผู้ใช้เปลี่ยนเอกสาร ให้สร้างออบเจกต์ใหม่ (และมักเป็นแถวใหม่หรือแถวเวอร์ชันใหม่) แทนการเขียนทับไบต์ในที่เดิม มันทำให้แคชง่ายขึ้น หลีกเลี่ยงสถานการณ์ "ลิงก์เก่าได้ไฟล์ใหม่" และให้เรื่องการคืนสถานะสะอาด
ตัดสินใจเรื่องความเป็นส่วนตัวตั้งแต่ต้น: เป็นส่วนตัวโดยดีฟอลต์ และเป็นสาธารณะเฉพาะกรณียกเว้น กฎง่าย ๆ คือ: ฐานข้อมูลเป็นแหล่งข้อมูลจริงว่าผู้ใดเข้าถึงไฟล์ได้ Object storage จะบังคับใช้นโยบายสิทธิ์ชั่วคราวที่ API ของคุณออกให้
ด้วยการแยกที่ชัดเจน 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 ของคุณจัดการการประสานงาน ไม่ใช่ไบต์ไฟล์ ฐานข้อมูลของคุณจะยังตอบสนองได้ ในขณะที่ object storage รับภาระแบนด์วิดท์
เริ่มด้วยการสร้างเรคคอร์ดอัปโหลดก่อนจะส่งอะไรไปเลย API ของคุณส่งคืน upload_id, ตำแหน่งที่จะเก็บไฟล์ (object_key), และสิทธิ์อัปโหลดชั่วคราว
โฟลว์ทั่วไป:
pending พร้อมขนาดที่คาดไว้และ content type ที่ตั้งใจupload_id และฟิลด์การตอบกลับจากสตอเรจ (เช่น ETag) เซิร์ฟเวอร์ของคุณตรวจสอบขนาด checksum (ถ้ามี) และ content type แล้วทำเครื่องหมายแถวเป็น uploadedfailed และลบออบเจ็กต์ถ้าต้องการการลองซ้ำและสำเนาซ้ำเป็นเรื่องปกติ ทำให้การเรียก finalize เป็น idempotent: ถ้า finalize เดียวกันถูกเรียกซ้ำกับ upload_id เดิม ให้คืนความสำเร็จโดยไม่เปลี่ยนแปลง
เพื่อ ลดสำเนาซ้ำเมื่อ retry และ re-upload ให้เก็บ checksum และถือว่า "same owner + same checksum + same size" เป็นไฟล์เดียวกัน
โฟลว์ดาวน์โหลดที่ดีเริ่มจาก URL คงที่ในแอปของคุณ แม้ว่าจะเก็บไบต์ไว้อีกที่ก็ตาม คิดว่า: /files/{file_id} API ของคุณใช้ file_id เพื่อค้นหาเมตาดาต้าใน Postgres ตรวจสอบสิทธิ์ แล้วตัดสินใจว่าจะส่งมอบไฟล์อย่างไร
file_iduploadedการ 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 มักเป็นชัยชนะง่าย ๆ ตัวอย่าง: เก็บรูปภาพต้นฉบับ "ร้อน" 30 วัน แล้วย้ายไปชั้นเก็บที่ถูกกว่า; เก็บใบแจ้งหนี้ 7 ปี แต่ลบชิ้นส่วนการอัปโหลดที่ล้มเหลวหลัง 7 วัน นโยบายการรักษาพื้นฐานจะหยุดการเพิ่มขึ้นของพื้นที่เก็บ
การลดซ้ำทำได้เรียบง่าย: เก็บ hash ของเนื้อหา (เช่น SHA-256) ในตารางเมตาดาต้าแล้วบังคับความไม่ซ้ำต่อเจ้าของ เมื่อผู้ใช้ฟรีอัปโหลด PDF เดิมสองครั้ง คุณสามารถใช้วัตถุเดิมและสร้างแถวเมตาดาต้าใหม่แทนการเก็บออบเจกต์ซ้ำ
สุดท้าย ให้ติดตามการใช้งานในที่ที่คุณทำบัญชีผู้ใช้แล้ว: Postgres เก็บ bytes_uploaded, bytes_downloaded, object_count, และ last_activity_at ต่อผู้ใช้หรือ workspace จะทำให้แสดงขีดจำกัดใน UI และแจ้งเตือนก่อนบิลมาถึงได้ง่าย
ความปลอดภัยของการอัปโหลดสรุปได้สองเรื่อง: ใครเข้าถึงไฟล์ได้ และคุณพิสูจน์อะไรได้เมื่อเกิดปัญหา
เริ่มจากโมเดลการเข้าถึงที่ชัดเจนและเข้ารหัสไว้ในเมตาดาต้า Postgres ไม่ใช่กฎกระจัดกระจายทั่วบริการ
โมเดลง่าย ๆ ที่ครอบคลุมแอปส่วนใหญ่:
สำหรับไฟล์ส่วนตัว หลีกเลี่ยงการเปิดเผยคีย์ออบเจกต์ดิบ ออก presigned upload/download ที่มีเวลาและขอบเขตจำกัด และหมุนบ่อยๆ
ยืนยันการเข้ารหัสทั้งระหว่างส่งและเมื่อพักอยู่ ในการส่งให้ใช้ HTTPS ตลอดทาง รวมถึงการอัปโหลดตรงไปที่ที่เก็บ เมื่อพักหมายถึงการเข้ารหัสฝั่งเซิร์ฟเวอร์ในผู้ให้บริการที่เก็บ และให้แน่ใจว่าแบ็กอัพและรีพลิกาเข้ารหัสด้วย
เพิ่มจุดตรวจสอบเพื่อความปลอดภัยและคุณภาพข้อมูล: ตรวจชนิดและขนาดก่อนออก URL ในการอัปโหลด แล้วตรวจอีกครั้งหลังอัปโหลด (อิงจากไบต์ที่เก็บจริง ไม่ใช่แค่ชื่อไฟล์) ถ้าความเสี่ยงของคุณสูง ให้รันสแกนมัลแวร์แบบอะซิงโครนัสและกักไฟล์จนกว่าจะผ่าน
เก็บฟิลด์ audit เพื่อให้สืบสวนเหตุการณ์และตอบข้อกำหนดพื้นฐาน: uploaded_by, ip, user_agent, และ last_accessed_at เป็นฐานปฏิบัติที่เหมาะสม
ถ้ามีข้อกำหนดด้านถิ่นที่อยู่ข้อมูล ให้เลือกภูมิภาคการเก็บอย่างตั้งใจและให้สอดคล้องกับที่คุณรันคอมพิวต์
ปัญหาในการอัปโหลดส่วนใหญ่ไม่ใช่เรื่องความเร็วดิบ แต่เป็นการตัดสินใจเชิงออกแบบที่สะดวกในช่วงแรก แล้วเจ็บเมื่อมีการใช้งานจริง ข้อมูลจริง และการซัพพอร์ตจริง
ตัวอย่างที่จับต้องได้: ถ้าผู้ใช้เปลี่ยนภาพโปรไฟล์สามครั้ง คุณอาจจ่ายค่าที่เก็บสำหรับออบเจกต์เก่า 3 ชุดตลอดไปหากไม่มีงานล้างทิ้ง รูปแบบปลอดภัยคือ soft delete ใน Postgres แล้วงานแบ็กกราวด์ลบออบเจกต์และบันทึกผล
ปัญหาส่วนใหญ่ปรากฏเมื่อไฟล์ใหญ่แรกมาถึง ผู้ใช้รีเฟรชกลางอัปโหลด หรือใครสักคนลบบัญชีแต่ไบต์ยังคงอยู่
ให้แน่ใจว่าตาราง Postgres บันทึกขนาดไฟล์ checksum (เพื่อตรวจความสมบูรณ์) และเส้นทางสถานะชัดเจน (เช่น: pending, uploaded, failed, deleted)
เช็คลิสต์สุดท้าย:
uploaded ที่ขาดไบต์การทดสอบหนึ่งอย่างที่จับต้องได้: อัปโหลดไฟล์ 2 GB รีเฟรชหน้าเมื่อถึง 30% แล้ว resume จากนั้นดาวน์โหลดบนการเชื่อมต่อช้าและขอไปกึ่งกลาง ถ้าโฟลว์ใดโฟลว์หนึ่งยังไม่ราบรื่น แก้ไขก่อนเปิดใช้งาน
แอป SaaS แบบง่ายมักมีการอัปโหลดสองประเภทที่ต่างกันมาก: รูปโปรไฟล์ (บ่อย เล็ก และปลอดให้แคชได้) และ PDF ใบแจ้งหนี้ (ละเอียดอ่อน ต้องเป็นส่วนตัว) นี่คือที่การแยกเมตาดาต้าใน Postgres กับไบต์ใน object storage คุ้มค่า
นี่คือลักษณะเมตาดาต้าในตาราง files หนึ่งตาราง พร้อมฟิลด์สองสามอย่างที่กำหนดพฤติกรรม:
| field | ตัวอย่างรูปโปรไฟล์ | ตัวอย่าง PDF ใบแจ้งหนี้ |
|---|---|---|
kind | avatar | invoice_pdf |
visibility | private (เสิร์ฟผ่าน signed URL) | private |
cache_control | public, max-age=31536000, immutable | no-store |
object_key | users/42/avatars/2026-01-17T120102Z.webp | orgs/7/invoices/INV-1049.pdf |
status | uploaded | uploaded |
size_bytes | 184233 | 982341 |
เมื่อผู้ใช้แทนที่รูป ให้ถือเป็นไฟล์ใหม่ ไม่ใช่การเขียนทับ สร้างแถวใหม่และ 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" รักษาความเรียบง่ายจนกว่าจะมีการใช้งานจริงบังคับให้ปรับ
ใช้ Postgres สำหรับเมตาดาต้าที่คุณต้องการค้นหาและปกป้อง (เจ้าของ สิทธิ์ สถานะ checksum ตัวชี้ที่เก็บ) แล้วเก็บไบต์จริงใน object storage เพื่อให้การดาวน์โหลดและการถ่ายโอนขนาดใหญ่ไม่กินการเชื่อมต่อฐานข้อมูลหรือทำให้การสำรองข้อมูลบวม
มันทำให้ฐานข้อมูลของคุณต้องทำหน้าที่เป็นทั้งตัวจัดการข้อมูลและไฟล์เซิร์ฟเวอร์ ซึ่งเพิ่มขนาดตาราง ชะลอการแบ็กอัพและการกู้คืน เพิ่มภาระการทำสำเนา และทำให้ประสิทธิภาพไม่คงที่เมื่อลูกค้าหลายคนดาวน์โหลดพร้อมกัน
ใช่: เก็บ file_id ที่เสถียรในแอปของคุณ เก็บเมตาดาต้าใน Postgres และเก็บไบต์ใน object storage โดยระบุด้วย bucket และ object_key API ของคุณควรอนุญาตการเข้าถึงและออกสิทธิ์ชั่วคราวแทนการพร็อกซีไบต์
สร้างแถว pending ก่อน สร้าง object_key แบบไม่ซ้ำ แล้วให้ลูกค้าอัปโหลดตรงไปยังที่เก็บโดยใช้สิทธิ์ชั่วคราว หลังอัปโหลดให้เรียก endpoint finalize เพื่อให้เซิร์ฟเวอร์ตรวจสอบขนาดและ checksum (ถ้ามี) ก่อนเปลี่ยนเป็น uploaded
เพราะการอัปโหลดจริงมักล้มเหลวหรือร้องขอซ้ำ ฟิลด์สถานะช่วยแยกแยะไฟล์ที่คาดว่าจะมีแต่ายังไม่ปรากฏ (pending), เสร็จสมบูรณ์ (uploaded), เสีย (failed) และถูกลบ (deleted) เพื่อให้ UI งานล้างข้อมูล และเครื่องมือช่วยเหลือทำงานถูกต้อง
เก็บ original_filename ไว้เพื่อแสดงเท่านั้น ให้สร้างคีย์จัดเก็บที่ไม่ซ้ำ (มักเป็น UUID-based path) เพื่อหลีกเลี่ยงการชนกัน ตัวอักษรแปลก ๆ และปัญหาด้านความปลอดภัย คุณยังสามารถแสดงชื่อเดิมใน UI ได้โดยไม่ใช้เป็นคีย์จัดเก็บ
ใช้ URL คงที่ในแอป เช่น /files/{file_id} เป็นเกตสำหรับตรวจสิทธิ์ หลังจากตรวจ Postgres แล้วส่ง redirect หรือ presigned GET แบบอายุสั้นเพื่อให้ไคลเอนต์ดาวน์โหลดจาก object storage โดยตรง ซึ่งจะช่วยให้ API ของคุณไม่ต้องผ่านเส้นทางร้อน
โดยปกติคือการดาวน์โหลดออก (egress) และการดาวน์โหลดซ้ำที่เกิดขึ้นบ่อย ไม่ใช่แค่ขนาดไฟล์ตั้งอยู่ จำกัดขนาดไฟล์และโควต้า ใช้นโยบายการเก็บถาวร/ย้ายชั้นเก็บ ลดซ้ำด้วย checksum เมื่อเหมาะสม และเก็บตัวนับการใช้งานเพื่อแจ้งเตือนก่อนบิลพุ่ง
เก็บสิทธิ์และการมองเห็นใน Postgres เป็นแหล่งข้อมูลจริง และเก็บที่เก็บเป็นแบบส่วนตัวโดยดีฟอลต์ ตรวจสอบชนิดและขนาดทั้งก่อนและหลังอัปโหลด ใช้ HTTPS ตลอดทาง เข้ารหัสเมื่อพักข้อมูล และบันทึกฟิลด์ audit เพื่อสืบสวนเหตุการณ์ในภายหลัง
เริ่มจากตารางเมตาดาต้าเดียว โฟลว์อัปโหลดตรงไปยังที่เก็บ และ endpoint ดาวน์โหลดที่เป็นเกต จากนั้นเพิ่มงานล้างข้อมูลสำหรับออบเจกต์ร้างและแถว soft-deleted หากต้องการต้นแบบเร็วบนสแตก React/Go/Postgres ลองใช้ Koder.ai (koder.ai) เพื่อสร้าง endpoint ตาราง และงานแบ็กกราวด์จากแชทและวนซ้ำโดยไม่ต้องเขียนโครงสร้างพื้นฐานซ้ำ