หน้านี้แปลด้วยเครื่อง พบข้อผิดพลาดใช่ไหมช่วยกันปรับปรุง
Skip to content

ฐานข้อมูล

SnapOtter ใช้ PostgreSQL 17 กับ Drizzle ORM (pg-core / node-postgres) สำหรับการจัดเก็บข้อมูลอย่างถาวร สคีมาถูกกำหนดไว้ใน apps/api/src/db/schema.ts

การเชื่อมต่อถูกกำหนดค่าผ่านตัวแปรสภาพแวดล้อม DATABASE_URL (ค่าเริ่มต้น postgres://snapotter:snapotter@postgres:5432/snapotter) ใน Docker Compose คอนเทนเนอร์ Postgres จะเก็บข้อมูลไว้ใน named volume SnapOtter-pgdata การร้องขอจะถูกให้บริการด้วยบทบาทที่อ่านและเขียนแถวข้อมูลได้เท่านั้น ซึ่งครอบคลุมอยู่ในหัวข้อ บทบาทสิทธิ์ขั้นต่ำ ด้านล่าง

ตาราง

users

เก็บบัญชีผู้ใช้ สร้างขึ้นโดยอัตโนมัติเมื่อรันครั้งแรกจาก DEFAULT_USERNAME และ DEFAULT_PASSWORD

คอลัมน์ชนิดหมายเหตุ
iduuidคีย์หลัก
usernamevarcharไม่ซ้ำ จำเป็น
passwordHashvarcharscrypt hash
rolevarcharadmin, editor หรือ user
mustChangePasswordbooleanธงบังคับรีเซ็ตรหัสผ่าน
createdAttimestampเวลาที่สร้าง
updatedAttimestampเวลาที่อัปเดตล่าสุด

sessions

เซสชันการเข้าสู่ระบบที่ใช้งานอยู่ แต่ละแถวผูกโทเคนเซสชันกับผู้ใช้

คอลัมน์ชนิดหมายเหตุ
idvarcharคีย์หลัก (โทเคนเซสชัน)
userIduuidคีย์นอกไปยัง users.id
expiresAttimestampเวลาหมดอายุ
createdAttimestampเวลาที่สร้าง

teams

กลุ่มสำหรับจัดระเบียบผู้ใช้ ผู้ดูแลสามารถกำหนดผู้ใช้ให้กับทีมได้

คอลัมน์ชนิดคำอธิบาย
iduuidคีย์หลัก
namevarchar (ไม่ซ้ำ สูงสุด 50 อักขระ)ชื่อทีม
createdAttimestampเวลาที่สร้าง

api_keys

คีย์ API สำหรับการเข้าถึงแบบโปรแกรม คีย์ดิบจะแสดงเพียงครั้งเดียวตอนสร้าง เก็บเฉพาะ hash เท่านั้น

คอลัมน์ชนิดหมายเหตุ
iduuidคีย์หลัก
userIduuidคีย์นอกไปยัง users.id
keyHashvarcharscrypt hash ของคีย์
namevarcharป้ายชื่อที่ผู้ใช้กำหนด
createdAttimestampเวลาที่สร้าง
lastUsedAttimestampอัปเดตทุกครั้งที่มีการร้องขอที่ผ่านการยืนยันตัวตน

คีย์จะขึ้นต้นด้วย si_ ตามด้วยอักขระเลขฐานสิบหก 96 ตัว (สุ่ม 48 ไบต์)

pipelines

ชุดเครื่องมือที่บันทึกไว้ซึ่งผู้ใช้สร้างขึ้นใน UI

คอลัมน์ชนิดหมายเหตุ
iduuidคีย์หลัก
namevarcharชื่อ pipeline
descriptionvarcharคำอธิบายที่ไม่บังคับ
stepsjsonbอาร์เรย์ของอ็อบเจกต์ { toolId, settings }
createdAttimestampเวลาที่สร้าง

user_files

คลังไฟล์ถาวร โดยค่าเริ่มต้น การแก้ไขที่บันทึกไว้จะถูกแทรกเป็นแถวรากอิสระ ("บันทึกเป็นไฟล์ใหม่": version 1, parentId เป็น null ดังนั้นไฟล์ต้นฉบับยังคงอยู่ในรายการ) หรือเป็นเวอร์ชันที่เชื่อมโยงกับแถวแม่เมื่อคุณเขียนทับไฟล์ต้นฉบับ (กำหนด parentId, เพิ่มค่า version และแทนที่ไฟล์เดิม) คอลัมน์ toolChain บันทึกเครื่องมือที่นำมาใช้

คอลัมน์ชนิดคำอธิบาย
iduuidคีย์หลัก
userIduuidFK ไปยัง users (CASCADE DELETE)
originalNamevarcharชื่อไฟล์อัปโหลดต้นฉบับ
storedNamevarcharชื่อไฟล์บนดิสก์
mimeTypevarcharชนิด MIME
sizeintegerขนาดไฟล์เป็นไบต์
widthintegerความกว้างของภาพเป็นพิกเซล
heightintegerความสูงของภาพเป็นพิกเซล
versionintegerหมายเลขเวอร์ชัน (1 = ต้นฉบับ)
parentIduuid หรือ nullFK ไปยัง user_files (เวอร์ชันแม่)
toolChainjsonbรหัสเครื่องมือที่นำมาใช้ตามลำดับเพื่อสร้างเวอร์ชันนี้
createdAttimestampเวลาที่สร้าง

jobs

ติดตามงานประมวลผลสำหรับการรายงานความคืบหน้าและการล้างข้อมูล

คอลัมน์ชนิดหมายเหตุ
iduuidคีย์หลัก
typevarcharตัวระบุเครื่องมือหรือ pipeline
statusvarcharqueued, processing, completed หรือ failed
progressrealเศษส่วน 0.0-1.0
inputFilesjsonbอาร์เรย์ของพาธไฟล์อินพุต
outputPathvarcharพาธไปยังไฟล์ผลลัพธ์
settingsjsonbการตั้งค่าเครื่องมือที่ใช้
errorvarcharข้อความข้อผิดพลาดหากล้มเหลว
createdAttimestampเวลาที่สร้าง
completedAttimestampเวลาที่เสร็จสิ้น

settings

ที่เก็บแบบคีย์-ค่าสำหรับการตั้งค่าทั้งเซิร์ฟเวอร์ที่ผู้ดูแลสามารถเปลี่ยนได้จาก UI

คอลัมน์ชนิดหมายเหตุ
keyvarcharคีย์หลัก
valuevarcharค่าการตั้งค่า
updatedAttimestampเวลาที่อัปเดตล่าสุด

roles

บทบาทกำหนดเองพร้อมสิทธิ์แบบละเอียด

คอลัมน์ชนิดหมายเหตุ
iduuidคีย์หลัก
namevarcharชื่อบทบาทที่ไม่ซ้ำ
descriptionvarcharคำอธิบายที่ไม่บังคับ
permissionsjsonbอาร์เรย์ของสตริงสิทธิ์
createdAttimestampเวลาที่สร้าง

audit_log

บันทึกการกระทำที่เกี่ยวข้องกับความปลอดภัย

คอลัมน์ชนิดหมายเหตุ
iduuidคีย์หลัก
userIduuidFK ไปยัง users
actionvarcharชนิดการกระทำ
detailsjsonbข้อมูลเฉพาะการกระทำ
createdAttimestampเวลาที่กระทำ

user_preferences

สถานะ UI ของผู้ใช้แต่ละคน โดยใช้ชื่อการตั้งค่าเป็นคีย์ เก็บเครื่องมือที่ปักหมุดบนหน้าหลัก ซึ่งเขียนผ่าน PUT /api/v1/preferences

คอลัมน์ชนิดหมายเหตุ
userIdtextFK ไปยัง users ลบแบบต่อเนื่อง เป็นคีย์หลักร่วมกับ key
keytextชื่อการตั้งค่า เป็นคีย์หลักร่วมกับ userId
valuejsonbข้อมูลของการตั้งค่า
updatedAttimestampเวลาที่เขียนล่าสุด

การย้ายข้อมูล (Migrations)

Drizzle จัดการการย้ายสคีมา ไฟล์การย้ายข้อมูลอยู่ใน apps/api/drizzle/ ระหว่างการพัฒนา:

bash
cd apps/api
npx drizzle-kit generate   # generate a migration from schema changes
npx drizzle-kit migrate    # apply pending migrations

ในโปรดักชัน การย้ายข้อมูลที่ค้างอยู่จะถูกนำมาใช้โดยอัตโนมัติเมื่อเริ่มต้น

บทบาทสิทธิ์ขั้นต่ำ

สองบทบาท สองหน้าที่ DATABASE_URL ใช้ให้บริการการร้องขอ และถือสิทธิ์ SELECT, INSERT, UPDATE, DELETE บนตารางของแอป รวมถึง USAGE และ SELECT บน sequence ของตารางเหล่านั้น มีเพียงเท่านี้ทั้งหมด บทบาทนี้ไม่สามารถสร้างหรือลบตาราง ติดตั้งส่วนขยาย TRUNCATE อ่าน pg_authid สร้างฐานข้อมูล แก้ไขบทบาท หรือแตะต้องสคีมา drizzle ที่เก็บประวัติการย้ายข้อมูลได้

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

Compose และอิมเมจแบบ all-in-one ถูกตั้งค่าไว้แบบนี้อยู่แล้ว รวมถึงการติดตั้งที่มีอยู่เดิมด้วย เมื่อบูต SnapOtter จะสร้างบทบาทรันไทม์หากยังไม่มี มอบสิทธิ์ให้ รันการย้ายข้อมูล แล้วกวาดมอบสิทธิ์ไปยังตารางที่มีอยู่ก่อนหน้า การอัปเกรดไม่ต้องรัน SQL ด้วยตนเอง

หากปล่อย DATABASE_MIGRATION_URL ว่างไว้ ระบบจะทำงานแบบบทบาทเดียว โดยให้ DATABASE_URL ทำหน้าที่ทั้งสองอย่างเหมือนก่อนการแยกบทบาททุกประการ นี่คือการตั้งค่าที่รองรับอย่างเป็นทางการ ไม่ใช่การตั้งค่าที่เลิกใช้แล้ว และเป็นทางเลือกที่ถูกต้องสำหรับ Postgres แบบ managed ซึ่งการสร้างบทบาทมักไม่ใช่สิ่งที่คุณทำได้เอง

Postgres ภายนอกและแบบ managed

บน RDS, Supabase, Cloud SQL หรือคลัสเตอร์ใดก็ตามที่คุณดูแลเอง การแยกบทบาทเป็นทางเลือกที่ต้องเปิดใช้เอง สร้างบทบาทรันไทม์เพียงครั้งเดียว:

sql
CREATE ROLE snapotter_app LOGIN PASSWORD 'choose-a-strong-password'
  NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS;

จากนั้นส่งสตริงการเชื่อมต่อทั้งสองให้ SnapOtter โดยชี้ไปยังโฮสต์ พอร์ต และฐานข้อมูลเดียวกัน:

bash
DATABASE_URL=postgres://snapotter_app:[email protected]:5432/snapotter
DATABASE_MIGRATION_URL=postgres://snapotter:[email protected]:5432/snapotter

หยุดเพียงเท่านี้ SnapOtter จะมอบสิทธิ์ให้เองและมอบสิทธิ์ซ้ำหลังการย้ายข้อมูลทุกครั้ง ดังนั้นตารางที่เพิ่มเข้ามาในรุ่นถัดไปจะได้รับสิทธิ์ครบโดยไม่ต้องมีใครรัน SQL ให้

บทบาทใน DATABASE_MIGRATION_URL ต้องเป็นเจ้าของตารางของ SnapOtter เพราะมีเพียงเจ้าของตารางเท่านั้นที่มอบสิทธิ์บนตารางนั้นได้ สำหรับการติดตั้งที่มีอยู่เดิม นั่นหมายถึงบทบาทที่คุณใช้รัน SnapOtter มาตลอด ไม่ใช่บทบาทใหม่ที่สร้างขึ้นเพื่อการนี้โดยเฉพาะ หากชี้ไปยังบทบาทใหม่ที่ไม่ได้เป็นเจ้าของสิ่งใดเลย การบูตจะล้มเหลวพร้อมข้อความแจ้งข้อผิดพลาดที่บอกเรื่องนี้ตรงๆ นอกจากนี้ยังต้องมีสิทธิ์ CREATEROLE เพื่อสร้างและดูแลบทบาทรันไทม์ และต้องมีสิทธิ์สร้างสคีมา drizzle ด้วย

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

เหตุใดจึงไม่แตะสถานะ superuser

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

การติดตั้งแบบ all-in-one ที่สร้างใหม่ไม่มีปัญหานี้เลย โดยจะได้บทบาทสามรายการ ได้แก่ postgres (superuser สำหรับบูตสแตรป ไม่ปรากฏในสตริงการเชื่อมต่อใดๆ ที่ SnapOtter ใช้), snapotter (NOSUPERUSER เป็นเจ้าของข้อมูล เชื่อมต่อเฉพาะตอนบูต) และ snapotter_app (เข้าถึงเฉพาะแถวข้อมูล ใช้ให้บริการการร้องขอ)

หากยังต้องการลดสิทธิ์ snapotter ของการติดตั้งเดิมอยู่ดี ให้สร้าง superuser รายที่สองขึ้นมาก่อน แล้วเข้าสู่ระบบด้วยบทบาทนั้นเพื่อยืนยันว่าใช้งานได้จริง จากนั้นจึงรัน ALTER ROLE snapotter NOSUPERUSER

สำรองและกู้คืน

ฐานข้อมูลเชิงสัมพันธ์อยู่ในโวลุ่ม SnapOtter-pgdata ของคอนเทนเนอร์ Postgres ไม่ใช่โวลุ่ม /data ของแอป

การสำรองข้อมูลแบบลอจิคัลพร้อมการตรวจสอบความถูกต้อง (แนะนำ)

bash
# Dump into PostgreSQL's portable custom archive format
docker exec SnapOtter-postgres \
  pg_dump --format=custom --no-owner -U snapotter snapotter > snapotter.dump
test -s snapotter.dump
docker exec -i SnapOtter-postgres pg_restore --list < snapotter.dump >/dev/null

# Restore into a fresh/disposable target first and fail on the first SQL error
docker exec -i SnapOtter-postgres \
  pg_restore --exit-on-error --clean --if-exists --no-owner \
  -U snapotter -d snapotter < snapotter.dump

คำสั่งทั้งสองเชื่อมต่อในนาม snapotter ซึ่งเป็นเจ้าของ และควรทำเช่นนั้นต่อไป บทบาทรันไทม์มองไม่เห็นสคีมา drizzle ดังนั้นดัมพ์ที่สร้างด้วยบทบาทนั้นจะออกมาไม่ครบถ้วน --no-owner จะทำให้อ็อบเจ็กต์ที่กู้คืนตกเป็นของผู้ที่รันการกู้คืน การรันในนามเจ้าของจึงวางความเป็นเจ้าของไว้ตรงตามที่สิทธิ์ที่มอบไว้คาดหมาย มีข้อควรระวังหนึ่งอย่างบนคลัสเตอร์ที่สร้างใหม่: pg_dump นำสิทธิ์ที่มอบไว้ติดไปด้วย แต่ไม่ได้นำบทบาทที่ถูกอ้างถึงไปด้วย ดังนั้นให้สร้าง snapotter_app ก่อนกู้คืน มิฉะนั้น --exit-on-error จะหยุดที่ GRANT แรก อย่างไรก็ตาม SnapOtter จะมอบสิทธิ์ซ้ำอีกครั้งเมื่อบูตครั้งถัดไปไม่ว่าในกรณีใดก็ตาม

ดัมพ์ฐานข้อมูลนี้ไม่มีอ็อบเจ็กต์ไลบรารีที่บันทึกไว้ใน /data/files หรือสถานะ BullMQ แบบทนทานใน Redis สำรองและกู้คืนข้อมูลเหล่านั้นด้วยขั้นตอนการประสานงานใน Security & Hardening

สแนปชอตวอลุ่มเย็น

bash
# Stop every service first, then use your storage platform to snapshot the
# PostgreSQL, app-data, and Redis volumes as one crash-consistent set.
docker compose -f docker/docker-compose.yml stop

อย่าคัดลอกไดเร็กทอรีข้อมูล PostgreSQL แบบสดด้วย tar เขียนคำนำหน้าชื่อวอลุ่มตามโปรเจ็กต์ ดังนั้นแก้ไข ID วอลุ่มที่ติดตั้งจาก docker inspect หรือแพลตฟอร์มพื้นที่จัดเก็บข้อมูลของคุณ แทนที่จะใช้ป้ายกำกับตัวอักษร SnapOtter-pgdata

การย้ายข้อมูลจาก 1.x (SQLite)

การอัปเกรดจาก SnapOtter 1.x มีคู่มือของตัวเอง ดู การอัปเกรดจาก 1.x ไปยัง 2.0 สรุปสั้นๆ ให้ใช้ volume /data ที่มีอยู่เดิมซ้ำ และ 2.0 จะตรวจจับและนำเข้า /data/snapotter.db โดยอัตโนมัติเมื่อบูตครั้งแรก (หรือกำหนด SQLITE_MIGRATE_PATH เพื่อชี้ไปยังไฟล์นั้นอย่างชัดเจน) สำรอง volume /data ทั้งหมดก่อน ไม่ใช่แค่ snapotter.db: 1.x ใช้โหมด SQLite WAL ดังนั้นคอนเทนเนอร์ที่หยุดทำงานมักจะทิ้งข้อมูลส่วนใหญ่ไว้ใน snapotter.db-wal ข้างๆ snapotter.db ที่แทบจะว่างเปล่า