히스토리 목록 LRU 캐시 적용

유재민·2023년 5월 8일
0

최근 검색어. 검색창에서 검색하면 가장 최근에 검색한 키워드가 히스토리 목록 상단에 노출된다.

요구사항에는 구체적으로 몇 개까지 표시되는지 나와있지 않지만 제한이 없다면 성능적으로 문제가 있을 것이다.

그래서 제한을 두기로 결정하고 실제 네이버 검색창의 최근 검색어 개수는 몇 개인지 확인해봤다.

20개이다. 그리고 한 가지 조건이 더 있었다.

20개를 초과하는 경우, 가장 오래된 검색어부터 삭제됩니다.

가장 오래된 검색어를 삭제하는 페이지 교체 기법은 LRU 알고리즘이다. 그래서 해당 알고리즘을 이용한 LRU 캐시를 적용하기로 했다.


Foreign Key 설정

LRU 캐시를 적용하기 전에! 20개가 초과하여 가장 오래된 검색어가 삭제되는 경우를 생각해보았다. 히스토리 테이블의 검색 키워드는 각 카테고리 테이블의 history_keyword 와 연결되어있다. 검색어를 클릭 했을 때 로컬 DB에 접근하여 해당 검색어와 일치하는 카테고리 테이블의 데이터를 가져오게 되는데, 만약 히스토리 목록에서 검색어가 삭제된다면 로컬DB에서 데이터를 가지고 있을 필요가 없다. 그래서 히스토리 테이블의 검색 키워드가 삭제된다면 각 카테고리 테이블의 history_keyword 와 일치하는 모든 데이터도 삭제되어야한다. 그렇기에 onDelete = CASCADE 를 정의하여 자동으로 삭제되도록 구현하였다.

@Serializable
@Entity(
    tableName = "blog",
    foreignKeys = [ForeignKey(
        onDelete = CASCADE,
        entity = HistoryEntity::class,
        parentColumns = ["keyword"],
        childColumns = ["history_keyword"]
    )]
)
data class BlogEntity(
    @PrimaryKey @ColumnInfo(name = "id") val id: Int,
    @ColumnInfo(name = "history_keyword") val keyword: String,
    @ColumnInfo(name = "title") val title: String,
    @ColumnInfo(name = "description") val description: String,
    @ColumnInfo(name = "blogger_name") val bloggerName: String,
    @ColumnInfo(name = "date") val date: String,
    @ColumnInfo(name = "link") val link: String,
)

데이터 개수 확인 후 삭제

@Query("SELECT count(*) FROM history")
fun getHistoryCount(): Int

저장하기 전, 현재 히스토리 테이블의 전체 데이터 개수가 몇 개인지 확인해야한다. 개수가 20개가 넘으면 가장 오래된 데이터를 삭제하는 로직이 필요하다.


@Query("DELETE FROM history WHERE date = (SELECT MIN(date) FROM history)")
fun deleteOldestHistory()

가장 오래된 데이터를 삭제하는 쿼리문은 date 컬럼의 값이 가장 작은 데이터를 삭제하도록 구현한다.


override fun saveHistory(history: HistoryData): Completable {
    return Completable.fromCallable{
		val count = historyDao.getHistoryCount()
    	if (count >= HISTORY_SIZE) {
    		historyDao.deleteOldestHistory()
    	}
    	historyDao.saveHistory(history.toEntity())
	}
}

DataSource 계층에서의 로직이다. 히스토리 목록의 전체 개수를 가져와 HISTORY_SIZE (20) 보다 같거나 크면 가장 오래된 데이터를 삭제한다. 그리고 나서 방금 검색한 키워드를 저장하게 된다. 저장하고나서 전달받을 데이터가 필요하지는 않기 때문에 Completable 로 성공, 실패 여부만 반환하도록 한다. 각 쿼리문을 비동기로 실행하기 위해 fromCallable 메서드를 활용하여 작업 처리 후 Completable을 반환하도록 구현하였다.

같은 데이터가 삽입되는 경우

Dao의 Insert 메서드에서 충돌 전략을 REPLACE로 설정한다. 그러면, 기존 데이터가 삭제되고 새 데이터가 삽입된다.

@Insert(onConflict = OnConflictStrategy.REPLACE)

profile
유잼코딩

0개의 댓글