데이터베이스
database 묶음의 오퍼레이션 13 개입니다.
| 메서드 | 경로 | 하는 일 |
|---|---|---|
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/database | 데이터베이스 자격 여부 |
| POST | /v1/orgs/{orgSlug}/projects/{projectName}/database | 데이터베이스를 만든다 (runlot pg create) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/database/connect | psql·드라이버가 붙는 데 필요한 전부 |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/database/endpoint | 자격 없는 접속 끝점 (runlot port-forward) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/database/tokens | 살아 있는 단기 자격 목록 |
| POST | /v1/orgs/{orgSlug}/projects/{projectName}/database/tokens | 단기 기계 자격 발급 (runlot pg token) |
| DELETE | /v1/orgs/{orgSlug}/projects/{projectName}/database/tokens/{tokenUser} | 단기 자격 폐기 |
| POST | /v1/orgs/{orgSlug}/projects/{projectName}/database/export | 진단 이미지를 하나 만든다 (runlot pg export) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/database/generations | 백업 세대 목록 (runlot pg generations) |
| POST | /v1/orgs/{orgSlug}/projects/{projectName}/database/restore | 세대로 되돌린다 (runlot pg restore) |
| POST | /v1/orgs/{orgSlug}/projects/{projectName}/database/delete | 데이터베이스를 없앤다 (runlot pg delete) |
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/database/operations/{opId} | 연산 하나의 진행 |
| POST | /v1/orgs/{orgSlug}/projects/{projectName}/database/operations/{opId}/abort | 진행 중인 연산을 중단한다 (runlot pg abort) |
GET /v1/orgs/{orgSlug}/projects/{projectName}/database
답은 {db: bool} 하나다. 크기·세대 같은 실물 상태는 노드가 아는
사실이라 여기 없다.
자격(비밀번호)은 이 답에 없다. 이 라우트는 viewer 도 받는다 —
접속 정보는 /database/connect 로 갈렸다.
operationId getDatabase
| 응답 | 뜻 | 본문 |
|---|---|---|
| 200 | 자격 여부 | object |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/projects/{projectName}/database
행이 곧 자격이다. 자격이 켜지면 node-agent 가 다음 수렴에서 그
프로젝트의 workerd 를 env.db 가 붙은 설정으로 교체한다 — 배포도
epoch 변경도 필요 없다.
멱등이고, 비밀번호를 굴리지 않는다. 두 번째 호출은 행에 있는 값을 다시 읽어 같은 접속 정보를 답한다. 굴리면 첫 응답을 받아 적어 둔 접속 문자열이 조용히 죽고, 죽었다는 신호는 다음 로그인 실패뿐이다.
operationId createDatabase
| 응답 | 뜻 | 본문 |
|---|---|---|
| 200 | 자격과 접속 정보 | DatabaseConnect |
| 403 | — | — |
| 404 | — | — |
GET /v1/orgs/{orgSlug}/projects/{projectName}/database/connect
/database 와 나눈 이유는 역할이다. "데이터베이스가 있는가"는
viewer 가 알아도 되는 사실이고 비밀번호는 아니다. 한 응답에 둘을 담고
역할에 따라 필드를 지우면, 지우는 것을 잊은 경로 하나가 곧 유출이다.
member 이상만 받는다.
operationId getDatabaseConnect
| 응답 | 뜻 | 본문 |
|---|---|---|
| 200 | 접속 정보 | DatabaseConnect |
| 403 | — | — |
| 404 | 프로젝트가 없거나 데이터베이스를 아직 만들지 않았다 | Error |
GET /v1/orgs/{orgSlug}/projects/{projectName}/database/endpoint
host·port·database·sslmode 만이다. viewer 도 받는다 — 비밀이 없다.
runlot port-forward 는 로그인 세션으로 붙으므로 (front 가 세션을
검증한다, docs/pg-driver-support.md §4.4) 비밀번호가 필요 없고, 이
끝점만 있으면 된다.
operationId getDatabaseEndpoint
| 응답 | 뜻 | 본문 |
|---|---|---|
| 200 | 끝점 | object |
| 403 | — | — |
| 404 | — | — |
GET /v1/orgs/{orgSlug}/projects/{projectName}/database/tokens
비밀번호도 검증기도 없다. member.
operationId listDatabaseTokens
| 응답 | 뜻 | 본문 |
|---|---|---|
| 200 | 목록 | object |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/projects/{projectName}/database/tokens
접속 문자열에 넣는 user/password 한 벌. 비밀번호는 이 응답에만 있다 — CP 는 SCRAM 검증기만 두고, 잃으면 새로 발급한다. 세션은 그래도 프로젝트의 롤로 열린다: 토큰 사용자는 인증의 이름이지 엔진 롤이 아니다. member.
operationId createDatabaseToken
본문 application/json · object
| 응답 | 뜻 | 본문 |
|---|---|---|
| 201 | 발급된 자격 | DatabaseToken |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
DELETE /v1/orgs/{orgSlug}/projects/{projectName}/database/tokens/{tokenUser}
다음 연결부터 거절이다. 없는 것을 지우면 404. member.
operationId deleteDatabaseToken
| 응답 | 뜻 | 본문 |
|---|---|---|
| 204 | 폐기했다 | — |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/projects/{projectName}/database/export
백업이 아니다 (docs/env-db-assembly.md §5). 만들어지는 것은 현재
(project, epoch) 아래의 불변 진단 키이고, latest·freshness·
retention 어느 포인터도 움직이지 않으며 다음 자동 백업의 이름에도
영향을 주지 않는다.
정기 백업은 이제 돈다 (Phase 5, docs/phase5.md): node-agent 가
스케줄러와 노드 전역 세마포어를 소유하고 스텝 세대를 RPO 1h 격자로
뜬다. 그 세대는 gen/<project>/<epoch>/ 에 살고
…/database/generations 가 나열하며 …/database/restore 가 고른다.
이 경로가 만드는 진단 이미지는 그 네임스페이스 밖에 남는다 —
복원이 고를 수 없고, 프루닝이 세지 않으며, latest 를 못 움직인다.
멱등이 아니다. 부를 때마다 이미지가 하나씩 생긴다 — 서버가 요청마다 새 stamp 를 짓기 때문이다. 멱등하게 흡수되는 것은 노드까지 내려간 뒤의 같은 stamp 재시도뿐이다.
member 이상만 받는다. 이미지는 데이터베이스 전체라 viewer 가 한 번의 호출로 가져갈 것이 아니다.
느리다. 응답은 덤프가 끝난 뒤에 온다 — 크기에 비례하고, 그동안 그 프로젝트의 액터는 붙잡혀 있다.
operationId exportDatabase
| 응답 | 뜻 | 본문 |
|---|---|---|
| 200 | 만들어진 진단 키 | DatabaseExport |
| 403 | — | — |
| 404 | 프로젝트가 없거나 데이터베이스를 아직 만들지 않았다 | Error |
| 409 | 배치가 정지 상태다 (suspended). 진단 export 는 도는 incarnation 에서만 뜬다 — 정지된 프로젝트의 데이터를 꺼내는 것은 복원 경로의 일이다. | Error |
| 502 | 노드가 이미지를 만들지 못했다 (export_failed) | Error |
| 503 | 내보낼 수 있는 상태가 아니다 — 홈 노드 주소가 없거나 (no_home_node), CP 에 노드 admin 경로가 설정되지 않았다 (no_node_admin). | Error |
GET /v1/orgs/{orgSlug}/projects/{projectName}/database/generations
복원이 고를 수 있는 것 전부다 (docs/phase5.md B1). CP 의
db_generations 표를 그대로 읽는다 — 오프사이트를 훑지 않는다:
bytes·sha256 가 CP 에 있어야 복원이 받은 바이트를 검증한다.
lastCheckedAt 은 무변경 스킵의 하트비트다. 세대가 늘지 않는 것과
백업이 죽은 것은 다른 일이고, 이 값이 그 둘을 가른다 — 비어 있으면 아직
한 번도 확인하지 않았다는 뜻이다.
member 이상만 받는다. 목록에는 오프사이트 키와 다이제스트가 실린다.
데이터베이스 자격을 요구하지 않는다. pg delete 뒤에도 최종 안전
세대는 남고, 그것을 보는 것이 이 목록의 마지막 쓸모다.
operationId listGenerations
| 응답 | 뜻 | 본문 |
|---|---|---|
| 200 | 세대 목록 (최신 순) | DatabaseGenerations |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/projects/{projectName}/database/restore
세대 복원은 퇴거 + 재활성이다 (docs/phase5.md B2). pg.restore 가
빈 저장소만 받는 성질이 이 모양을 정한다: draining → sealing(현재
데이터로 pre-restore 세대를 먼저 남긴다 — 되돌릴 길) → committing(고른
세대를 final_gen 에 적는다) → 재활성(epoch+1).
그 사이의 쓰기는 사라진다. 그래서 admin 이상이고, CLI 는 프로젝트 이름을 다시 입력받는다.
latest 를 서버가 해석하지 않는다. 목록에서 본 (epoch, seq) 를
그대로 싣는다 — 서버가 "가장 최근"을 고르면 그 사이에 정기 세대가 하나
더 뜬 경우 사용자가 본 것과 다른 세대로 간다.
202 다. 돌아오는 것은 결과가 아니라 연산 id 이고, 진행은
…/database/operations/{opId} 로 본다.
operationId restoreDatabase
본문 application/json · RestoreRequest
| 응답 | 뜻 | 본문 |
|---|---|---|
| 202 | 연산을 열었다 | OperationStarted |
| 400 | — | — |
| 403 | — | — |
| 404 | 프로젝트가 없거나 데이터베이스를 아직 만들지 않았다 | Error |
| 409 | 이미 다른 연산이 진행 중이거나 (operation_in_progress) 배치가 정지 상태다 (suspended). | Error |
| 503 | 복원할 수 있는 상태가 아니다 (홈 노드 없음 등) | Error |
POST /v1/orgs/{orgSlug}/projects/{projectName}/database/delete
draining → sealing(최종 안전 세대를 final 로 남긴다) →
committing(project_databases 행 삭제; 노드는 다음 수렴에서 env.db
없는 프로세스로 교체하고 데이터 디렉터리를 tombstone 으로 옮긴다,
docs/phase5.md B3). admin 이상.
confirm 이 프로젝트 이름과 정확히 같아야 한다. 다르면 400
confirm_mismatch 이고 아무것도 시작되지 않는다. CLI 의 프롬프트만으로는
부족하다 — --yes 를 단 스크립트가 잘못된 디렉토리에서 돌면 프롬프트가
아예 뜨지 않는다.
DELETE 메서드가 아닌 이유: 이 요청은 행 하나를 지우는 것이 아니라 202
로 시작해 분 단위로 끝나는 연산이고, 본문에 확인 문자열을 받아야 한다 —
DELETE 의 본문은 지나는 프록시마다 취급이 다르다.
operationId deleteDatabase
본문 application/json · DeleteRequest
| 응답 | 뜻 | 본문 |
|---|---|---|
| 202 | 연산을 열었다 | OperationStarted |
| 400 | 확인 문자열이 프로젝트 이름과 다르다 (confirm_mismatch) | Error |
| 403 | — | — |
| 404 | 프로젝트가 없거나 데이터베이스를 아직 만들지 않았다 | Error |
| 409 | 이미 다른 연산이 진행 중이다 (operation_in_progress) | Error |
GET /v1/orgs/{orgSlug}/projects/{projectName}/database/operations/{opId}
복원·삭제의 진행을 본다. CLI 가 2 초마다 두드리고 phase 가 바뀔 때마다 찍는다.
연산이 프로젝트 아래에 사는 이유는 id 가 자격이 아니기 때문이다.
cp-core 의 /v1/operations/{opId} 는 id 를 아는 클라이언트(노드·
cp-public)에게 답하지만, 사용자 표면이 그러면 id 하나가 다른 조직의 연산
상태를 여는 열쇠가 된다. 이 라우트는 답하기 전에 연산의 projectId 가
경로의 프로젝트인지 보고, 아니면 없는 것과 같은 404 를 준다.
member 이상.
operationId getDatabaseOperation
| 응답 | 뜻 | 본문 |
|---|---|---|
| 200 | 연산 상태 | Operation |
| 403 | — | — |
| 404 | 프로젝트에 그 연산이 없다 (다른 프로젝트의 연산도 여기로 온다) | Error |
POST /v1/orgs/{orgSlug}/projects/{projectName}/database/operations/{opId}/abort
되돌릴 수 없는 phase 앞에서만 된다 (docs/phase5.md B4):
evict·restore_generation·delete 는 committing 전까지, restore 는 불가.
중단은 배치를 active 로 되돌리고 terminalCode=aborted 를 남긴다.
admin 이상. 서버는 먼저 읽고 그 다음에 중단한다 — 순서가 반대면 남의 프로젝트 연산을 실제로 중단한 뒤에 404 를 돌려주게 된다.
operationId abortDatabaseOperation
| 응답 | 뜻 | 본문 |
|---|---|---|
| 200 | 중단된 연산의 상태 | Operation |
| 403 | — | — |
| 404 | 프로젝트에 그 연산이 없다 | Error |
| 409 | 돌이킬 수 없는 지점을 지났다 (not_abortable) — committing 이후이거나 restore 연산이다. | Error |