안드로이드에 기본 탑재된 경량화된 관계형 데이터베이스.
SQLite를 객체지향적으로 추상화한 ORM
반드시 싱글톤으로 생성해야 함 (비용 큼)
Coroutine & Flow 연동
suspend fun → 하나의 insert/delete 등 단일 작업 처리Flow<T> → 테이블 값이 변할 때마다 자동 업데이트 받음 (리액티브)Room은 기본 타입(Int, String 등) 외에는 저장 못 해.
예를 들어 List 저장하려면 TypeConverter가 필요해.
DB 스키마가 바뀌면 version을 올리고 Migration을 정의해야 해.
컴파일 타임에 Room이 Annotation Processor (KAPT/KSP)로 DAO, Entity 분석
Room은 @Entity, @Dao, @Database 등의 어노테이션을 기반으로 Annotation Processing을 수행
Room은 실제로 SQLite를 직접 호출하는 자바 클래스들을 컴파일 타임에 생성해.
SQLite 쿼리 검증 및 DAO 구현체 생성 (코드 자동 생성)
우리가 UserDao 인터페이스만 작성해도, Room이 내부에서 SQL 실행 코드를 만들어주는 것이야.
AppDatabase를 통해 SQLiteOpenHelper 기반으로 SQLite DB 생성
onCreate()에서 SQL 실행해 테이블 생성onUpgrade() 또는 Migration이 호출됨DAO 메서드 호출 시 자동으로 생성된 코드가 SQL 실행
이제 우리가 앱에서 userDao.getUserById(1)을 호출하면:
UserDao_Impl의 구현 코드가 실행됨SQLiteDatabase.query() 호출됨Cursor 형태로 반환됨UserEntity 객체로 변환해 반환Room이 이 모든 과정을 대신 처리해주기 때문에, 우리는 단지 추상 메서드를 선언하고 사용할 수 있는 것이야.
Flow/LiveData 사용 시, 변화 감지 트리거 및 자동 재조회
Room은 내부적으로 DB 변경을 감지할 수 있는 Observer를 등록해 둬.
데이터가 바뀌면 쿼리를 다시 실행해서 Flow로 emit 해 줌.
이건 SQLite의 Invalidation Tracker를 활용해 테이블의 변경을 감지하는 구조야.
| 단계 | 설명 |
|---|---|
| 컴파일 타임 | 어노테이션 분석 → SQL 검증 → DAO 구현 클래스 자동 생성 |
| 런타임 | RoomDatabase 초기화 → SQLite DB 생성/열기 |
| DAO 호출 | 자동 생성된 코드가 SQLite 쿼리 실행 |
| 결과 반환 | Cursor → Entity 변환 → suspend/Flow로 리턴 |
| Flow 감지 | InvalidationTracker가 테이블 변경 감지 → Flow emit |
작은 크기의 데이터를 안전하고 비동기적으로 저장하기 위한 라이브러리. Coroutine + Flow 기반이며, 타입 안정성 + 트랜잭션 처리 + 에러 핸들링까지 신경쓴 구조.
SharedPreferences는 apply()나 commit() 등을 통해 데이터를 저장할 때 메인 스레드에서도 동작합니다.
그래서 잘못 사용하면 ANR(Application Not Responding) 이 발생할 수 있어요.
반면 DataStore는 Kotlin Coroutine 기반으로 설계되어, 내부 작업이 모두 비동기적으로 수행됩니다.
그래서 메인 스레드를 막지 않으면서도 안전하게 데이터 저장이 가능해요.
SharedPreferences에서 저장이 실패하더라도, 개발자가 이를 감지하고 대응하기 어렵습니다.
commit()의 반환값으로 실패 여부를 알 수 있지만, 거의 신경 쓰지 않거나 놓치는 경우가 많죠.
반면 DataStore는 IOException 등의 에러를 Flow에서 명확하게 캐치할 수 있기 때문에,
실패한 경우의 처리(재시도, 사용자 알림 등)가 훨씬 정교하게 가능합니다.
특히 apply()를 사용하면 비동기 저장이긴 하지만, 앱이 강제 종료되면 디스크에 쓰기 전에 데이터가 유실될 수 있어요.
DataStore는 내부적으로 AtomicFile 구조를 사용하고, 트랜잭션 방식으로 파일을 안전하게 쓰기 때문에 데이터 유실 가능성이 현저히 줄어듭니다.
예를 들어 SharedPreferences에서 값을 실시간으로 반영하고 싶다면,
OnSharedPreferenceChangeListener를 등록해서 콜백으로 처리해야 했어요.
→ 코드가 장황하고 유지보수가 어렵습니다.
하지만 DataStore는 Flow를 기반으로 데이터를 구독할 수 있어서,
collect {} 구문만으로 데이터 변화를 자동으로 감지하고 반영할 수 있습니다.
→ 리액티브 프로그래밍에 적합한 구조입니다.
예를 들어 이름, 나이, 이메일 같은 여러 속성을 하나로 저장하려면
SharedPreferences에서는 각각의 key로 나눠 저장해야 했어요.
하지만 Proto DataStore는 Protocol Buffer 기반 구조화된 데이터 객체를 저장할 수 있어서,
마치 Room처럼 타입 안정성과 구조적 데이터 저장이 가능해요.
→ 클린 아키텍처 및 상태 기반 설계와도 잘 맞습니다.
CoroutineScope, Flow, Disk IO Dispatcher를 사용AtomicFile로 안전하게 저장 (부분 저장, 충돌 방지)edit 블록은 트랜잭션 보장data는 cold Flow, 즉 수집 시에만 읽음| 종류 | 확장자 | 형식 |
|---|---|---|
| Preferences DataStore | .preferences_pb | Protocol Buffer 기반, 내부적으로 key-value 구조 |
| Proto DataStore | .pb | 완전한 .proto 스키마 기반 구조화 데이터 |
둘 다 Protocol Buffer 기반의 바이너리 포맷이라 일반 텍스트처럼 쉽게 볼 수 없고,
직접 수동으로 읽거나 조작하기 어렵습니다. 대신 속도와 안정성 면에서 매우 효율적입니다.
DataStore는 SQLite가 아닌 직접 파일 시스템에 Protocol Buffer 형식으로 저장합니다.
| 항목 | DataStore | Room (SQLite 기반) |
|---|---|---|
| 저장 방식 | 파일에 직렬화 (Protocol Buffer) | 관계형 데이터베이스 (SQL 테이블) |
| 구조 | 단순 key-value / Proto 모델 | 테이블 + 쿼리 + Index |
| I/O 처리 | 순차적 파일 I/O | SQL 파싱 + 엔진 처리 + Cursor |
| 쓰기 속도 | 매우 빠름 (작은 단일 파일 갱신) | 상대적으로 느릴 수 있음 (INSERT, UPDATE 등 처리) |
| 읽기 속도 | 빠름 (전체 파일 읽고 메모리 캐싱됨) | 쿼리에 따라 성능 달라짐 |
| 동시성 | 하나의 트랜잭션으로 동작 → 충돌 없음 | 쿼리 동시 처리 가능하나, 잠금 발생 가능 |
| Flow 연동 | 기본 지원 | Flow, LiveData 변환 필요 |
| 복잡한 쿼리/정렬 | ❌ 불가 | ✅ 지원 |
| 대용량 데이터 | ❌ 적합하지 않음 | ✅ 최적화되어 있음 |
DataStore는 작고 단순한 데이터를 안전하게 저장하는 데 목적이 있어요.DataStore는 데이터를 모두 한 파일에 직렬화하여 저장하기 때문에,
항목 수가 많아지면 쓰기/읽기 비용이 기하급수적으로 늘어납니다.
그래서 수십 개 이상 엔트리를 저장하거나, 동시 쓰기 작업이 많은 경우에는 Room이 더 안정적이에요.