Android Database, Room, DataStore

Pardess·2025년 4월 24일

SQLite

안드로이드에 기본 탑재된 경량화된 관계형 데이터베이스.

  • SQL 문법을 사용하여 데이터를 조작
  • SQLite는 안드로이드에 내장된 로컬 RDBMS이며, Room은 이를 추상화한 Jetpack 라이브러리야
  • 직접 SQL 작성이 필요하고, 모든 동작을 명시적으로 처리해야 함

Room

SQLite를 객체지향적으로 추상화한 ORM

Entity : 테이블 정의

DAO: 데이터 액세스 객체

Database: Room 데이터베이스 정의

DB 초기화 (싱글톤 패턴)

반드시 싱글톤으로 생성해야 함 (비용 큼)

Coroutine & Flow 연동

  • suspend fun → 하나의 insert/delete 등 단일 작업 처리
  • Flow<T> → 테이블 값이 변할 때마다 자동 업데이트 받음 (리액티브)

TypeConverter

Room은 기본 타입(Int, String 등) 외에는 저장 못 해.

예를 들어 List 저장하려면 TypeConverter가 필요해.

Migration

DB 스키마가 바뀌면 version을 올리고 Migration을 정의해야 해.

Room 작동 원리 요약 흐름

  1. 컴파일 타임에 Room이 Annotation Processor (KAPT/KSP)로 DAO, Entity 분석
    Room은 @Entity, @Dao, @Database 등의 어노테이션을 기반으로 Annotation Processing을 수행
    Room은 실제로 SQLite를 직접 호출하는 자바 클래스들을 컴파일 타임에 생성해.

  2. SQLite 쿼리 검증 및 DAO 구현체 생성 (코드 자동 생성)
    우리가 UserDao 인터페이스만 작성해도, Room이 내부에서 SQL 실행 코드를 만들어주는 것이야.

  3. AppDatabase를 통해 SQLiteOpenHelper 기반으로 SQLite DB 생성

    • DB 파일이 없으면 onCreate()에서 SQL 실행해 테이블 생성
    • DB 버전이 변경되면 onUpgrade() 또는 Migration이 호출됨
  4. DAO 메서드 호출 시 자동으로 생성된 코드가 SQL 실행

    이제 우리가 앱에서 userDao.getUserById(1)을 호출하면:

    1. UserDao_Impl의 구현 코드가 실행됨
    2. SQLiteDatabase.query() 호출됨
    3. 결과가 Cursor 형태로 반환됨
    4. Cursor 데이터를 UserEntity 객체로 변환해 반환

    Room이 이 모든 과정을 대신 처리해주기 때문에, 우리는 단지 추상 메서드를 선언하고 사용할 수 있는 것이야.

  5. Flow/LiveData 사용 시, 변화 감지 트리거 및 자동 재조회

    Room은 내부적으로 DB 변경을 감지할 수 있는 Observer를 등록해 둬.

    데이터가 바뀌면 쿼리를 다시 실행해서 Flow로 emit 해 줌.

    이건 SQLite의 Invalidation Tracker를 활용해 테이블의 변경을 감지하는 구조야.

결론: Room 작동 원리 핵심 요약

단계설명
컴파일 타임어노테이션 분석 → SQL 검증 → DAO 구현 클래스 자동 생성
런타임RoomDatabase 초기화 → SQLite DB 생성/열기
DAO 호출자동 생성된 코드가 SQLite 쿼리 실행
결과 반환Cursor → Entity 변환 → suspend/Flow로 리턴
Flow 감지InvalidationTracker가 테이블 변경 감지 → Flow emit

DataStore

작은 크기의 데이터를 안전하고 비동기적으로 저장하기 위한 라이브러리. Coroutine + Flow 기반이며, 타입 안정성 + 트랜잭션 처리 + 에러 핸들링까지 신경쓴 구조.

종류

Preferences DataStore: 키-값 기반(String, Int, Boolean 등) 저장 방식 (SharedPreferences 대체)

Proto DataStore: 정형화된 데이터 모델 저장 (Google Protocol Buffers 사용) - 타입 안정성, 구조화

장점

1. SharedPreferences는 동기식이고, DataStore는 비동기식

SharedPreferencesapply()commit() 등을 통해 데이터를 저장할 때 메인 스레드에서도 동작합니다.

그래서 잘못 사용하면 ANR(Application Not Responding) 이 발생할 수 있어요.

반면 DataStoreKotlin Coroutine 기반으로 설계되어, 내부 작업이 모두 비동기적으로 수행됩니다.

그래서 메인 스레드를 막지 않으면서도 안전하게 데이터 저장이 가능해요.

2. SharedPreferences는 저장 실패 시 예외를 던지지 않지만, DataStore는 예외 처리가 가능하다

SharedPreferences에서 저장이 실패하더라도, 개발자가 이를 감지하고 대응하기 어렵습니다.

commit()의 반환값으로 실패 여부를 알 수 있지만, 거의 신경 쓰지 않거나 놓치는 경우가 많죠.

반면 DataStore는 IOException 등의 에러를 Flow에서 명확하게 캐치할 수 있기 때문에,

실패한 경우의 처리(재시도, 사용자 알림 등)가 훨씬 정교하게 가능합니다.

3. SharedPreferences는 데이터 손실 가능성이 있다

특히 apply()를 사용하면 비동기 저장이긴 하지만, 앱이 강제 종료되면 디스크에 쓰기 전에 데이터가 유실될 수 있어요.

DataStore는 내부적으로 AtomicFile 구조를 사용하고, 트랜잭션 방식으로 파일을 안전하게 쓰기 때문에 데이터 유실 가능성이 현저히 줄어듭니다.

4. SharedPreferences는 콜백 기반으로 데이터 변화를 감지해야 해서 코드가 복잡하다

예를 들어 SharedPreferences에서 값을 실시간으로 반영하고 싶다면,

OnSharedPreferenceChangeListener를 등록해서 콜백으로 처리해야 했어요.

코드가 장황하고 유지보수가 어렵습니다.

하지만 DataStore는 Flow를 기반으로 데이터를 구독할 수 있어서,

collect {} 구문만으로 데이터 변화를 자동으로 감지하고 반영할 수 있습니다.

리액티브 프로그래밍에 적합한 구조입니다.

5. SharedPreferences는 단순한 key-value만 저장 가능하고, 구조화된 데이터는 힘들다

예를 들어 이름, 나이, 이메일 같은 여러 속성을 하나로 저장하려면

SharedPreferences에서는 각각의 key로 나눠 저장해야 했어요.

하지만 Proto DataStoreProtocol Buffer 기반 구조화된 데이터 객체를 저장할 수 있어서,

마치 Room처럼 타입 안정성과 구조적 데이터 저장이 가능해요.

클린 아키텍처 및 상태 기반 설계와도 잘 맞습니다.

DataStore의 내부 구조

  • 내부적으로 CoroutineScope, Flow, Disk IO Dispatcher를 사용
  • 데이터는 AtomicFile로 안전하게 저장 (부분 저장, 충돌 방지)
  • edit 블록은 트랜잭션 보장
  • datacold Flow, 즉 수집 시에만 읽음

파일 포맷은?

종류확장자형식
Preferences DataStore.preferences_pbProtocol Buffer 기반, 내부적으로 key-value 구조
Proto DataStore.pb완전한 .proto 스키마 기반 구조화 데이터

둘 다 Protocol Buffer 기반의 바이너리 포맷이라 일반 텍스트처럼 쉽게 볼 수 없고,

직접 수동으로 읽거나 조작하기 어렵습니다. 대신 속도와 안정성 면에서 매우 효율적입니다.

DataStore는 SQLite가 아닌 직접 파일 시스템에 Protocol Buffer 형식으로 저장합니다.

항목DataStoreRoom (SQLite 기반)
저장 방식파일에 직렬화 (Protocol Buffer)관계형 데이터베이스 (SQL 테이블)
구조단순 key-value / Proto 모델테이블 + 쿼리 + Index
I/O 처리순차적 파일 I/OSQL 파싱 + 엔진 처리 + Cursor
쓰기 속도매우 빠름 (작은 단일 파일 갱신)상대적으로 느릴 수 있음 (INSERT, UPDATE 등 처리)
읽기 속도빠름 (전체 파일 읽고 메모리 캐싱됨)쿼리에 따라 성능 달라짐
동시성하나의 트랜잭션으로 동작 → 충돌 없음쿼리 동시 처리 가능하나, 잠금 발생 가능
Flow 연동기본 지원Flow, LiveData 변환 필요
복잡한 쿼리/정렬❌ 불가✅ 지원
대용량 데이터❌ 적합하지 않음✅ 최적화되어 있음

왜 SQLite를 안 쓸까?

  • DataStore작고 단순한 데이터를 안전하게 저장하는 데 목적이 있어요.
  • SQLite는 무겁고 복잡한 기능이 많기 때문에, 설정값 저장용으로는 오버스펙입니다.
  • 그래서 DataStore는 SQLite 없이도 빠르고 안전한 저장이 가능하도록 설계되었고, 내부적으로는 AtomicFile + Coroutine + Snapshot 시스템으로 동작합니다.

DataStore는 왜 빠를까?

  • 전체 데이터를 한 번에 읽어서 메모리에 캐싱하고, 값 변경 시도마다 트랜잭션으로 파일 전체 갱신합니다.
  • 구조가 단순하고 경량화되어 있어서, 작은 데이터에서는 빠른 속도를 보입니다.

하지만 주의할 점

DataStore는 데이터를 모두 한 파일에 직렬화하여 저장하기 때문에,

항목 수가 많아지면 쓰기/읽기 비용이 기하급수적으로 늘어납니다.

그래서 수십 개 이상 엔트리를 저장하거나, 동시 쓰기 작업이 많은 경우에는 Room이 더 안정적이에요.

0개의 댓글