데이터베이스 스키마는 클래스, 열, 타입, 관계처럼 데이터가 구성되는 방식을 정의하되 데이터 자체는 담지 않는 설계도입니다. 외워 둘 만한 한 줄짜리 삼각 구도가 있습니다. 스키마는 설계도이고, 인스턴스는 한 시점의 데이터이며, 데이터베이스는 건물 전체입니다. 설계도는 드물게 그리고 의도적으로 바뀌지만, 방은 끊임없이 다시 채워집니다.
핵심 요약
| 질문 | 답변 |
|---|---|
| 정의 | 메타데이터로서의 구조: 테이블/클래스, 타입이 지정된 열, 키, 제약 조건 |
| 아닌 것 | 데이터 – 데이터는 쓰기마다 바뀌는 인스턴스에 해당 |
| 세 가지 고도 | 논리(설계) · 물리(스토리지) · 뷰(각 소비자가 보는 것) |
| “스키마리스”? | 잘못된 명칭 – 읽기 시점 스키마는 강제 시점을 쿼리 시점으로 옮길 뿐 |
| 진화 방식 | 마이그레이션: 버전이 매겨지고 스크립트로 작성되며 리뷰 가능한 변경 |
글로 적은 설계도
스키마를 그 본래의 언어로 적은 것입니다. 테이블 두 개, 키, 제약 조건 하나, 인덱스 하나, 뷰 하나로, 핵심 어휘 대부분이 여기에 있습니다:
CREATE TABLE users (
id bigserial PRIMARY KEY,
email text NOT NULL UNIQUE, -- 제약 조건: 중복 불가
role text NOT NULL DEFAULT 'member'
);
CREATE TABLE orders (
id bigserial PRIMARY KEY,
user_id bigint NOT NULL REFERENCES users(id), -- 관계
total numeric(10,2) CHECK (total >= 0), -- 데이터가 따라야 하는 규칙
placed timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX orders_by_user ON orders (user_id, placed); -- 물리 계층
CREATE VIEW recent_orders AS -- 뷰 계층
SELECT * FROM orders WHERE placed > now() - interval '30 days';
스키마가 유연한 플랫폼에서는 같은 설계도가 저장하는 데이터로부터 자라납니다. 첫 쓰기에서 타입이 지정된 열이 추론되고, 대시보드에 즉시 표시됩니다:
// JavaScript / Node.js — Back4app JS SDK
// The schema grows typed columns from what you save
const event = new Parse.Object('Event');
event.set('name', 'Launch day'); // String
event.set('seats', 120); // Number
event.set('startsAt', new Date('2026-09-01T18:00:00Z')); // Date
event.set('venue', new Parse.GeoPoint(38.72, -9.14)); // GeoPoint
await event.save(); // columns exist, typed, visible in the dashboard // Flutter / Dart — Back4app Flutter SDK
// The schema grows typed columns from what you save
final event = ParseObject('Event')
..set('name', 'Launch day') // String
..set('seats', 120) // Number
..set('startsAt', DateTime.parse('2026-09-01T18:00:00Z')) // Date
..set('venue', ParseGeoPoint(latitude: 38.72, longitude: -9.14));
await event.save(); // columns exist, typed, visible in the dashboard // iOS / Swift — Back4app Swift SDK
// The schema is a typed struct — columns mirror the model
struct Event: ParseObject {
var objectId: String?; var createdAt: Date?
var updatedAt: Date?; var ACL: ParseACL?; var originalData: Data?
var name: String? // String column
var seats: Int? // Number column
var startsAt: Date? // Date column
var venue: ParseGeoPoint? // GeoPoint column
}
var event = Event(); event.name = "Launch day"; event.seats = 120
event.save { _ in } // columns exist, typed, visible in the dashboard // Android / Kotlin — Back4app Android SDK
// The schema grows typed columns from what you save
val event = ParseObject("Event").apply {
put("name", "Launch day") // String
put("seats", 120) // Number
put("startsAt", Date()) // Date
put("venue", ParseGeoPoint(38.72, -9.14)) // GeoPoint
}
event.saveInBackground() // columns exist, typed, visible in the dashboard 논리 vs. 물리 vs. 뷰
| 구분 | 이쪽 | vs. 저쪽 |
|---|---|---|
| 스키마 vs. 인스턴스 | 설계도, 드물게 바뀜 | 데이터 스냅숏, 끊임없이 바뀜 |
| 논리 vs. 물리 | 엔진 독립적인 설계 | 엔진별 스토리지 결정 |
| 설계도 vs. 네임스페이스 | 앱의 “스키마” | CREATE SCHEMA – 권한을 가진, 이름 있는 객체 컨테이너 |
| 쓰기 시점 vs. 읽기 시점 스키마 | 데이터가 들어오기 전에 검증(관계형) | 사용 시점에 강제(문서형의 기본값) |
| 트랜잭션 vs. 분석 | 정규화된 앱 스키마 | 집계를 위해 만든 스타/스노플레이크 형태 |
세 번째 행은 대부분의 설명 글이 건너뛰는 실제 모호함을 해소합니다. 일부 엔진에서 이 단어는 테이블을 담고 권한이 걸린 컨테이너인 네임스페이스도 가리키므로, “스키마”는 앱의 설계도일 수도 있고 데이터베이스 안의 폴더일 수도 있으며, 어느 쪽인지는 문맥이 정합니다.
쓰기 시점 스키마 vs. 읽기 시점 스키마
“스키마리스” 데이터베이스에도 스키마가 있습니다. 비용을 다른 방식으로 청구할 뿐입니다. 쓰기 시점 스키마(schema-on-write)는 데이터가 들어오기 전에 구조를 검증합니다. 잘못된 타입, 누락된 필드, 깨진 참조는 입구에서 거부됩니다. 읽기 시점 스키마(schema-on-read)는 쓰기를 유연하게 받아들이고 데이터를 사용할 때 기대를 강제합니다. 반복은 빨라지지만, 모든 읽는 쪽이 검증자가 됩니다. 현대의 입장은 전쟁이 아니라 다이얼입니다. 문서 플랫폼은 검증을 추가하고, 관계형 엔진은 JSON 열을 추가하며, 관리형 백엔드는 그 중간을 택합니다. 열마다 타입이 추론되고 강제되면서도, 새 열은 마이그레이션 절차 없이 생깁니다. 엄격함의 문제는 사실 소유권의 문제입니다. 잘못된 레코드를 누가 찾아내는가, 쓰기 시점의 데이터베이스인가, 새벽 2시에 돌아가는 애플리케이션 코드인가?
스키마는 어떻게 진화하는가
설계도는 첫 초안보다 오래 살아남으며, 마이그레이션은 설계도가 혼란 없이 바뀌는 방법입니다. 각 스키마 변경은 버전이 매겨진 스크립트입니다. 열을 추가하고, 값을 채우고(backfill), 제약 조건을 강화하는 작업을 순서대로, 모든 환경에서, 그것에 의존하는 코드처럼 리뷰를 거쳐 적용합니다. 가치의 대부분은 두 가지 규율에서 나옵니다. 이전 코드와 새 코드가 겹치는 기간에는 변경을 하위 호환으로 만들고(추가 → 마이그레이션 → 제거의 순서를 따르며, 제자리 이름 변경은 하지 않음), 스키마 버전을 저장소에 두어 코드와 구조가 함께 이동하게 하는 것입니다. 대시보드로 관리하는 플랫폼에서도 도구만 다를 뿐 같은 규율이 적용됩니다. 시각적으로 발전시키되 의도적으로 진행하고, 프로덕션에서는 클라이언트 주도의 스키마 변경을 비활성화합니다.
일반적인 사용 사례
- 새 백엔드 설계. 스키마는 데이터 모델링의 산출물입니다. 엔터티와 연결이 클래스, 열, 키가 됩니다.
- 무결성 강제. 음수가 아닌 합계, 고유한 이메일처럼 실행 가능한 규칙으로서의 제약 조건은 버그 리포트가 아니라 엔진이 잡아냅니다.
- 팀 간 계약. 스키마는 백엔드, 프론트엔드, 분석 사이의 공용 어휘이며, ER 다이어그램은 실제와 어긋날 수 없는 문서입니다.
- 성능의 기초 작업. 스키마의 아래층인 인덱스와 물리 레이아웃이 어떤 쿼리가 대규모에서도 빠르게 유지될지를 결정합니다.
- 보안 표면. 스키마 수준의 권한이 클래스별로 누가 무엇을 할 수 있는지 통제합니다. 구조와 접근 제어가 한곳에 있습니다.
스키마는 얼마나 엄격해야 할까? 결정 매트릭스
| 엄격함(쓰기 시점 스키마)을 택해야 하는 경우… | 유연함(추론 + 검증)을 택해야 하는 경우… |
|---|---|
| 데이터 오류의 비용이 큼(금전, 재고) | 매주 제품을 반복 개선하고 있음 |
| 쓰는 주체가 많고 계약은 하나 | 한 팀이 코드와 데이터를 모두 소유 |
| 분석이 안정적인 열에 의존 | 필드가 실제로 레코드마다 다름 |
| 제약 조건이 비즈니스 규칙을 표현 | 규칙이 어차피 서버 측 검증에 있음 |
| 팀에게 마이그레이션이 일상적인 작업 | 마이그레이션 절차가 탐색을 늦춤 |
앱 백엔드를 위한 실용적인 기본값은 배우는 동안은 유연하게, 출시하면서 강화하는 것입니다. 개발 중에는 스키마를 추론하게 하고, 실제 사용자가 오는 날 스키마를 고정합니다(클라이언트의 스키마 변경 금지, add-field 비활성화).
한계와 트레이드오프
- 스키마는 가정을 고정합니다. 열 타입과 카디널리티에 관한 모든 결정은 오늘은 저렴하지만 내일은 마이그레이션이 됩니다. 한 단계 더 큰 규모를 위해 설계해야 합니다.
- 엄격함은 반복에 비용을 부과합니다. 모든 실험이 마이그레이션 통행료를 냅니다. 이는 결함이 아니라 보장의 대가입니다.
- 유연함은 읽는 쪽에 비용을 부과합니다. 읽기 시점 스키마는 검증을 모든 소비자에게 옮깁니다. 규율이 없으면 “유연함”은 “같은 레코드의 다섯 가지 모양”이 됩니다.
- 물리 계층은 문제가 생기기 전까지 보이지 않습니다. 인덱스와 레이아웃은 정확성을 바꾸지 않습니다. 쿼리가 성장을 견디는지만 바꿉니다.
- 네임스페이스 권한은 설계가 아닙니다.
CREATE SCHEMA는 객체를 정리하고 접근을 통제하지만, 설계도를 좋게 만들어 주지는 않습니다.
Back4app에서의 스키마
Back4app은 관리형 데이터베이스, 자동 생성되는 REST 및 GraphQL API, 인증, 파일 스토리지, Cloud Code 서버리스 함수를 결합한 오픈소스 Backend as a Service(BaaS) 플랫폼입니다. 여기서 스키마는 눈에 보이는 일급 화면입니다. 대시보드에서 클래스와 타입이 지정된 열을 정의하거나, 첫 저장으로 추론되게 할 수 있습니다. 위의 코드 탭은 실제로 타입이 지정된 열을 만들며, 모든 클래스에는 기본적으로 objectId, createdAt, updatedAt과 ACL이 추가됩니다. 스키마가 선언하는 모든 것은 자동 생성 API에 즉시 반영되고, 클래스 수준 권한으로 보호되며, 시각적 대시보드에서 둘러볼 수 있습니다. 설계도, 강제, 문서가 하나의 결과물로 합쳐집니다.
자주 묻는 질문
데이터베이스 스키마를 쉽게 설명하면 무엇입니까?
데이터베이스의 설계도입니다. 어떤 테이블이나 클래스가 존재하는지, 각각 어떤 열을 갖는지, 각 열의 타입, 키와 제약 조건, 레코드가 서로 어떻게 연결되는지를 담습니다. 스키마는 메타데이터로, 저장된 데이터가 아니라 구조입니다. 스키마를 바꾸면 데이터가 가질 수 있는 모양이 바뀌고, 데이터 자체는 그 모양 안에 존재합니다.
스키마, 데이터베이스, 인스턴스의 차이는 무엇입니까?
세 가지 확대 수준입니다. 데이터베이스는 엔진과 저장된 데이터를 합친 시스템 전체입니다. 스키마는 그 형식적 구조로, 드물게 그리고 의도적으로 바뀝니다. 인스턴스는 한 시점의 실제 데이터로, 쓰기가 일어날 때마다 바뀝니다. 하나의 스키마, 하나의 데이터베이스, 시간에 따라 끝없이 많은 인스턴스가 있습니다.
논리 스키마와 물리 스키마의 차이는 무엇입니까?
논리 스키마는 엔진에 독립적인 설계입니다. ER 다이어그램이 그리는 엔터티, 속성, 관계, 제약 조건이 여기에 해당합니다. 물리 스키마는 그 설계가 특정 엔진에 구현되는 방식으로, 스토리지 레이아웃, 인덱스, 파티션을 다룹니다. 세 번째 계층인 뷰 스키마 또는 외부 스키마는 각 소비자가 무엇을 보는지 정의합니다. 같은 설계를 세 가지 고도에서 본 것입니다.
스키마에는 실제로 무엇이 들어 있습니까?
스키마 객체입니다. 테이블이나 클래스, 데이터 타입이 지정된 열, 기본 키와 외래 키, NOT NULL, UNIQUE, CHECK 같은 제약 조건, 인덱스, 뷰가 그것입니다. 일부 엔진에서는 "스키마"에 두 번째 의미도 있습니다. 이 객체들을 묶고 접근 권한을 갖는 이름 있는 네임스페이스로, CREATE SCHEMA가 만드는 것이 바로 이것입니다.
NoSQL 데이터베이스에도 스키마가 있습니까?
"스키마리스"는 잘못된 명칭입니다. 스키마는 언제나 존재하며, 문제는 누가 언제 강제하느냐입니다. 관계형 엔진은 쓰기 시점 스키마로, 데이터가 들어오기 전에 구조를 검증합니다. 문서 저장소는 기본적으로 읽기 시점 스키마로, 구조가 애플리케이션의 기대 안에 있고 데이터를 사용할 때 검사됩니다. 이제 대부분의 문서 플랫폼이 검증도 지원하므로, 엄격함은 양자택일이 아니라 조절 가능한 다이얼입니다.
스키마 마이그레이션이란 무엇입니까?
열 추가나 제약 조건 강화처럼, 버전이 매겨지고 스크립트로 작성된 스키마 변경으로, 여러 환경에 걸쳐 점진적으로 순서대로 적용됩니다. 마이그레이션은 스키마가 혼란 없이 진화하는 방법입니다. 각 변경은 리뷰할 수 있고 반복할 수 있으며 되돌릴 수 있고, 스키마 버전은 그것을 전제로 하는 코드베이스와 함께 이동합니다.
스타 스키마와 스노플레이크 스키마란 무엇입니까?
분석 전용 스키마 형태입니다. 스타 스키마는 중앙의 팩트 테이블(이벤트, 판매) 하나를 비정규화된 차원 테이블들이 둘러싸는 구조로, 조인이 적고 집계가 빠릅니다. 스노플레이크 스키마는 그 차원들을 하위 테이블로 정규화한 구조로, 중복은 줄지만 조인이 늘어납니다. 둘 다 리포팅 워크로드를 최적화하며, 애플리케이션 백엔드가 쓰는 트랜잭션 스키마와 경쟁 관계가 아니라 사촌 관계입니다.
Backend as a Service에서는 스키마를 어떻게 정의합니까?
서로 보완적인 두 가지 방법이 있습니다. 대시보드에서 클래스와 타입이 지정된 열을 만드는 시각적 방법, 그리고 첫 객체를 저장하면 클래스와 타입이 지정된 열이 자동으로 생성되고 objectId, createdAt, updatedAt 같은 기본 필드와 ACL을 플랫폼이 추가하는 추론 방식입니다. 이후 프로덕션 강화 단계에서 스키마를 고정합니다. 클라이언트 주도의 스키마 변경을 끄고, 이후의 진화는 대시보드에서 의도적으로 진행합니다.