면접 질문 목록으로 가기
면접 질문실전 질문꼬리 질문

안드로이드 로컬 데이터 영속성 (Local Data Persistence)

skydovesJaewoong Eum (skydoves)||8분 소요

안드로이드 로컬 데이터 영속성 (Local Data Persistence)

안드로이드는 로컬 데이터를 저장하고 영속적으로 유지하기 위한 다양한 메커니즘을 제공하며, 각 메커니즘은 서로 다른 데이터 형태와 접근 패턴에 맞게 설계되어 있습니다. 올바른 저장 방식을 선택하려면 해당 데이터가 단순한 키-값(key-value) 쌍인지, 구조화된 관계형 레코드인지, 혹은 원시 바이너리 파일인지에 따라 판단해야 합니다. SharedPreferences, DataStore, Room, 파일 저장소 간의 트레이드오프를 이해하는 것은 면접에서 자주 등장하는 주제이기도 합니다. 개발자가 안드로이드의 데이터 계층을 얼마나 깊이 이해하고 있는지를 파악하기에 좋은 질문이기 때문입니다.

이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.

  • SharedPreferences의 목적과 키-값 저장소로서의 한계를 설명할 수 있습니다.
  • Jetpack DataStore가 비동기, 코루틴 기반 API를 통해 SharedPreferences를 어떻게 개선하는지 이해할 수 있습니다.
  • Room이 구조화된 관계형 데이터에 적합한 선택인 경우를 파악할 수 있습니다.
  • 바이너리 또는 커스텀 데이터에 대해 내부 저장소와 외부 저장소의 차이를 구분할 수 있습니다.

SharedPreferences

SharedPreferences는 안드로이드에서 가장 오래된 키-값 저장 메커니즘입니다. Boolean, Int, String, Float과 같은 원시 타입(primitive type)을 디스크의 XML 파일에 저장하며, 앱을 재시작해도 데이터가 유지됩니다. 기본적으로 해당 애플리케이션 내에서만 접근할 수 있는 프라이빗 저장소입니다.

val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit {
    putString("user_name", "skydoves")
}
val name = prefs.getString("user_name", "")

SharedPreferences의 읽기 작업은 동기적으로 수행되며, 최초 접근 시 전체 파일을 메모리에 로드합니다. apply()를 통한 쓰기 작업은 비동기로 처리되지만, 아직 디스크에 기록되지 않은 쓰기 작업이 남아 있으면 Activity.onPause() 시점에 메인 스레드를 블로킹할 수 있습니다. commit() 메서드는 동기적으로 데이터를 기록하고 성공 여부를 Boolean으로 반환하는데, 쓰기가 완료될 때까지 호출 스레드를 블로킹합니다. 면접에서 이 차이를 명확히 설명하면 좋은 인상을 줄 수 있으므로, apply()commit()의 동작 방식 차이를 정확히 숙지하시길 권장합니다.

SharedPreferences는 타입 안전성, 스키마 진화(schema evolution), 구조화된 데이터를 지원하지 않습니다. 사용자 설정이나 피처 플래그(feature flag)처럼 소량의 설정 데이터에는 적합하지만, 데이터의 양이 많거나 구조가 복잡해지면 여러 가지 문제가 발생할 수 있습니다.

Jetpack DataStore

Jetpack DataStore는 SharedPreferences의 현대적인 대체 솔루션입니다. 두 가지 구현체를 제공하는데, 키-값 저장을 위한 Preferences DataStore와 Protocol Buffers를 활용한 구조화된 데이터 저장을 위한 Proto DataStore가 있습니다.

DataStore는 코틀린 코루틴과 Flow를 기반으로 구축되어, 모든 읽기와 쓰기가 기본적으로 비동기로 처리됩니다. 덕분에 SharedPreferences에서 발생할 수 있는 메인 스레드 블로킹 문제를 근본적으로 해결합니다.

val dataStore: DataStore<Preferences> =
    context.createDataStore(name = "settings")

val userNameKey = stringPreferencesKey("user_name")
// suspend 함수이므로 코루틴 내에서 호출해야 합니다
suspend fun saveUserName(name: String) {
    dataStore.edit { settings ->
        settings[userNameKey] = name
    }
}

// Flow를 통해 데이터 변경을 반응적으로 관찰할 수 있습니다
val userNameFlow: Flow<String> = dataStore.data.map { prefs ->
    prefs[userNameKey] ?: ""
}

이 면접 질문은 구독자 전용입니다

Dove Letter를 구독하시면 안드로이드, 코틀린 개발 관련 독점 면접 질문의 전체 내용을 볼 수 있습니다.

구독하기
면접 질문 목록으로 가기