Index는 데이터베이스에서 특정 데이터를 더 빠르게 검색할 수 있도록 도와주는 중요한 도구입니다. Isar는 고도로 최적화된 인덱스를 제공하며, 이는 대규모 데이터를 다룰 때 특히 유용합니다. 데이터를 효율적으로 검색하려면 적절한 인덱스를 설계하고 사용하는 것이 중요합니다.
컬렉션에 데이터가 저장될 때, 쿼리는 특정 데이터를 검색하기 위해 모든 데이터를 탐색해야 할 수도 있습니다. 이는 특히 데이터가 많을수록 비효율적입니다. 인덱스는 데이터를 정렬하여 특정 데이터를 빠르게 검색할 수 있도록 도와줍니다.
예를 들어, Isar에서는 다음과 같이 인덱스를 정의할 수 있습니다:
()
class Item {
Id id = Isar.autoIncrement; // 자동 증가 ID
late String name; // 아이템 이름
late int price; // 아이템 가격
}
위 코드에서 name이나 price 필드를 인덱스로 설정할 수 있습니다.
아래는 예제 데이터입니다:
| id | name | price |
|---|---|---|
| 1 | Book | 55 |
| 2 | Chair | 25 |
| 3 | Pencil | 5 |
| 4 | Laptop | 1000 |
| 5 | Pillow | 35 |
| 6 | Computer | 620 |
| 7 | Soap | 3 |
인덱스가 없는 경우, 특정 조건에 맞는 데이터를 찾으려면 모든 데이터를 순차적으로 검색해야 합니다.
예를 들어, 가격이 620 이상인 데이터를 검색하는 경우:
final results = await isar.items.filter()
.priceGreaterThan(620)
.findAll();
이 경우, 데이터가 많아질수록 탐색 시간도 길어지게 됩니다.
price 필드에 인덱스를 추가하면 검색 성능이 크게 향상됩니다. 인덱스는 데이터를 정렬된 형태로 저장하여 탐색 속도를 높입니다.
다음은 price 필드에 인덱스를 추가하는 방법입니다:
()
class Item {
Id id = Isar.autoIncrement;
late String name;
()
late int price; // 인덱스를 추가한 필드
}
인덱스를 추가하면 쿼리 속도가 향상됩니다.
| price | id |
|---|---|
| 3 | 7 |
| 5 | 3 |
| 25 | 2 |
| 35 | 5 |
| 55 | 1 |
| 620 | 6 |
| 1000 | 4 |
이제 price가 620 이상인 데이터를 찾을 때, 쿼리는 정렬된 데이터에서 바로 해당 값으로 이동합니다. 전체 데이터를 순차적으로 탐색하지 않아도 됩니다.
인덱스를 사용하면 데이터 정렬 작업도 매우 빠르게 수행할 수 있습니다. 데이터베이스는 정렬을 위해 데이터를 메모리로 불러오고 처리해야 하므로 비용이 많이 들 수 있지만, 인덱스를 사용하면 이러한 과정을 최적화할 수 있습니다.
final cheapest = await isar.products.filter()
.sortByPrice() // 가격으로 정렬
.limit(4) // 상위 4개 선택
.findAll(); // 결과 가져오기
final cheapestFast = await isar.products.where()
.anyPrice() // 가격 기준 인덱스 사용
.limit(4) // 상위 4개 선택
.findAll(); // 결과 가져오기
anyPrice()는 가격 인덱스를 사용해 데이터를 정렬된 상태로 가져옵니다..anyX()는 특정 인덱스를 정렬 목적으로 사용하도록 Isar에 지시합니다. 또는 .priceGreaterThan()와 같은 where 절을 사용하여 정렬된 결과를 얻을 수도 있습니다.이제 인덱스를 활용한 정렬의 중요성과 방법을 이해할 수 있습니다. 이를 통해 대규모 데이터를 효율적으로 다룰 수 있습니다.
유니크 인덱스는 데이터베이스에서 중복값이 저장되지 않도록 보장하는 기능입니다. 특정 필드나 필드 조합의 값이 항상 고유해야 한다면 유니크 인덱스를 설정해야 합니다.
username 필드에 유니크 인덱스 적용하기()
class User {
Id? id; // 자동 증가 ID
(unique: true) // 유니크 인덱스를 추가
late String username; // 사용자 이름
late int age; // 사용자 나이
}
위 예제에서는 username 필드에 유니크 인덱스를 추가하여 동일한 이름으로 데이터를 저장하지 못하도록 설정했습니다.
final user1 = User()
..id = 1
..username = 'user1' // 고유한 사용자 이름
..age = 25;
await isar.users.put(user1); // -> 정상적으로 저장됨
final user2 = User()
..id = 2
..username = 'user1' // 중복된 사용자 이름
..age = 30;
// 동일한 username을 가진 데이터를 삽입 시도
await isar.users.put(user2);
// -> 에러 발생: unique constraint violated
// 저장된 데이터 확인
print(await isar.users.where().findAll());
// 출력: [{id: 1, username: 'user1', age: 25}]
대체 인덱스는 유니크 제약 조건이 위반되었을 때, 에러를 발생시키는 대신 기존 객체를 새로운 객체로 교체하도록 설정할 수 있습니다. Isar에서 이는 인덱스의 replace 속성을 true로 설정하여 활성화할 수 있습니다.
username 필드에 대체 인덱스 설정()
class User {
Id? id; // 자동 증가 ID
(unique: true, replace: true) // 유니크 인덱스 + 대체 설정
late String username; // 사용자 이름
late int age; // 사용자 나이
}
위 코드에서는 username 값이 중복될 경우, 기존 객체를 교체하도록 설정합니다.
final user1 = User()
..id = 1
..username = 'user1' // 사용자 이름
..age = 25; // 나이
await isar.users.put(user1); // -> 데이터 저장
print(await isar.users.where().findAll());
// 출력: [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2
..username = 'user1' // 동일한 사용자 이름
..age = 30; // 새로운 나이
await isar.users.put(user2); // -> 기존 데이터를 교체
print(await isar.users.where().findAll());
// 출력: [{id: 2, username: 'user1', age: 30}]
user2의 username이 기존 user1과 동일하기 때문에 기존 데이터가 교체됩니다.putByX() 메서드 사용대체 인덱스는 기존 객체를 교체하는 대신 특정 키를 기준으로 데이터를 업데이트할 수도 있습니다. 이를 위해 Isar는 putByX() 메서드를 제공합니다.
putByX() 예제final user1 = User()
..id = 1
..username = 'user1' // 사용자 이름
..age = 25; // 나이
await isar.users.putByUsername(user1); // -> 데이터 저장
print(await isar.users.where().findAll());
// 출력: [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2
..username = 'user1' // 동일한 사용자 이름
..age = 30; // 새로운 나이
await isar.users.putByUsername(user2); // -> 기존 데이터를 업데이트
print(await isar.users.where().findAll());
// 출력: [{id: 1, username: 'user1', age: 30}]
putByUsername()은 기존 ID를 재사용하며, 링크 데이터도 유지됩니다.putByX() 메서드를 사용하면 기존 객체를 업데이트하면서 ID와 링크를 유지할 수 있습니다.기본적으로 String 및 List<String> 속성의 인덱스는 대소문자를 구분합니다. 하지만, 대소문자에 관계없이 데이터를 검색하려면 인덱스를 생성할 때 caseSensitive 옵션을 false로 설정해야 합니다.
()
class Person {
Id? id; // 자동 증가 ID
(caseSensitive: false) // 대소문자 구분 없음 설정
late String name; // 이름
(caseSensitive: false) // 대소문자 구분 없음 설정
late List<String> tags; // 태그 목록
}
name 필드: 이름을 대소문자 구분 없이 검색할 수 있도록 설정.tags 필드: 태그 목록의 값을 대소문자 구분 없이 검색 가능.final person1 = Person()
..id = 1
..name = 'Alice' // 대소문자 섞인 이름
..tags = ['Dart', 'Flutter'];
await isar.persons.put(person1); // 데이터 저장
final result1 = await isar.persons.filter()
.nameEqualTo('alice') // 대소문자 구분 없음
.findAll();
print(result1);
// 출력: [{id: 1, name: 'Alice', tags: ['Dart', 'Flutter']}]
final result2 = await isar.persons.filter()
.tagsElementEqualTo('flutter') // 태그 검색 (대소문자 무시)
.findAll();
print(result2);
// 출력: [{id: 1, name: 'Alice', tags: ['Dart', 'Flutter']}]
nameEqualTo: 이름을 검색할 때 대소문자를 무시하고 Alice를 alice로 검색 가능.tagsElementEqualTo: 태그 목록 내 요소를 검색할 때 대소문자를 무시.caseSensitive: false를 설정하면 String 및 List<String> 속성을 대소문자에 관계없이 검색 가능.데이터를 효과적으로 관리하기 위해 Isar는 세 가지 주요 인덱스 유형을 제공합니다. 각 유형은 데이터의 성격과 검색 방식에 따라 사용됩니다.
값 인덱스는 기본 인덱스 유형으로, 모든 기본 타입(숫자, 문자열 등)과 리스트(List)에 사용할 수 있습니다. 이 인덱스는 데이터의 각 값을 기준으로 인덱스를 생성합니다.
startsWith()와 같은 접두사 검색이 필요한 경우.()
class Product {
Id? id;
(type: IndexType.value) // 기본 값 인덱스
late String name;
(type: IndexType.value)
late List<String> categories;
}
해시 인덱스는 문자열과 리스트의 데이터를 해시 처리하여 저장 공간을 절약합니다. 하지만 해시 기반으로 저장되기 때문에 접두사 검색(startsWith())은 지원하지 않습니다.
()
class Product {
Id? id;
(type: IndexType.hash) // 해시 인덱스
late String name;
(type: IndexType.hash)
late List<String> tags;
}
리스트 데이터를 전체가 아닌 개별 요소로 해싱하여 저장합니다. 이를 통해 리스트 요소를 효과적으로 검색할 수 있습니다.
()
class Product {
Id? id;
(type: IndexType.hashElements) // 리스트의 각 요소를 해싱
late List<String> tags;
}
Composite Indexes (복합 인덱스)
복합 인덱스는 여러 속성(최대 5개)을 조합하여 인덱스를 생성하는 방식입니다. 이는 다중 속성 인덱스라고도 불립니다. 복합 인덱스를 사용하면 여러 속성을 조합하여 효율적으로 데이터를 검색할 수 있습니다.
다음은 age와 name 속성을 조합하여 복합 인덱스를 생성하는 예제입니다:
()
class Person {
Id? id; // 자동 증가 ID
(composite: [CompositeIndex('name')]) // 복합 인덱스 생성
late int age; // 나이
late String name; // 이름
late String hometown; // 고향
}
| id | age | name | hometown |
|---|---|---|---|
| 1 | 24 | Daniel | Berlin |
| 2 | 30 | Carl | Paris |
| 3 | 24 | David | San Diego |
| 4 | 24 | Carl | Munich |
| 5 | 30 | Audrey | Prague |
| 6 | 24 | Smith | London |
| age | name | id |
|---|---|---|
| 24 | Carl | 2 |
| 24 | David | 3 |
| 24 | Daniel | 1 |
| 24 | Smith | 6 |
| 30 | Audrey | 5 |
| 30 | Carl | 4 |
복합 인덱스는 age에 따라 정렬되고, 같은 age 내에서는 name을 기준으로 정렬됩니다.
final result = await isar.persons.where()
.ageEqualTo(24) // 첫 번째 속성 조건
.nameEqualTo('David') // 두 번째 속성 조건
.findAll();
print(result);
// 출력: [{id: 3, age: 24, name: 'David', hometown: 'San Diego'}]
final result = await isar.persons.where()
.ageEqualTo(24) // 첫 번째 속성 조건
.nameGreaterThan('D') // 두 번째 속성 범위 조건
.findAll();
print(result);
// 출력: [{id: 3, age: 24, name: 'David', hometown: 'San Diego'}, {id: 6, age: 24, name: 'Smith', hometown: 'London'}]
>, <)을 사용할 수 있음.Isar에서 리스트(List<String> 등)를 인덱싱하면 자동으로 다중 항목 인덱스가 생성됩니다. 리스트의 각 요소는 독립적인 인덱스 항목으로 처리되어 검색 시 활용됩니다. 이 방식은 태그, 키워드와 같은 데이터를 검색하거나, 전체 텍스트 검색을 구현할 때 유용합니다.
()
class Product {
Id? id; // 자동 증가 ID
late String description; // 설명
(type: IndexType.value, caseSensitive: false)
late List<String> descriptionWords; // 설명에 포함된 단어들
}
descriptionWords 필드는 리스트 형태의 데이터를 인덱싱하며, 각 단어가 개별적으로 검색 가능해집니다.caseSensitive: false로 설정하면 대소문자 구분 없이 검색할 수 있습니다.| id | description | descriptionWords |
|---|---|---|
| 1 | Comfortable black sofa | comfortable, black, sofa |
| 2 | Black leather recliner | black, leather, recliner |
| 3 | Recliner with soft cushions | recliner, soft, cushions |
| descriptionWords | id |
|---|---|
| black | 1, 2 |
| comfortable | 1 |
| cushions | 3 |
| leather | 2 |
| recliner | 2, 3 |
| sofa | 1 |
| soft | 3 |
final result = await isar.products.where()
.descriptionWordsElementEqualTo('black') // 'black' 포함된 데이터 검색
.findAll();
print(result);
// 출력: [
// {id: 1, description: 'Comfortable black sofa'},
// {id: 2, description: 'Black leather recliner'}
// ]
final result = await isar.products.filter()
.descriptionWordsElementEqualTo('recliner') // 'recliner' 포함
.descriptionWordsElementEqualTo('soft') // 'soft' 포함
.findAll();
print(result);
// 출력: [{id: 3, description: 'Recliner with soft cushions'}]
Isar는 Unicode Annex #29를 준수하여 텍스트를 단어로 분리합니다. 그러나 동일한 소리가 나는 단어를 검색하려면 음운 알고리즘(예: Soundex)을 사용하는 것이 유용할 수 있습니다.
descriptionWordsElementEqualTo와 같은 메서드를 사용하여 리스트 요소를 효율적으로 검색할 수 있습니다.질문이나 더 알고 싶은 점이 있다면 언제든 말씀해주세요! 😊