ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 플러터 시작하기 - 7. 로컬 캐싱
    카테고리 없음 2025. 4. 29. 13:33
    반응형

    원격 서버로부터 데이터를 가져올 수 있다고 해서 항상 그렇게 해야 하는 것은 아닙니다. 때때로, 이전 네트워크 요청에서 받은 데이터를 다시 재활용하는 것이 더 나을 때가 있습니다. 그러면 사용자는 네트워크로 다시 데이터를 가져오는 긴 시간을 기다리지 않아도 됩니다. 이렇게 애플리케이션 데이터를 보존해 두었다가 나중에 다시 보여주는 기술을 캐싱(caching)이라고 하며, 이 페이지에서는 Flutter 앱에서 이 작업을 수행하는 방법을 다룹니다.


    캐싱 소개

    가장 기본적으로 모든 캐싱 전략은 다음과 같은 3단계 작업으로 요약할 수 있습니다. 아래는 이를 나타낸 의사 코드(pseudocode)입니다:

    Data? _cachedData;
    
    Future<Data> get data async {
        // 1단계: 캐시에 원하는 데이터가 있는지 확인
        if (_cachedData == null) {
            // 2단계: 캐시가 비어있으면 데이터를 로드
            _cachedData = await _readData();
        }
        // 3단계: 캐시에 저장된 값을 반환
        return _cachedData!;
    }
    

     

    이 기본 전략은 캐시의 위치, 캐시를 사전 로딩하거나(warming) 값들을 미리 저장하는 정도 등을 원하는 방식으로 사용할 수 있습니다.


    일반적인 캐싱 용어

    캐싱에는 고유의 용어가 있으며, 그중 일부를 아래에 정의하고 설명합니다.

    • 캐시 히트(Cache hit): 앱이 원하는 정보가 이미 캐시에 있을 때를 캐시 히트라고 합니다. 이 경우, 실제 데이터 원본에서 데이터를 다시 가져올 필요가 없습니다.
    • 캐시 미스(Cache miss): 앱이 원하는 데이터가 캐시에 없어서 실제 데이터 원본으로부터 데이터를 가져오고, 이후 이를 캐시에 저장하는 경우를 캐시 미스라고 합니다.

    캐싱 데이터의 위험성

    캐시가 오래된 경우(stale cache), 즉 실제 데이터 원본이 변경되었는데 앱이 이를 반영하지 못한 경우를 의미하며, 앱이 오래되고 부정확한 정보를 렌더링 할 위험이 생깁니다.

     

    모든 캐싱 전략은 오래된 데이터를 보유할 위험을 내포합니다. 문제는, 캐시의 최신성을 검증하는 작업이 종종 데이터를 완전히 다시 로드하는 것만큼 시간이 걸린다는 점입니다. 그래서 대부분의 앱은 런타임 중 데이터가 신선하다는 것을 신뢰할 수 있을 때에만 캐싱의 이점을 얻습니다.

     

    이를 처리하기 위해 대부분의 캐싱 시스템은 개별 캐시 데이터에 시간 제한(time limit)을 둡니다. 이 시간이 초과되면, 원래는 캐시 히트가 될 수 있었던 요청도 캐시 미스로 간주되어 새 데이터를 다시 로드합니다.

     

    컴퓨터 과학자들 사이에서 유명한 농담이 있습니다:
    "컴퓨터 과학에서 가장 어려운 세 가지는 캐시 무효화(Cache invalidation), 이름 짓기(Naming things), 그리고 오프-바이-원 에러(Off-by-one errors)이다." 😄

     

    위험성에도 불구하고, 세상 거의 모든 앱은 데이터를 적극적으로 캐싱합니다. 이 페이지의 나머지 부분에서는 Flutter 앱에서 데이터를 캐싱하는 여러 접근 방법을 소개합니다. 모든 방법은 상황에 맞게 수정하거나 조합할 수 있습니다.


    메모리에 데이터 캐싱하기

    가장 간단하고 성능이 좋은 캐싱 전략은 인-메모리(in-memory) 캐시입니다.


    단점은, 캐시가 시스템 메모리에만 저장되기 때문에 세션이 끝나면 데이터도 사라진다는 것입니다. (물론, 이는 오래된 캐시 문제를 자연스럽게 해결하는 장점이 되기도 합니다!) 이러한 단순성 덕분에 인-메모리 캐시는 위에서 본 의사 코드와 매우 비슷하게 작동합니다.

     

    하지만, 레포지토리 패턴(Repository pattern) 같은 검증된 설계 원칙을 사용해 코드 구조를 잘 조직하는 것이 중요합니다. 코드 곳곳에 캐시 체크 코드가 흩어지는 것을 방지할 수 있습니다.

     

    예를 들어, 사용자를 메모리에 캐싱해 중복 네트워크 요청을 방지하는 UserRepository 클래스를 생각해 볼 수 있습니다. 구현은 다음과 같을 수 있습니다:

    class UserRepository {
      UserRepository(this.api);
      
      final Api api;
      final Map<int, User?> _userCache = {};
    
      Future<User?> loadUser(int id) async {
        if (!_userCache.containsKey(id)) {
          final response = await api.get(id);
          if (response.statusCode == 200) {
            _userCache[id] = User.fromJson(response.body);
          } else {
            _userCache[id] = null;
          }
        }
        return _userCache[id];
      }
    }
    

     

    이 UserRepository는 여러 검증된 설계 원칙을 따릅니다:

    • 의존성 주입(Dependency Injection): 테스트가 쉬워집니다.
    • 느슨한 결합(Loose coupling): 주변 코드가 구현 세부사항에 의존하지 않게 보호합니다.
    • 관심사의 분리(Separation of concerns): 구현이 너무 많은 책임을 가지지 않도록 합니다.

    그리고 가장 좋은 점은, 세션 내에서 사용자가 여러 번 같은 사용자를 불러와도 네트워크 요청은 단 한 번만 발생한다는 것입니다.

    하지만, 사용자는 앱을 다시 시작할 때마다 데이터를 기다려야 하는 것에 eventually(결국) 지칠 수도 있습니다. 이를 해결하기 위해, 다음 섹션에서 소개할 지속성 캐시(Persistent cache) 전략을 사용할 수 있습니다.


    지속성 캐시

    메모리에 데이터를 캐싱하는 방식은 계속 데이터를 유지하지 못합니다. 앱을 새로 시작했을 때도 캐시 히트의 이점을 누리려면, 데이터를 디바이스의 하드 드라이브에 저장해야 합니다.

     

    shared_preferences로 데이터 캐싱하기

    shared_preferences는 Flutter 플러그인으로, Flutter가 지원하는 여섯 플랫폼(Android, iOS, 웹, Windows, macOS, Linux) 각각에서 플랫폼별 키-값 저장소를 감쌉니다. 이러한 키-값 저장소는 소량의 데이터 저장을 위해 설계되었지만, 대부분의 애플리케이션에 적합한 캐싱 전략을 제공합니다.


    자세한 가이드는 다음 리소스를 참고하세요:

     

    파일 시스템으로 데이터 캐싱하기

    만약 여러분의 앱이 shared_preferences의 저속 처리량을 초과하는 시나리오에 도달했다면, 디바이스의 파일 시스템을 사용해 데이터 캐싱을 고려할 때입니다. 자세한 가이드는 다음을 참고하세요:

     

    디바이스 내 데이터베이스로 캐싱하기

    로컬 데이터 캐싱의 최종 보스는 데이터베이스를 사용하는 전략입니다. 관계형(Relational) 데이터베이스와 비관계형(Non-relational) 데이터베이스를 포함해 다양한 종류가 있으며, 특히 대규모 데이터셋에서 단순 파일보다 훨씬 더 좋은 성능을 제공합니다.

    자세한 가이드는 다음을 참고하세요:


    이미지 캐싱

    이미지 캐싱은 일반 데이터 캐싱과 비슷하지만, 하나의 표준 솔루션이 존재합니다.
    Flutter 앱이 파일 시스템을 사용해 이미지를 저장하도록 하려면 cached_network_image 패키지를 사용하세요.


    상태 복원

    애플리케이션 데이터 외에도, 사용자 세션의 다른 측면들 — 예를 들어, 내비게이션 스택, 스크롤 위치, 양식 작성 진행 상황 등 — 도 저장하고 싶을 수 있습니다. 이를 상태 복원(State restoration) 이라고 하며, Flutter에 기본 내장되어 있습니다. Flutter 프레임워크는 Element 트리의 데이터를 Flutter 엔진과 동기화한 후, 플랫폼별 저장소에 캐시 합니다. 이를 통해 이후 세션에서 복원이 가능합니다.

     

    Android 및 iOS에서 상태 복원을 활성화하는 방법은 다음 문서를 참고하세요:


    목차

    플러터 시작하기 - 0. 개요

    플러터 시작하기 - 1. 다트 소개

    플러터 시작하기 - 2. 위젯

    플러터 시작하기 - 3. 레이아웃

    플러터 시작하기 - 4. 상태 관리

    플러터 시작하기 - 5. 사용자 입력 처리

    플러터 시작하기 - 6. 네트워킹과 데이터

    플러터 시작하기 - 7. 로컬 캐싱

    반응형
Designed by Tistory.