OkHttp - Cache

HEETAE HEO·2023년 4월 2일
0
post-thumbnail

Cache (캐시)

OkHttp에서의 캐시란 API 호출의 응답을 로컬 저장소에 저장하여 이후 동일한 요청이 발생할 때 서버에서 데이터를 가져오지 않고 로컬 저장소에서 불러오게 하여 성능을 향상시키는 방법입니다. 이렇게 하면 네트워크 대역폭 사용이 줄어들고 응답 시간이 단축되며, 서버 부하가 감소합니다.

언제 사용하면 좋을까??

캐시를 사용하기 적합한 상황은 다음과 같습니다.

  1. 동일한 데이터를 반복적으로 요청하는 경우
  2. 변경되지 않거나 자주 변경되지 않는 데이터를 요청하는 경우
  3. 네트워크 연결이 불안정하거나 제한된 대역폭이 있는 경우
  4. 서버 부하를 최소화하고자 하는 경우

간단 Cache(캐시) 사용방법

  • 캐시 디렉터리 및 캐시 크기 설정 : 캐시를 저장할 디렉터리르 설정하고 캐시의 최대 크기를 지정합니다.
val cacheSize = 10 * 1024 * 1024 // 10 MB
val cacheDirectory = File(context.cacheDir, "cache")
val cache = Cache(cacheDirectory, cacheSize.toLong())
  • OkHttpClient에 캐시 설정 : 앞에서 생성한 캐시 인스턴스를 OkHttpClient의 캐시로 설정합니다.
val okHttpClient = OkHttpClient.Builder()
    .cache(cache)
    .build()

서버에서의 추가적인 처리

  • 서버 응답에 캐시 헤더 추가(선택 사항): 서버 측에서 캐시 관련 헤더를 추가하여 클라이언트 측 캐시 동작을 제어할 수 있습니다. 일반적으로 사용되는 캐시 관련 헤더는 다음과 같습니다.

Cache-Control : 캐시 제어을 제어하는 지시문들을 포함합니다.

  • ex) max-age, no-store, no-cache, private, public 등이 있습니다.

ETag : 응답 데이터의 버전을 나타내는 고유한 식별자입니다. 데이터가 변경되면 ETag 값이 변경되어 클라이언트가 캐시된 데이터를 사용할지 서버에서 새로운 데이터를 가져올지를 결정하는 데 도움이 됩니다.

Last-Modified : 리소스가 마지막으로 수정된 날짜를 나타냅니다. 클라이언트가 캐시된 데이터를 사용할지 서버에서 새로운 데이터를 가져올지를 결정하는 데 도움이 됩니다.

서버에서 이러한 헤더를 적절하게 설정하면 클라이언트 측에서 캐시 동작을 더 잘 제어할 수 있습니다. 위에 적은 내용의 예시 코드를 본다면

Cache-Control: max-age=3600
ETag: "some_unique_etag_value"
Last-Modified: Wed, 15 April 2023 07:28:00 GMT

이렇게 설정되면 클라이언트는 응답을 최대 1시간 동안 캐시하고, 1시간이 지난 후에는 서버에서 새로운 데이터를 가져오게 됩니다.

캐시 사용 확인: 애플리케이션을 실행하고 동일한 요청을 여러 번 수행하여 캐시가 올바르게 작동하는지 확인합니다. 디버깅을 통해 네트워크 호출이 캐시에서 가져오는 경우와 서버에서 새로 가져오는 경우를 확인할 수 있습니다.
이제 OkHttpClient에서 캐시 기능을 사용하도록 설정했습니다. 이렇게 설정하면 OkHttpClient를 사용하는 모든 네트워크 요청에 캐시가 적용됩니다. 캐시 동작을 더 세밀하게 제어하려면 요청별로 캐시 정책을 설정할 수도 있습니다.

참고로, 서버 측에서 캐시 관련 헤더를 설정하지 않거나 적절한 값을 제공하지 않으면 캐시 동작이 예상대로 작동하지 않을 수 있습니다. 이 경우 클라이언트 측에서 캐시 정책을 설정하여 캐시 동작을 강제할 수 있지만, 이는 서버와 클라이언트 간의 데이터 일관성 문제가 발생할 수 있으므로 주의해야 합니다.

더 효율적인 캐시 사용법들

  1. 스토리지 파티셔닝: 서로 다른 종류의 캐시 데이터를 분리하여 저장함으로써 캐시 관리를 개선할 수 있습니다. 예를 들어 이미지, CSS, JavaScript 등의 정적 리소스와 동적으로 생성된 API 응답을 별도의 캐시 저장소에 저장할 수 있습니다.
  1. 캐시 키 생성: 사용자 정의 캐시 키를 사용하여 캐시를 더 세밀하게 제어할 수 있습니다. 캐시 키를 통해 특정 사용자, 기기, 지역 등에 따라 다른 캐시 항목을 제공할 수 있습니다.

  2. 스마트 캐시 무효화: 데이터 변경 시 자동으로 관련 캐시 항목을 무효화하는 기능을 구현할 수 있습니다. 이를 통해 항상 최신 데이터를 제공하면서 캐시 효율을 높일 수 있습니다.

  3. 캐시 온-디맨드: 특정 요청에 대해 캐시를 명시적으로 사용하거나 사용하지 않도록 설정할 수 있습니다. 이를 통해 캐시 정책을 더 유연하게 적용할 수 있습니다.

  4. 캐시 디버깅 및 분석: 캐시 성능을 모니터링하고 분석하는 기능을 사용하면 캐시 최적화를 더욱 효과적으로 수행할 수 있습니다. 이를 통해 캐시 히트율, 캐시사이즈 등의 지표를 관리하고 캐시 정책을 개선할 수 있습니다.

  • 캐시 온-디맨드 예시 :
import android.util.LruCache

data class CacheKey(val userId: String, val deviceId: String, val resource: String)

class AdvancedCache(private val cacheSize: Int) {
    private val cache = object : LruCache<CacheKey, ByteArray>(cacheSize) {
        override fun sizeOf(key: CacheKey, value: ByteArray): Int {
            return value.size
        }
    }

    fun get(userId: String, deviceId: String, resource: String, useCache: Boolean): ByteArray? {
        if (!useCache) return null

        val cacheKey = CacheKey(userId, deviceId, resource)
        return cache.get(cacheKey)
    }

    fun put(userId: String, deviceId: String, resource: String, data: ByteArray) {
        val cacheKey = CacheKey(userId, deviceId, resource)
        cache.put(cacheKey, data)
    }

    fun remove(userId: String, deviceId: String, resource: String) {
        val cacheKey = CacheKey(userId, deviceId, resource)
        cache.remove(cacheKey)
    }
}

위 코드에서 AdvancedCache 라는 클래스를 정의하고, 캐시 키를 생성하기 위해 CacheKey 데이터 클래스를 사용합니다. 이 클래스는 userId, deviceId, 그리고 resource를 포함하며, 이 정보를 통해 사용자, 기기, 리소스별로 캐시를 구분할 수 있습니다.

get 메서드에서는 캐시 키를 생성한 후 온-디맨드 캐시를 구현하기 위해 useCache 인자를 사용하여 캐시 사용 여부를 결정합니다. 캐시 사용이 활성화된 경우에만 캐시에서 데이터를 가져옵니다.

put 메서드에서는 캐시 키를 생성하고 해당 키와 데이터를 캐시에 저장하는 역할을 합니다.

remove 메서드는 캐시 키를 생성한 후, 해당 키와 관련된 캐시 항목을 제거합니다.

이렇게 이번 글에서는 Cache(캐시)를 사용해보는 글을 작성해보았습니다. 읽어주셔서 감사합니다!!!

profile
Android 개발 잘하고 싶어요!!!

0개의 댓글