RealmSwift 10.47.0 기준으로 작성된 글입니다.
[READ]
No indexing
총 Item 갯수 \ Query 실행 횟수 |
50,000 | 100,000 | 200,000 |
5,000 | 2.377 (초) | 4.388 | 8.310 |
10,000 | 4.689 | 8.703 | 16.429 |
20,000 | 9.376 | 16.963 | 32.514 |
Yes Indexing
총 Item 갯수 \ Query 실행 횟수 |
50,000 | 100,000 | 200,000 |
5,000 | 0.436 | 0.440 | 0.435 |
10,000 | 0.874 | 0.873 | 0.906 |
20,000 | 1.749 | 1.755 | 1.773 |
[Write]
No Indexing
Query 실행 횟수 | 소요 시간 | ||
1,000 | 2.673 | ||
5,000 | 16.570 | ||
10,000 | 31.701 | ||
20,000 | 70.368 |
Yes Indexing
Query 실행 횟수 | 소요 시간 |
1,000 | 2.534 |
5,000 | 18.546 |
10,000 | 32.907 |
20,000 | 70.370 |
일단 쓰기 작업은 거의 차이가 없었다. 아마도 텍스트 인덱스 단 하나를 추가했기때문에 인덱싱을 위한 추가 작업 시간이 무시해도 될 정도 였던걸까?(잘 모르겠다.)
하지만 읽기 작업에서는 큰 차이를 확인할 수 있었다. 인덱스를 사용하지 않았을 때는 Item 갯수가 적음에도 갯수가 N배 증가함에따라 소요시간도 N배 증가하는 추세를 보였지만, 인덱스를 사용했을 때는 200,000개 이하 Item 갯수에서 갯수 변화에 따른 소요시간에 큰 변화도 없었고 전체적인 소요시간이 굉장히 많이 줄어들었다.
import Foundation
import Realm
import RealmSwift
import XCTest
/// Read 테스트를 위해 준비할 아이템 갯수
fileprivate let PREPARED_ITEM_COUNT = 100_000 // 변경 가능
/// 테스트 중 쿼리가 실행되는 횟수
fileprivate let QUERY_EXECUTION_COUNT = 5_000 // 변경 가능
/// 실제 테스트가 실행되는 범위. 1에서 `QUERY_EXECUTION_COUNT`까지 무작위로 섞는다.
fileprivate let TEST_SCOPE = (1...QUERY_EXECUTION_COUNT).shuffled()
final class RealmPerformanceTests: XCTestCase {
var sut: Realm!
var config: Realm.Configuration!
override func setUpWithError() throws {
try super.setUpWithError()
self.continueAfterFailure = false
}
override func tearDownWithError() throws {
try super.tearDownWithError()
sut = nil
config = nil
}
func test_performanceWithoutIndex_whenWrite() throws {
// Arrange
config = .init()
config.fileURL?.deleteLastPathComponent()
config.fileURL?.append(path: "RealmWriteTestsWithoutIndex")
config.fileURL?.appendPathExtension("realm")
sut = try! .init(configuration: config)
// print(config.fileURL!)
defer { // 측정 대상 보존 상태 검증
let objects = sut.objects(Item.self)
XCTAssertTrue(objects.isEmpty)
}
// Act
self.measure {
do { // 측정 대상 준비 상태 검증
let objects = sut.objects(Item.self)
XCTAssertTrue(objects.isEmpty)
}
TEST_SCOPE.forEach { number in
let newItem: Item = .init(number: "\(number)")
do {
try sut.write {
sut.add(newItem)
}
} catch {
XCTFail("저장 실패.")
}
}
do {
try sut.write {
sut.deleteAll()
}
} catch {
XCTFail("삭제 실패.")
}
}
}
func test_performanceWithIndex_whenWrite() throws {
// Arrange
config = .init()
config.fileURL?.deleteLastPathComponent()
config.fileURL?.append(path: "RealmWriteTestsWithIndex")
config.fileURL?.appendPathExtension("realm")
sut = try! .init(configuration: config)
// print(config.fileURL!)
defer { // 측정 대상 보존 상태 검증
let objects = sut.objects(IndexingItem.self)
XCTAssertTrue(objects.isEmpty)
}
// Act
self.measure {
do { // 측정 대상 준비 상태 검증
let objects = sut.objects(IndexingItem.self)
XCTAssertTrue(objects.isEmpty)
}
TEST_SCOPE.forEach { number in
let newItem: IndexingItem = .init(number: "\(number)")
do {
try sut.write {
sut.add(newItem)
}
} catch {
XCTFail("저장 실패.")
}
}
do {
try sut.write {
sut.deleteAll()
}
} catch {
XCTFail("삭제 실패.")
}
}
}
func test_performanceWithoutIndex_whenRead() throws {
// Arrange
config = .init()
config.fileURL?.deleteLastPathComponent()
config.fileURL?.append(path: "RealmReadTestsWithoutIndex")
config.fileURL?.appendPathExtension("realm")
sut = try! .init(configuration: config)
// print(config.fileURL!)
XCTAssertGreaterThanOrEqual(PREPARED_ITEM_COUNT, QUERY_EXECUTION_COUNT, "준비된 아이템 갯수가 Query 실행 횟수보다 많아야합니다.")
if sut.objects(Item.self).count != PREPARED_ITEM_COUNT {
try sut.write {
sut.deleteAll()
(1...PREPARED_ITEM_COUNT).forEach { number in
let item: Item = .init(number: "\(number)")
sut.add(item)
}
}
}
// Act
self.measure {
TEST_SCOPE.forEach { number in
let objects = sut.objects(Item.self)
let object = objects.where { $0.number.equals("\(number)") }
XCTAssertNotNil(object)
}
}
}
func test_performanceWithIndex_whenRead() throws {
// Arrange
config = .init()
config.fileURL?.deleteLastPathComponent()
config.fileURL?.append(path: "RealmReadTestsWithIndex")
config.fileURL?.appendPathExtension("realm")
sut = try! .init(configuration: config)
// print(config.fileURL!)
XCTAssertGreaterThanOrEqual(PREPARED_ITEM_COUNT, QUERY_EXECUTION_COUNT, "준비된 아이템 갯수가 Query 실행 횟수보다 많아야합니다.")
if sut.objects(Item.self).count != PREPARED_ITEM_COUNT {
try sut.write {
sut.deleteAll()
(1...PREPARED_ITEM_COUNT).forEach { number in
let item: IndexingItem = .init(number: "\(number)")
sut.add(item)
}
}
}
// Act
self.measure {
TEST_SCOPE.forEach { number in
let objects = sut.objects(IndexingItem.self)
let object = objects.where { $0.number.equals("\(number)") }
XCTAssertNotNil(object)
}
}
}
}
final class Item: Object {
@Persisted(primaryKey: true) var id: ObjectId = .generate()
@Persisted var number: String
convenience init(number: String) {
self.init()
self.number = number
}
}
final class IndexingItem: Object {
@Persisted(primaryKey: true) var id: ObjectId = .generate()
@Persisted(indexed: true) var number: String
convenience init(number: String) {
self.init()
self.number = number
}
}
사용된 테스트 코드
'Computer' 카테고리의 다른 글
What is a Use Case? (0) | 2024.03.18 |
---|---|
Class vs Struct 성능 비교 테스트 (4) | 2024.03.14 |
RxSwift 구현에 대한 이해 기초 (1) (1) | 2024.02.15 |
[RxSwift] `UITextField.rx.text` 프로퍼티의 '예상치 못한 요소 방출' 이슈 (0) | 2024.02.14 |
RxSwift.Single 구현에서의 `stopped` 변수의 의미 (1) | 2024.02.13 |