5minho / DreamRecorder

mino & bran BoostCamp Project
6 stars 0 forks source link

문자열 검색을 Like 쿼리에서 -> FTS(전문검색) 으로 바꿨을때 이슈 #76

Closed 5minho closed 6 years ago

5minho commented 6 years ago

꿈 검색 기능을 구현하면서 처음에는 Like Query를 생각하고 단순히 이런 코드를 작성했다.

let filterResult = dbManager.filterRow(query: DreamTable.table.filter(
            DreamTable.Column.title.like("%\(searchText)%") ||
            DreamTable.Column.content.like("%\(searchText)%")
            )
        )

처음에는 잘 작동하는거 처럼 보였으나 꿈 데이터를 몇 만개씩 늘리니 슬슬 검색 속도가 줄기 시작했다. 해결책으로는 Sqlite 에 Full test search (전문검색) 이라는 것이 있었고 Like 쿼리는 해당 문자열을 찾기 위해 테이블 전체를 Full-Scan 하고 전문검색 같은 경우는 문자열을 인덱싱해서 B-tree로 유지해 빠른 문자열 검색을 지원한다고 한다.

sqlite 에서는 가상테이블을 통해 전문검색을 지원한다.

가상테이블 vs 일반테이블 일반테이블을 처리할때 SQLite 는 DB파일에 접근하지만 가상테이블은 custom code에 접근한다. custom code 에는 data source 에서 데이터를 가져오는것 같은 작업들을 정의한다.

우리 앱은 DB 테이블이 많지 않다. 그래서 빠른 검색을 위해 가상테이블로 문자열 인덱스를 유지해도 메모리 부분의 이슈는 없을거라고 판단되어 전문검색을 사용하기로 했다.

5minho commented 6 years ago
let config = FTS4Config()
            .column(Virtual.Column.id, [.unindexed])
            .column(Virtual.Column.title)
            .column(Virtual.Column.content)
            .column(Virtual.Column.createdDate, [.unindexed])
            .column(Virtual.Column.modifiedDate, [.unindexed])

        self.dbManager.createTable(statement: Virtual.table.create(.FTS4(config)))

index를 유지할 컬럼을 설정해주자!! title 과 content 검색을 의도 했는데 검색 키워드와 전혀 상관없는 꿈들이 나와서 가상테이블을 위처럼 생성해주니 의도한 바로 검색결과가 나왔다.

5minho commented 6 years ago

Like Query 와 전문탐색 중 어느것이 더 빠른가를 보기 위해 테스트를 했다.

self.measure {
            let searchTexts = ["내용", "423", "꿈", "42", "번", "번째", "13", "31"]

            for searchText in searchTexts {

                DreamDataStore.shared.filter(searchText) // like %searchText% 사용
                DreamDataStore.shared.filteredDreams.forEach { dream in

                    if let content = dream.content?.lowercased(),
                        let title = dream.title?.lowercased() {
                        let lowerSearchText = searchText.lowercased()
                        if content.contains(lowerSearchText) == false  && title.contains(lowerSearchText) == false {
                            XCTFail()
                        }
                    }
                }
            }
        }
like
self.measure {
            let searchTexts = ["내용", "423", "꿈", "42", "번", "번째", "13", "31"]

            for searchText in searchTexts {

                DreamDataStore.shared.ftsFilter(searchText) // 전문탐색 사용
                DreamDataStore.shared.filteredDreams.forEach { dream in

                    if let content = dream.content?.lowercased(),
                        let title = dream.title?.lowercased() {
                        let lowerSearchText = searchText.lowercased()
                        if content.contains(lowerSearchText) == false  && title.contains(lowerSearchText) == false {
                            XCTFail()
                        }
                    }
                }
            }
        }
fts

이런식으로 searchText의 내용을 바꿔가면서 테스트를 해봤는데 결과는 like 쿼리가 더 빨랐다. 문자열을 다 비슷하게 저장해서 검색한점??? 아직 문자열의 개수가 작은점??? 아니면 테스트코드가 미숙해서??? 아직 정확한 이유가 생각하지 않아서 좀 더 조사해봐야겠다.

5minho commented 6 years ago
    func testLotsOfDateInsert() {

        var date = Date(timeIntervalSince1970: 0)

        let current = Date()
        var i = 0
        while date.timeIntervalSince1970 < current.timeIntervalSince1970 {

            let dream = Dream(  title: "\(i + 1) 번째 꿈 제목",
                                content: "\(i + 1) 번째 꿈의 내용",
                                createdDate: date,
                                modifiedDate: nil)

            date.addTimeInterval(43200)
            i += 1

            DreamDataStore.shared.insert(dream: dream)
        }

    }

위의 조사를 하기 위해 이런식으로 데이터를 삽입 했고 1970년 부터 오늘까지 하루에 2번씩 꿈을 저장 했을때 상황입니다. 테이블마다 문자열의 패턴이 일정해서 저런 결과가 나올수도 있으니 좀더 다양하게 문자열을 저장해서 검사해 볼 것!