이 글은 누구를 위한 것인가
- 오프라인 우선 앱에서 어떤 DB를 써야 할지 모르는 팀
- 수만 건 데이터를 모바일에서 빠르게 검색해야 하는 개발자
- Room/Core Data에서 Realm으로 마이그레이션을 고려하는 팀
들어가며
"오프라인에서도 검색이 돼야 한다." 수만 건 상품 데이터를 로컬에 저장하고 빠르게 검색하려면 올바른 DB 선택이 중요하다. SQLite, Realm, WatermelonDB는 각각 다른 상황에 최적이다.
이 글은 bluefoxdev.kr의 모바일 데이터 저장소 가이드 를 참고하여 작성했습니다.
1. DB 선택 기준
[로컬 DB 비교]
SQLite (Room/FMDB):
성숙도: 최고 (40년+)
쿼리: 완전한 SQL
성능: 대용량 집계 쿼리 강함
반응성: 트리거 필요 (자동 업데이트 없음)
크로스플랫폼: Room(Android)/FMDB(iOS) 별도
적합: 복잡한 쿼리, 레거시, 팀이 SQL 잘 알 때
Realm:
성숙도: 높음
쿼리: Realm Query Language
성능: 읽기 빠름, 쓰기 ACID
반응성: 객체 변경 자동 감지
크로스플랫폼: iOS, Android, React Native, Flutter
적합: 반응형 앱, 오프라인 동기화, Atlas Device Sync
WatermelonDB:
성숙도: 중간
기반: SQLite (C++)
반응성: Observable 쿼리 (RxJS)
성능: 수십만 레코드에 최적화
React Native: 네이티브 SQLite 직접 접근
적합: React Native 대용량 데이터
[선택 가이드]
단순 키-값: UserDefaults/SharedPreferences
구조화 데이터 < 1만건: Room/Core Data/SwiftData
반응형 + 오프라인 동기화: Realm
React Native 대용량: WatermelonDB
복잡한 SQL 집계: SQLite (Room/FMDB)
2. Realm 구현 (크로스플랫폼)
// iOS Swift - Realm
import RealmSwift
// 1. 모델 정의
class Product: Object {
@Persisted(primaryKey: true) var id: String = ""
@Persisted var name: String = ""
@Persisted var price: Double = 0
@Persisted var category: String = ""
@Persisted var isFavorite: Bool = false
@Persisted var updatedAt: Date = Date()
// 역참조
@Persisted(originProperty: "products") var orders: LinkingObjects<Order>
}
class Order: Object {
@Persisted(primaryKey: true) var id: String = ""
@Persisted var products: List<Product>
@Persisted var totalAmount: Double = 0
@Persisted var createdAt: Date = Date()
}
// 2. Realm 설정 및 마이그레이션
class RealmManager {
static let shared = RealmManager()
private var realm: Realm!
func configure() throws {
let config = Realm.Configuration(
schemaVersion: 3,
migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion < 2 {
migration.enumerateObjects(ofType: "Product") { _, newObject in
newObject!["category"] = "기타"
}
}
if oldSchemaVersion < 3 {
// isFavorite 새로 추가 (기본값 false)
}
}
)
Realm.Configuration.defaultConfiguration = config
realm = try Realm()
}
// 3. 반응형 쿼리 (뷰 자동 업데이트)
func observeFavoriteProducts(onChange: @escaping ([Product]) -> Void) -> NotificationToken {
let results = realm.objects(Product.self)
.filter("isFavorite == true")
.sorted(byKeyPath: "name")
return results.observe { changes in
switch changes {
case .initial(let products):
onChange(Array(products))
case .update(let products, _, _, _):
onChange(Array(products))
case .error(let error):
print("Realm 에러: \(error)")
}
}
}
// 4. 쓰기 트랜잭션
func toggleFavorite(productId: String) throws {
guard let product = realm.object(ofType: Product.self, forPrimaryKey: productId) else { return }
try realm.write {
product.isFavorite = !product.isFavorite
}
}
// 5. 대량 데이터 삽입 (서버 동기화)
func syncProducts(_ products: [ProductDTO]) throws {
let realmProducts = products.map { dto -> Product in
let p = Product()
p.id = dto.id
p.name = dto.name
p.price = dto.price
p.category = dto.category
p.updatedAt = dto.updatedAt
return p
}
try realm.write {
realm.add(realmProducts, update: .modified)
}
}
// 6. 검색 (풀텍스트 유사)
func searchProducts(query: String) -> Results<Product> {
return realm.objects(Product.self)
.filter("name CONTAINS[cd] %@", query)
.sorted(byKeyPath: "name")
}
}
// SwiftUI에서 Realm 사용
struct ProductListView: View {
@ObservedResults(Product.self, sortDescriptor: SortDescriptor(keyPath: "name")) var products
var body: some View {
List(products) { product in
ProductRow(product: product)
}
.searchable(text: $searchText)
.onChange(of: searchText) { newValue in
$products.filter = NSPredicate(format: "name CONTAINS[cd] %@", newValue)
}
}
@State private var searchText = ""
}
struct ProductRow: View {
@ObservedRealmObject var product: Product
var body: some View {
HStack {
Text(product.name)
Spacer()
Button {
$product.isFavorite.wrappedValue.toggle() // 자동 Realm 업데이트
} label: {
Image(systemName: product.isFavorite ? "heart.fill" : "heart")
}
}
}
}
struct ProductDTO { let id: String; let name: String; let price: Double; let category: String; let updatedAt: Date }
마무리
DB 선택은 "얼마나 복잡한 쿼리가 필요한가"와 "반응형 UI가 필요한가"로 결정한다. 반응형 UI + 오프라인 동기화 → Realm, 복잡한 집계 SQL → Room/FMDB. Realm의 @ObservedResults와 @ObservedRealmObject는 DB 변경을 자동으로 UI에 반영해 코드를 크게 단순화한다.