데이터베이스 선택

이 페이지

배포마다 데이터베이스 어댑터를 하나 선택하세요. 데이터베이스는 콘텐츠 모델, 항목, 사용자, 설정, 플러그인 데이터를 보관합니다. 미디어 바이너리는 별도의 스토리지 백엔드에 속합니다.

개요

DatabaseUse it whenRuntime
SQLite하나의 Node.js 프로세스가 영구 디스크를 가질 때Node.js 또는 로컬 개발
D1사이트가 Cloudflare Workers에서 실행되며 Cloudflare SQL을 써야 할 때Cloudflare Workers
Hyperdrive사이트가 Workers에서 실행되며 기존 PostgreSQL 원본을 써야 할 때Cloudflare Workers
PostgreSQL여러 Node.js 프로세스가 하나의 공유 데이터베이스를 필요로 할 때Node.js
libSQLNode.js 배포가 원격 SQLite 호환 데이터베이스를 필요로 할 때Node.js

D1은 Cloudflare 템플릿의 기본값입니다. SQLite는 가장 간단한 Node.js 옵션이지만, 쓰기 가능한 영구 볼륨과 운영 데이터베이스 백업이 필요합니다.

SQLite

SQLite는 Node.js 내장 데이터베이스 드라이버를 사용하며 Node.js 배포에 가장 간단한 옵션입니다.

import { sqlite } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: sqlite({ url: "file:./data.db" }),
		}),
	],
});

구성

OptionTypeDescription
urlstringfile: 접두사가 있는 파일 경로

파일 경로

url은 file:로 시작해야 합니다:

// Relative path
database: sqlite({ url: "file:./data/emdash.db" });

// Absolute path
database: sqlite({ url: "file:/var/data/emdash.db" });

// From environment variable
database: sqlite({ url: `file:${process.env.DATABASE_PATH}` });

Write-ahead logging

EmDash는 SQLite 데이터베이스를 write-ahead logging(WAL) 모드로 엽니다. 사이트가 실행되는 동안 SQLite는 데이터베이스 옆에 emdash.db-wal과 emdash.db-shm 같은 추가 파일 두 개를 유지합니다. 프로세스는 이를 만들기 위해 데이터베이스 디렉터리에 대한 쓰기 권한이 필요합니다.

-wal 파일에는 아직 메인 데이터베이스 파일에 들어가지 않은 커밋된 변경이 있을 수 있습니다. .db 파일만 복사하지 말고 SQLite 백업 명령으로 백업하세요. SQLite 백업 및 복구를 참조하세요.

WAL은 공유 메모리가 필요하므로 NFS나 SMB 같은 네트워크 파일 시스템이 아니라 로컬 디스크나 블록 볼륨에 데이터베이스를 두세요.

Cloudflare D1

D1은 Cloudflare의 서버리스 SQLite 데이터베이스입니다. Cloudflare Workers에 배포할 때 사용하세요.

import { d1 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: d1({ binding: "DB" }),
		}),
	],
});

구성

OptionTypeDefaultDescription
bindingstring—wrangler.jsonc의 D1 바인딩 이름
sessionstring"disabled"읽기 복제 모드(아래 참조)
bookmarkCookiestring"__em_d1_bookmark"세션 북마크용 쿠키 이름

Wrangler 바인딩

wrangler.jsonc

{
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "emdash-db"
    }
  ]
}

wrangler.toml

[[d1_databases]]
binding = "DB"
database_name = "emdash-db"

Wrangler는 배포 중 이 바인딩에서 누락된 D1 데이터베이스를 프로비저닝할 수 있습니다. EmDash 마이그레이션은 별도 단계입니다. 전체 바인딩 세트는 Cloudflare에 배포를, 마이그레이션 런북은 핵심 데이터베이스 마이그레이션 관리를 따르세요.

읽기 복제본

D1은 전 세계에 분산된 사이트의 읽기 지연을 낮추기 위한 읽기 복제를 지원합니다. 활성화되면 읽기 쿼리는 항상 기본 데이터베이스에 가지 않고 가까운 복제본으로 라우팅됩니다.

EmDash는 D1 Sessions API로 이를 투명하게 관리합니다. session 옵션으로 활성화하세요:

import { d1 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: d1({
				binding: "DB",
				session: "auto",
			}),
		}),
	],
});

세션 모드

ModeBehavior
"disabled"세션 없음. 모든 쿼리가 기본으로 감. 기본값.
"auto"익명 요청은 가장 가까운 복제본에서 읽음. 인증된 사용자는 북마크 쿠키로 read-your-writes 일관성을 얻음.
"primary-first""auto"와 같지만 첫 쿼리는 항상 기본으로 감. 쓰기가 매우 잦은 사이트에 사용.

작동 방식

  • 익명 방문자는 first-unconstrained를 받습니다 — 읽기는 최저 지연을 위해 가장 가까운 복제본으로 갑니다. 익명 사용자는 쓰지 않으므로 일관성 보장이 필요 없습니다.
  • 인증된 사용자(편집자, 작성자)는 북마크 기반 세션을 받습니다. 쓰기 후 북마크 쿠키가 다음 요청이 적어도 그 상태를 보도록 보장합니다.
  • 쓰기 요청(POST, PUT, DELETE)은 항상 기본 데이터베이스에서 시작합니다.
  • 빌드 시점 쿼리(Astro 콘텐츠 컬렉션)는 세션을 완전히 우회하고 기본을 직접 사용합니다.

libSQL

libSQL은 원격 연결을 지원하는 SQLite 포크입니다. Cloudflare D1 없이 원격 데이터베이스가 필요할 때 사용하세요.

import { libsql } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: libsql({
				url: process.env.LIBSQL_DATABASE_URL,
				authToken: process.env.LIBSQL_AUTH_TOKEN,
			}),
		}),
	],
});

구성

OptionTypeDescription
urlstring데이터베이스 URL(libsql://... 또는 file:...)
authTokenstring원격 데이터베이스용 런타임 인증 토큰(로컬에서는 선택)
migrationAuthTokenEnvstring마이그레이션 토큰 변수 이름(기본값 TURSO_AUTH_TOKEN)

로컬 개발

개발 중에는 로컬 libSQL 파일을 사용하세요:

database: libsql({ url: "file:./data.db" });

PostgreSQL

PostgreSQL은 완전한 관계형 데이터베이스가 필요한 Node.js 배포에서 지원됩니다.

import { postgres } from "emdash/db";

export default defineConfig({
	integrations: [
		emdash({
			database: postgres({
				connectionString: process.env.DATABASE_URL,
			}),
		}),
	],
});

구성

연결 문자열 또는 개별 매개변수로 연결할 수 있습니다:

// Connection string
database: postgres({
	connectionString: "postgres://user:password@localhost:5432/emdash",
});

// Individual parameters
database: postgres({
	host: "localhost",
	port: 5432,
	database: "emdash",
	user: "emdash",
	password: process.env.DB_PASSWORD,
	ssl: true,
});
OptionTypeDescription
connectionStringstringPostgreSQL 연결 URL
hoststring데이터베이스 호스트
portnumber데이터베이스 포트
databasestring데이터베이스 이름
userstring데이터베이스 사용자
passwordstring데이터베이스 비밀번호
sslbooleanSSL 활성화
pool.minnumber최소 풀 연결(기본값 0)
pool.maxnumber최대 풀 연결(기본값 10)
pool.connectionTimeoutMillisnumber최대 연결 대기(pg 기본값: 0, 타임아웃 없음)
pool.idleTimeoutMillisnumber유휴 클라이언트 수명(pg 기본값: 10,000 ms)
migrationConnectionStringEnvstring마이그레이션 연결 문자열 변수 이름(기본값 DATABASE_URL)

PostgreSQL에 도달할 수 없거나 풀 연결이 없을 때 요청이 기다리는 시간을 제한하려면 pool.connectionTimeoutMillis를 0이 아닌 값으로 설정하세요. 풀이 닫힐 때까지 유휴 클라이언트를 열어 두려면 pool.idleTimeoutMillis를 0으로 설정하세요. 옵션을 생략하면 pg 기본값이 유지됩니다.

데이터베이스 역할 요구 사항

EmDash는 자체 PostgreSQL 테이블을 만들고 업데이트합니다. 핵심 마이그레이션은 시스템 및 컬렉션 테이블을 만들고 변경하며, 콘텐츠 유형은 ec_* 테이블을 만들고, 필드 추가 또는 제거는 해당 컬렉션 테이블을 변경합니다. 따라서 구성된 PostgreSQL 역할은 초기 설정뿐 아니라 사이트 수명 동안 스키마 권한이 필요합니다.

EmDash용 정규 역할 하나를 사용하세요. 다음이 필요합니다:

  • 데이터베이스에 대한 CONNECT;
  • 활성 스키마에 대한 USAGE와 CREATE;
  • 모든 EmDash 테이블과 함수의 소유권(직접 또는 소유 역할에 INHERIT로 멤버십); 그리고
  • 해당 테이블에 대한 SELECT, INSERT, UPDATE, DELETE.

슈퍼유저일 필요는 없고, CREATEDB나 CREATEROLE도, 확장 생성도 필요 없습니다. PostgreSQL은 테이블 ALTER 또는 DROP 권한을 제공하지 않습니다. 그런 작업은 객체 소유자와 그 권한을 상속하는 역할에 속합니다. 다른 역할에 테이블 ALL을 부여해도 그 역할이 소유자가 되지 않습니다. EmDash는 SET ROLE을 실행하지 않으므로 상속 없이 구성된 멤버십은 충분하지 않습니다.

대부분의 설치는 일반적으로 public인 데이터베이스의 기존 스키마를 사용할 수 있습니다. 데이터베이스가 EmDash 전용일 때 가장 간단한 옵션입니다. 아래 예에서 emdash_app은 EmDash 연결 문자열의 로그인 역할입니다. 기존 제공자 역할을 쓰거나 전용 로그인을 만드세요. 데이터베이스, 스키마, 역할 이름을 바꿔 관리 연결로 접근을 부여하세요:

GRANT CONNECT ON DATABASE app TO emdash_app;
GRANT USAGE, CREATE ON SCHEMA public TO emdash_app;

이 권한은 역할이 새 객체를 만들 수 있게 합니다. 기존 테이블의 소유자는 바꾸지 않습니다. 기존 사이트에 혼합 소유자가 있으면 PostgreSQL 소유권 복구 런북을 사용하세요.

EmDash는 PostgreSQL의 활성 current_schema()를 사용합니다. 스키마를 만들거나 search_path를 설정하지 않으므로 배포 전에 연결을 확인하세요:

SELECT
  current_database(),
  session_user,
  current_user,
  current_schema(),
  current_setting('search_path');

선택: 전용 스키마 사용

EmDash가 다른 애플리케이션과 데이터베이스를 공유하거나 객체를 public에서 분리하고 싶을 때 전용 스키마를 사용하세요. 선택 사항이며 첫 EmDash 설정 전에 구성하는 것이 가장 쉽습니다. EmDash 전용 데이터베이스에는 별도 스키마가 필요 없습니다.

정규 emdash_app 역할이 이미 있다고 가정하고, 관리 연결로 그 스키마를 만들고 선택하세요:

GRANT CONNECT ON DATABASE app TO emdash_app;
CREATE SCHEMA emdash AUTHORIZATION emdash_app;
ALTER ROLE emdash_app IN DATABASE app SET search_path = emdash;

이것은 기존 설치를 public에서 옮기거나 혼합 소유권을 복구하지 않습니다. 기존 사이트는 현재 스키마를 유지하고 대신 PostgreSQL 소유권 복구 런북을 사용하세요.

연결 풀링

어댑터는 pg.Pool을 사용합니다. 배포에 맞게 풀 크기를 조정하세요:

database: postgres({
	connectionString: process.env.DATABASE_URL,
	pool: { min: 2, max: 20 },
});

Hyperdrive

기존 PostgreSQL — 또는 Postgres 호환(예: PlanetScale Postgres) — 데이터베이스를 백엔드로 Cloudflare Workers에서 EmDash를 실행하려면 hyperdrive() 어댑터를 사용하세요. Hyperdrive는 Cloudflare 네트워크에서 연결을 풀링하고 가속합니다. EmDash의 PostgreSQL 방언이 쿼리를 실행합니다.

import { hyperdrive, r2 } from "@emdash-cms/cloudflare";

export default defineConfig({
	integrations: [
		emdash({
			database: hyperdrive({ binding: "HYPERDRIVE" }),
			storage: r2({ binding: "MEDIA" }),
		}),
	],
});

요구 사항

  • 사이트에 pg >= 8.16.3 설치(pnpm add pg)
  • compatibility_flags: ["nodejs_compat"]
  • compatibility_date >= "2024-09-23"

설정

먼저 PostgreSQL 역할을 준비하세요. 그런 다음 해당 역할의 연결 문자열로 Hyperdrive 구성을 만들고 Wrangler 구성에 바인딩을 추가하세요:

wrangler hyperdrive create emdash-db \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full" \
  --caching-disabled

wrangler.jsonc

{
  "hyperdrive": [
    {
      "binding": "HYPERDRIVE",
      "id": "<your-hyperdrive-id>"
    }
  ]
}

wrangler.toml

[[hyperdrive]]
binding = "HYPERDRIVE"
id = "<your-hyperdrive-id>"

구성

OptionTypeDefaultDescription
bindingstring"HYPERDRIVE"기본(캐싱 비활성) Hyperdrive 바인딩 이름
cachedBindingstring—익명 읽기용 선택적 캐싱 활성 바인딩(아래 참조)
preferUncachedAfterWriteMsnumber60000*콘텐츠 게시 후 익명 공개 읽기에서 이 밀리초 동안 binding 선호(Hyperdrive max_age에 맞춤)
migrationConnectionStringEnvstringCLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>emdash migrate용 직접 PostgreSQL 원본 URL을 담은 환경 변수
maxnumber5Hyperdrive로의 Worker 내 연결 풀 최대 크기

*기본값 60000은 cachedBinding이 설정된 경우에만 적용되며, 그렇지 않으면 무시됩니다.

캐시에서 익명 읽기 제공

기본적으로 Hyperdrive 캐싱을 완전히 끕니다. 관리와 쓰기에 read-after-write 일관성이 필요하기 때문입니다. 하지만 GET 또는 HEAD를 쓰는 익명 공개 요청은 짧은 지연 창을 허용할 수 있습니다. 그 절충이 받아들일 만하면 같은 데이터베이스에 두 개의 Hyperdrive 구성을 실행하세요. 캐싱 끈 것(기본 binding)과 캐싱 켠 것(cachedBinding)입니다. EmDash는 그 익명 공개 요청을 캐시 활성 바인딩으로, 다른 모든 요청을 캐시 없는 기본으로 라우팅합니다.

# Primary — caching OFF (used by admin, auth'd requests, writes, migrations)
wrangler hyperdrive create emdash-db \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full" \
  --caching-disabled

# Cached — SAME database role and connection string, caching ON
wrangler hyperdrive create emdash-db-cached \
  --connection-string "postgres://user:password@host/db?sslmode=verify-full"
{
	"hyperdrive": [
		{ "binding": "HYPERDRIVE", "id": "<caching-disabled-id>" },
		{ "binding": "HYPERDRIVE_CACHED", "id": "<caching-enabled-id>" }
	]
}
database: hyperdrive({ binding: "HYPERDRIVE", cachedBinding: "HYPERDRIVE_CACHED" });

이것은 캐싱에 대해 Cloudflare가 문서화한 두 구성 패턴입니다. EmDash는 요청마다 어느 바인딩을 쓸지 결정합니다:

  • 공개 사이트 경로의 익명 읽기(GET/HEAD, 세션 없음, /_emdash 아래 아님) → 캐시 활성 cachedBinding. 단, 콘텐츠 게시 후 짧은 창(기본 60초; preferUncachedAfterWriteMs를 Hyperdrive max_age로 설정)에서는 EmDash가 캐시 없는 binding을 선호해 리빌드가 아직 오래된 Hyperdrive 결과로 엣지/객체 캐시를 다시 채우지 못하게 합니다.
  • 인증된 요청(편집자, 작성자) → 캐시 없는 binding.
  • 변경 요청(POST, PUT, PATCH, DELETE, 익명 포함) → 캐시 없는 binding.
  • /_emdash 아래의 모든 요청(관리, 설정, 인증, 내부 API), 익명 GET이라도 → 캐시 없는 binding.
  • 런타임 마이그레이션과 콜드 스타트 → 항상 기본 binding.
  • 배포 관리 마이그레이션 → migrationConnectionStringEnv로 PostgreSQL 원본에 직접 연결. Hyperdrive 바인딩은 쓰지 않습니다.

선택: 별도 캐시 역할 사용

마이그레이션, 설정, 인증된 요청, 명시적 쓰기 요청은 항상 기본 binding을 사용합니다. cachedBinding용 별도 역할에는 스키마 소유권이나 CREATE가 필요 없지만, CONNECT, 스키마 USAGE, 공개 사이트가 쓰는 모든 테이블에 대한 SELECT가 필요합니다.

익명 공개 GET과 HEAD 요청은 리다이렉트 히트와 404도 기록할 수 있습니다. 그 기능을 유지하려면 캐시 역할에 추가로 _emdash_redirects에 대한 UPDATE와 _emdash_404_log에 대한 SELECT, INSERT, UPDATE, DELETE가 필요합니다. 공개 GET 또는 HEAD 중에 쓰는 플러그인이나 애플리케이션 코드는 더 필요할 수 있습니다. 제한된 캐시 역할로 사이트를 테스트하지 않았다면 두 바인딩에 같은 역할을 사용하세요.

EmDash가 초기 마이그레이션을 마친 뒤 캐시 역할을 추가하세요. 아래 예는 선택적 emdash 스키마를 사용합니다. public 같은 활성 스키마로 바꾸세요. 제공자의 관리 역할로 로그인과 데이터베이스 설정을 만드세요:

CREATE ROLE emdash_cached LOGIN PASSWORD 'replace-with-a-secret';
GRANT CONNECT ON DATABASE app TO emdash_cached;
ALTER ROLE emdash_cached IN DATABASE app SET search_path = emdash;

그런 다음 스키마와 테이블 소유자인 emdash_app으로 연결해 기존 및 향후 테이블에 대한 접근을 부여하세요:

GRANT USAGE ON SCHEMA emdash TO emdash_cached;
GRANT SELECT ON ALL TABLES IN SCHEMA emdash TO emdash_cached;
GRANT UPDATE ON emdash._emdash_redirects TO emdash_cached;
GRANT SELECT, INSERT, UPDATE, DELETE ON emdash._emdash_404_log TO emdash_cached;

ALTER DEFAULT PRIVILEGES IN SCHEMA emdash
  GRANT SELECT ON TABLES TO emdash_cached;

두 역할로 연결해 cachedBinding을 켜기 전에 같은 current_database()와 current_schema()를 보고하는지 확인하세요. 공유 스키마에서 GRANT SELECT ON ALL TABLES는 관련 없는 테이블도 노출합니다. 대신 개별 EmDash 테이블에 접근을 부여하고, 컬렉션이나 다른 스키마 객체가 추가될 때 그 권한을 업데이트하세요.

핵심 마이그레이션

EmDash는 지원되는 모든 방언에 대해 기본적으로 핵심 마이그레이션을 자동 실행합니다. Astro 빌드와 동기화도 검증되고 비밀이 없는 .emdash/migrations.json을 내보내며, emdash migrate가 배포 전에 적용할 수 있습니다. SQLite, libSQL, PostgreSQL, D1, Hyperdrive 뒤의 직접 PostgreSQL 원본에는 배포 실행기가 있습니다.

대상 자격 증명, CI 직렬화, auto/check/manual 런타임 정책, 알 수 없는 레코드나 모호한 D1 쓰기 복구는 핵심 데이터베이스 마이그레이션 관리를 참조하세요.

PostgreSQL의 경우 런타임 마이그레이션은 구성된 연결을 통해 실행되고, Hyperdrive 런타임 마이그레이션은 항상 기본 바인딩을 사용합니다. 배포 관리 Hyperdrive 마이그레이션은 PostgreSQL 원본에 직접 연결합니다. 핵심 마이그레이션은 테이블, 인덱스, 함수를 만들고, 열과 제약을 변경하거나 삭제하며, 기존 행을 업데이트할 수 있습니다. 연결하고 행을 수정할 수 있지만 기존 EmDash 객체를 소유하지 않는 역할은 충분하지 않습니다. 런타임 마이그레이션이 설정 전에 실행되므로 설정 마법사는 누락된 데이터베이스 권한을 복구할 수 없습니다.

데이터베이스가 비어 있고(컬렉션 없음) 설정 마법사가 완료되지 않았다면 EmDash는 첫 부팅 시 시드 파일도 적용합니다. 시드는 .emdash/seed.json, package.json#emdash.seed의 경로, 또는 seed/seed.json — 먼저 찾은 것 — 에서 읽고 컴파일 시 빌드에 인라인됩니다. 없으면 내장 기본 시드가 사용됩니다. 기존 데이터베이스에 대한 이후 부팅은 콘텐츠를 그대로 둡니다.

환경마다 별도 데이터베이스 사용

개발, 미리보기, 스테이징, 프로덕션에 각각 자체 데이터베이스를 주세요. 프로덕션을 가리키는 미리보기 배포는 라이브 데이터에 대해 핵심 마이그레이션이나 파괴적인 콘텐츠 모델 명령을 실행할 수 있습니다.

Cloudflare에서는 각 D1 또는 Hyperdrive 바인딩을 일치하는 Wrangler 환경 아래에 정의하고 Wrangler 명령에 --env를 전달하세요. Node.js에서는 각 런타임 환경에 다른 데이터베이스 URL을 주입하세요. 자격 증명은 astro.config.mjs가 아니라 런타임 시크릿에 두세요.