공통 질문
SQLite
파일 기반의 경량 관계형 데이터베이스 관리 시스템(RDBMS)
자체적으로 실행 가능한 "임베디드(Embedded) 데이터베이스"**입니다.
서버가 필요 없으며, 애플리케이션에 직접 통합할 수 있습니다.
특징
1. 파일 기반 데이터베이스
- 데이터는 하나의 단일 파일(.sqlite, .db 등)에 저장됨.
- 파일 이동만으로도 데이터베이스 백업 및 복사가 가능함.
2. 서버가 필요 없는 임베디드 데이터베이스
- PostgreSQL, MySQL과 달리 클라이언트-서버 아키텍처가 아님.
- 애플리케이션 내부에서 직접 데이터베이스를 실행할 수 있음.
- 별도의 설정 없이 실행 가능(Zero Configuration).
3. 경량(Lightweight)
- 바이너리 크기가 매우 작음 (약 500KB ~ 1MB)
- 메모리 사용량이 적음 → 모바일 앱, IoT 기기, 브라우저 등에 적합.
4. ACID(원자성, 일관성, 독립성, 지속성) 지원
- 트랜잭션을 안전하게 처리하여 데이터 무결성을 유지.
- WAL(Write-Ahead Logging) 모드를 사용하면 고속 동시 읽기/쓰기 가능.
5. 크로스 플랫폼 지원
- Windows, macOS, Linux, Android, iOS 등 거의 모든 플랫폼에서 동작 가능.
- 데이터베이스 파일을 운영체제 간 쉽게 이동 가능.
6. SQL 표준 지원
- ANSI SQL-92를 대부분 지원.
- JOIN, 트랜잭션, 인덱스, 뷰, 트리거, 서브쿼리 등 지원.
장점
설치 및 설정이 필요 없음
- SQLite는 실행 파일(sqlite3)만 있으면 바로 사용 가능.
빠른 속도 (단일 사용자 환경)
- 읽기 성능이 매우 빠름 (전체 데이터가 파일로 관리됨).
- 작은 데이터베이스에서 MySQL, PostgreSQL보다 성능이 더 좋을 수 있음.
트랜잭션 지원
- BEGIN TRANSACTION, COMMIT, ROLLBACK 지원.
모바일과 임베디드 환경에 최적화
- Android, iOS, IoT, 브라우저(LocalStorage)에서 자주 사용됨.
하나의 파일로 데이터베이스를 관리
- .db, .sqlite 같은 파일 하나에 데이터베이스 저장.
- 파일을 직접 복사하거나 전송 가능.
단점
SQLite에서 읽기/쓰기 동시 수행이 불가능함
- SQLite의 기본 설정(PRAGMA journal_mode=DELETE)에서는 읽기와 쓰기를 동시에 수행할 수 없음.
- 하나의 트랜잭션이 쓰기(UPDATE, INSERT, DELETE)를 실행하면, 다른 모든 SELECT 작업이 차단됨.
- 따라서 웹 애플리케이션처럼 여러 클라이언트가 동시에 접근하는 환경에서는 성능이 저하될 수 있음.
- 해결방법: WAL 사용
- WAL(Write-Ahead Logging) 모드를 활성화하면 읽기/쓰기 동시 수행이 가능.
- WAL 모드를 사용하면 쓰기 작업이 wal 파일에 먼저 기록되며, 이를 통해 읽기 작업과 쓰기 작업을 동시에 수행할 수 있음.
- 읽기 작업 (SELECT)
- 여러 개의 읽기(SELECT) 작업은 동시에 실행 가능.
- 하지만 기본적으로 읽기 중에 쓰기(INSERT, UPDATE, DELETE)가 들어오면 충돌(lock) 발생.
- 쓰기 작업 (INSERT, UPDATE, DELETE)
- SQLite에서는 한 번에 하나의 쓰기 작업만 허용.
- 쓰기 작업이 진행 중이면 다른 쓰기 작업은 대기(blocking) 또는 실패(locked error)
- 읽기 + 쓰기 동시 작업
- 기본적으로 읽기 중에도 쓰기 작업이 불가능 (읽기 중에는 데이터베이스를 "SHARED LOCK" 상태로 유지).
- 쓰기 작업이 시작되면, 모든 읽기 작업이 차단됨.
동시 쓰기 성능이 낮음 (멀티 유저 환경 비효율적)
- SQLite는 하나의 프로세스만 동시에 쓰기 가능.
- 여러 사용자가 동시 접속하는 환경(예: 웹 애플리케이션)에서는 성능이 떨어짐.
- WAL(Write-Ahead Logging) 모드를 사용하면 동시 읽기는 가능하지만, 쓰기 동시성은 MySQL/PostgreSQL보다 낮음.
대규모 데이터베이스 지원 부족
- GB 단위 이상의 대용량 데이터에는 적합하지 않음.
- 데이터가 많아질수록 성능 저하 발생 가능.
고급 기능 부족
- Stored Procedure(저장 프로시저) 미지원.
- 권한 관리 기능이 없음 (사용자 계정 개념이 없음).
- 네트워크 액세스 기능 없음 (클라이언트-서버 모델이 아님).
비관적락, 낙관적락 지원 안함
비관적 락(Pessimistic Lock)이란?
- 트랜잭션이 특정 행(Row)을 수정하는 동안 다른 트랜잭션이 동일한 행을 읽거나 변경하지 못하도록 차단하는 방식.
- SELECT ... FOR UPDATE 같은 명령어로 구현됨.
❌ SQLite는 FOR UPDATE를 지원하지 않음.
❌ 비관적 락을 사용할 수 없기 때문에, 같은 데이터를 여러 트랜잭션이 동시에 수정하는 경우 데이터 충돌이 발생할 수 있음.
대체 방법: 트랜잭션을 활용한 Locking
SQLite에서는 비관적 락을 사용할 수 없으므로, 트랜잭션을 활용하여 동시성을 최소한으로 제어할 수 있음.
await this.repository.manager.transaction(async (entityManager) => {
const group = await entityManager.findOne(GroupDto, { where: { id: 1 } });
if (!group) throw new Error("Group not found");
// 데이터 업데이트 (다른 트랜잭션이 이 데이터를 변경하기 전에 수행)
group.name = "Updated Group";
await entityManager.save(group);
});
✅ 트랜잭션 내에서 데이터를 읽고 즉시 업데이트하면 어느 정도의 동시성 충돌을 줄일 수 있음.
❌ 하지만, 다른 트랜잭션이 동시에 실행될 경우 완전히 차단되지 않음(비관적 락과 동일한 효과를 내지 못함).
낙관적 락(Optimistic Lock)이란?
- 트랜잭션을 수행하는 동안 다른 트랜잭션이 데이터를 변경할 가능성을 허용하고, 최종적으로 충돌을 감지하는 방식.
- 버전 필드(version), 수정 시간(updatedAt)을 비교하여 충돌을 감지.
SQLite는 낙관적 락을 직접 지원하지 않지만, 타임스탬프 필드(updatedAt) 또는 버전 필드(version)를 활용하여 수동으로 구현 가능.
✅ updatedAt을 사용한 낙관적 락
async function updateGroup(groupId: number, newName: string) {
return await this.repository.manager.transaction(async (entityManager) => {
const group = await entityManager.findOne(GroupDto, {
where: { id: groupId },
});
if (!group) throw new Error("Group not found");
// 기존 데이터의 updatedAt 필드를 가져옴
const previousUpdatedAt = group.updatedAt;
// 업데이트 수행
const result = await entityManager.update(
GroupDto,
{ id: groupId, updatedAt: previousUpdatedAt }, // 이전 상태를 조건으로 포함
{ name: newName, updatedAt: new Date() }
);
if (result.affected === 0) {
throw new Error("Data conflict detected! Please retry.");
}
return result;
});
}
두 개의 트랜잭션이 같은 데이터를 수정하려고 하면, updatedAt 값이 변경된 경우 충돌을 감지할 수 있음.
❌ 하지만, MySQL의 ON UPDATE 또는 PostgreSQL의 version 필드처럼 자동화된 지원은 없음.
lock 경험
// 그룹 순서 변경
async changeGroupOrder(
groupDtoCurrent: GroupDto,
groupBefore: GroupDto | null,
groupNext: GroupDto | null,
): Promise<string> {
// 현재 그룹을 업데이트
await this.repository.save({
...groupDtoCurrent,
beforeGroupId: groupBefore?.id ?? null,
nextGroupId: groupNext?.id ?? null,
});
// 다음 그룹이 존재하면 업데이트
if (groupNext) {
await this.repository.save({
...groupNext,
beforeGroupId: groupDtoCurrent.id,
});
}
// 이전 그룹이 존재하면 업데이트
if (groupBefore) {
await this.repository.save({
...groupBefore,
nextGroupId: groupDtoCurrent.id,
});
}
return groupDtoCurrent.id;
}
문제점
- 트랜잭션을 걸지 않으면 데이터 정합성이 깨질 수 있음.
- 기본적으로 쓰기 작업이 하나만 가능 → save()를 여러 번 호출하면 database is locked 오류 발생 가능.
- 해결 방법: WAL 모드 사용 + 트랜잭션 적용.
TypeORM에서 트랜잭션 적용
데이터 정합성을 보장하려면 트랜잭션을 사용하여 save()를 하나의 원자적 작업으로 묶어야 함.
async changeGroupOrder(
groupDtoCurrent: GroupDto,
groupBefore: GroupDto | null,
groupNext: GroupDto | null,
): Promise<string> {
return await this.repository.manager.transaction(async (entityManager) => {
// 현재 그룹 업데이트
await entityManager.save(GroupDto, {
...groupDtoCurrent,
beforeGroupId: groupBefore?.id ?? null,
nextGroupId: groupNext?.id ?? null,
});
// 다음 그룹 업데이트
if (groupNext) {
await entityManager.save(GroupDto, {
...groupNext,
beforeGroupId: groupDtoCurrent.id,
});
}
// 이전 그룹 업데이트
if (groupBefore) {
await entityManager.save(GroupDto, {
...groupBefore,
nextGroupId: groupDtoCurrent.id,
});
}
return groupDtoCurrent.id;
});
}
WAL 모드의 장점과 단점
✅ WAL 모드의 장점
✔ 읽기/쓰기 동시 가능
SELECT(읽기) 중에도 INSERT, UPDATE, DELETE(쓰기)가 가능.
✔ 쓰기 속도 향상
데이터가 직접 데이터베이스 파일에 기록되지 않고, wal 로그 파일에 저장되므로 I/O 성능 향상.
✔ Crash Recovery 성능 향상
장애 발생 시 wal 파일을 이용하여 더 빠르게 복구 가능.
❌ WAL 모드의 단점
❌ 동시에 여러 개의 쓰기는 여전히 불가능
한 번에 하나의 쓰기 작업만 가능 (다른 DBMS처럼 다중 트랜잭션 쓰기 지원 X).
❌ DELETE, UPDATE가 많은 경우 성능 저하 가능
wal 파일이 일정 크기로 커지면 checkpoint를 실행하여 데이터 파일로 병합해야 함.
PRAGMA wal_checkpoint(TRUNCATE); 명령을 주기적으로 실행하여 최적화 필요.
SQLite vs MySQL vs PostgreSQL 비교
SQLite가 사용되는 분야
SQLite는 주로 소규모 데이터베이스에 적합하며, 다음과 같은 분야에서 많이 사용됩니다.
📌 1. 모바일 애플리케이션
- Android → Android 앱 개발 시 SQLite가 기본 내장됨.
- iOS → Core Data에서 SQLite를 백엔드 저장소로 활용.
📌 2. 웹 브라우저 및 로컬 스토리지
- Google Chrome, Firefox → 쿠키, 설정 정보 저장에 사용.
- Electron 앱 (VS Code 등) → SQLite를 데이터 저장소로 사용.
📌 3. 임베디드 시스템 및 IoT
- 스마트 TV, 셋톱박스, 자동차 내비게이션 → 경량 데이터 저장 용도로 활용.
📌 4. 테스트 및 프로토타이핑
- 대규모 DBMS를 사용하기 전에 간단한 데이터 모델 테스트 용도로 SQLite 사용.
SQLite의 WAL(Write-Ahead Logging) 모드
SQLite는 기본적으로 Rollback Journal 모드를 사용하지만, WAL 모드를 사용하면 동시 읽기 성능이 향상됨.
PRAGMA journal_mode=WAL;
WAL 모드 장점:
- 여러 개의 프로세스가 동시에 읽기 가능 (쓰기 성능은 여전히 낮음).
- 트랜잭션 속도 향상 (쓰기 성능이 약간 개선됨).
SQLite를 사용하면 좋은 경우 vs 사용하면 안 되는 경우
✅ SQLite를 사용하면 좋은 경우
✔️ 단순한 데이터 저장(모바일 앱, 설정 저장, 쿠키 등).
✔️ 읽기(Read) 작업이 많고 쓰기(Write) 작업이 적은 경우.
✔️ 서버 없이 간단한 DB가 필요한 경우.
✔️ 파일 하나로 데이터를 저장해야 하는 경우.
❌ SQLite를 사용하면 안 되는 경우
❌ 웹 애플리케이션의 메인 데이터베이스 (MySQL, PostgreSQL 사용 추천).
❌ 대량의 동시 쓰기 작업이 필요한 경우.
❌ 복잡한 권한 관리, 멀티 유저 기능이 필요한 경우.
'학습 기록 (Learning Logs) > CS Study' 카테고리의 다른 글
transaction (0) | 2025.02.03 |
---|---|
isolation level (0) | 2025.02.03 |
Tree (0) | 2025.02.03 |
WAL(Write-Ahead Logging) (0) | 2025.02.03 |
kafka (0) | 2025.01.23 |