Isar v3에서 Isar Plus v4로 마이그레이션
레거시 isar 3.x 패키지를 isar_plus(v4)로 올리는 작업은 파일 포맷이 완전히 바뀌는 호환 불가 변경입니다. v4 코어는 서로 다른 메타데이터를 기록하므로 v3가 만든 데이터베이스를 열 수 없고, 다음과 같은 오류가 발생합니다.
VersionError: The database version is not compatible with this version of Isar.
해결 방법은 기존 데이터를 레거시 런타임으로 내보낸 뒤, 새 Isar Plus 데이터베이스로 다시 가져오는 것입니다. 아래 단계에 따라 진행하세요.
마이그레이션 개요
- 레거시 파일을 읽을 수 있도록
isar:^3.1.0+1에 의존하는 빌드를 유지하거나 배포합니다. - 마이그레이션 동안
isar_plus와isar_plus_flutter_libs를 기존 패키지 옆에 추가합니다. - 코드 생성기를 다시 실행해 스키마가 v4 API에 맞춰 컴파일되도록 합니다.
- v3 인스턴스의 모든 레코드를 새 Isar Plus 인스턴스로 복사합니다.
- 복사가 완료되면 레거시 파일을 삭제하고 오래된 의존성을 제거합니다.
이전 데이터가 필요 없다면, v3 디렉터리를 삭제하고 새 데이터베이스로 시작해도 됩니다. 이후 내용은 데이터를 보존하는 경우를 다룹니다.
의존성을 나란히 업데이트하기
복사가 끝날 때까지 기존 런타임을 유지한 후 새 런타임을 추가하세요.
dependencies:
isar: ^3.1.0+1
isar_flutter_libs: ^3.1.0+1
isar_generator: ^3.1.0+1
isar_plus: ^1.1.5
isar_plus_flutter_libs: ^1.1.5
dev_dependencies:
build_runner: ^2.4.10
두 패키지는 동일한 Dart 심볼을 노출하므로, 마이그레이션 중에는 항상 별칭으로 import 하세요.
import 'package:isar/isar.dart' as legacy;
import 'package:isar_plus/isar_plus.dart' as plus;
v4용 스키마 다시 생성하기
Isar Plus는 메인 패키지 내부에 생성기를 포함합니다. 빌더를 다시 실행해 새로운 헬퍼와 어댑터를 생성하세요.
dart run build_runner build --delete-conflicting-outputs
여기서 잠시 멈추고 컴파일 오류를 해결합니다(예: nullable Id? 필드는 int id 또는 Isar.autoIncrement로 바꿔야 함). API 마이그레이션 가이드에 핵심 변경 사항이 정리되어 있습니다.
writeTxn()->writeAsync(),writeTxnSync()->write()txn()->readAsync(),txnSync()->read()- ID는
id라는 이름을 사용하거나@id로 표시해야 하며, 자동 증가에는Isar.autoIncrement사용 @enumerated가@enumValue로 변경- 대부분의 레거시 링크는 임베디드 객체로 대체
실제 데이터 복사
단발성 마이그레이션 루틴을 작성하세요(예: 앱 초기화 전에 main()에서 실행하거나 별도 bin/migrate.dart 작성). 패턴은 다음과 같습니다.
- v3 런타임으로 레거시 저장소를 연다.
- 다른 디렉터리나 이름으로 새로운 v4 인스턴스를 연다.
- 각 컬렉션을 페이지 단위로 읽고 새 스키마에 매핑한 뒤 새 DB에
put한다. - SharedPreferences, 로컬 파일 또는 기능 플래그로 마이그레이션 완료 여부를 저장해 재실행을 막는다.
Future<void> migrateLegacyDb(String directoryPath) async {
final legacyDb = await legacy.Isar.open(
[LegacyUserSchema, LegacyTodoSchema],
directory: directoryPath,
inspector: false,
name: 'legacy',
);
final plusDb = await plus.Isar.open(
[UserSchema, TodoSchema],
directory: directoryPath,
name: 'app_v4',
engine: plus.IsarEngine.sqlite, // 네이티브 코어를 쓰려면 IsarEngine.isar
inspector: false,
);
await _copyUsers(legacyDb, plusDb);
await _copyTodos(legacyDb, plusDb);
await legacyDb.close();
await plusDb.close();
}
Future<void> _copyUsers(legacy.Isar legacyDb, plus.Isar plusDb) async {
const pageSize = 200;
final total = await legacyDb.legacyUsers.count();
for (var offset = 0; offset < total; offset += pageSize) {
final batch = await legacyDb.legacyUsers.where().offset(offset).limit(pageSize).findAll();
await plusDb.writeAsync((isar) async {
await isar.users.putAll(
batch.map((user) => User(
id: user.id ?? plus.Isar.autoIncrement,
email: user.email,
status: _mapStatus(user.status),
)),
);
});
}
}
팁:
_mapStatus같은 매핑 함수는 마이그레이션 루틴 옆에 두면 enum 이름 변경, 필드 제거, 데이터 정리를 한곳에서 처리할 수 있습니다.
컬렉션이 매우 크다면 루프를 isolate나 백그라운드 서비스에서 실행해 UI가 멈추지 않도록 하세요. 임베디드 객체와 링크도 동일한 방식으로 옮길 수 있습니다.
프로덕션에서 한 번만 실행되도록 하기
두 런타임을 함께 배포하면 매번 콜드 스타트 시 마이그레이션이 다시 실행될 수 있습니다. 상태 플래그를 저장해 설치당 한 번만 복사가 돌도록 하세요.
class MigrationTracker {
static const key = 'isarPlusMigration';
static Future<bool> needsMigration() async {
final prefs = await SharedPreferences.getInstance();
return !prefs.getBool(key).toString().contains('true');
}
static Future<void> markDone() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(key, true);
}
}
Future<void> bootstrapIsar(String dir) async {
if (await MigrationTracker.needsMigration()) {
await migrateLegacyDb(dir);
await MigrationTracker.markDone();
}
final isar = await plus.Isar.open(
[UserSchema, TodoSchema],
directory: dir,
);
runApp(MyApp(isar: isar));
}
향후 추가 마이그레이션을 예상한다면 불리언 대신 숫자 스키마 버전(예: 레거시는 3, Isar Plus는 4)을 저장해도 좋습니다. 데스크톱/서버 환경에서는 데이터베이스 폴더 옆에 .migrated 파일을 만들어도 됩니다.
정리하기
모든 컬렉션을 복사한 뒤에는 다음을 수행하세요.
prefs.setBool('migratedToIsarPlus', true)같은 플래그를 저장해 재실행을 막습니다.- 레거시 파일을 삭제합니다(수동 또는
plus.Isar.deleteDatabase(name: 'legacy', directory: directoryPath, engine: plus.IsarEngine.isar)호출). pubspec.yaml에서isar와isar_flutter_libs의존성을 제거합니다.- 필요하다면 새 데이터베이스 이름/디렉터리를 원래 이름으로 되돌립니다.
사용자들이 더 이상 레거시 빌드를 열지 않는다고 확신할 때 isar_plus만 의존하는 업데이트를 배포하세요.
문제 해결
VersionError가 계속된다: v4 인스턴스를 열기 전에 v3 파일을 삭제했는지 확인하세요. 오래된 WAL/LCK 파일이 헤더를 유지할 수 있습니다.- 기본 키 중복: v4의 ID는 고유한 non-null 정수여야 합니다.
Isar.autoIncrement를 사용하거나 복사 과정에서 직접 키를 생성하세요. - 제너레이터 실패:
dart pub clean후build_runner를 실행하고part '...g.dart';지시문이 누락되지 않았는지 확인합니다. - 롤백이 필요: 별도의 데이터베이스에 기록하므로 새 파일을 삭제하고 레거시 데이터를 유지한 채 다시 시도할 수 있습니다.
이 절차를 갖추면 사용자는 isar 3.x 빌드에서 isar_plus 릴리스로 안전하게 업데이트할 수 있습니다.