데이터베이스 선택

이 페이지

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

개요

데이터베이스사용하는 경우런타임
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" }),
		}),
	],
});

구성

옵션타입설명
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" }),
		}),
	],
});

구성

옵션타입기본값설명
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",
			}),
		}),
	],
});

세션 모드

모드동작
"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,
			}),
		}),
	],
});

구성

옵션타입설명
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,
});
옵션타입설명
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가 다른 애플리케이션과 데이터베이스를 공유하거나 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

hyperdrive() 어댑터를 사용하면 기존 PostgreSQL(또는 PlanetScale Postgres 같은 Postgres 호환) 데이터베이스를 백엔드로 두고 Cloudflare Workers에서 EmDash를 실행할 수 있습니다. 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>"

구성

옵션타입기본값설명
bindingstring"HYPERDRIVE"기본(캐싱 비활성화) Hyperdrive 바인딩 이름
cachedBindingstring—익명 읽기를 위한, 캐싱이 활성화된 선택적 바인딩(아래 참고)
preferUncachedAfterWriteMsnumber60000*콘텐츠를 게시한 후 이 ms 동안 익명 공개 읽기에서 binding을 우선 사용(Hyperdrive의 max_age와 맞추세요)
migrationConnectionStringEnvstringCLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<BINDING>emdash migrate가 사용하는 PostgreSQL 오리진 직접 URL이 들어 있는 환경 변수
maxnumber5Worker 내부에서 Hyperdrive로 향하는 연결 풀의 최대 크기

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

캐시에서 익명 읽기 제공

기본적으로는 Hyperdrive 캐싱을 완전히 비활성화합니다. 관리자와 쓰기 작업에는 쓰기 후 읽기 일관성이 필요하기 때문입니다. 하지만 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로 설정하세요)에는 재빌드가 아직 오래된 Hyperdrive 결과로 엣지/오브젝트 캐시를 다시 채우지 않도록 EmDash가 캐시되지 않는 binding을 우선 사용합니다.
  • 인증된 요청(편집자, 작성자) → 캐시되지 않는 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가 아니라 런타임 시크릿에 보관하세요.