본문 바로가기

Android/Troubleshooting

ROOM DB 마이그레이션 오류 개선

 

 

앱 업데이트를 진행하면 참 기상천외한 버그들을 많이 보는 것 같다.

 

특히 DB 관련 로직을 수정하면 재현조차 되지 않는 버그들을 흔히 볼 수 있다..

 

data class에 컬럼을 추가해서 업데이트를 했는데 마이그레이션 코드에서 이미 존재하는 컬럼에 대한 중복 추가 오류로 

Fatal Exception: android.database.sqlite.SQLiteException
duplicate column name:~~~

 

요런게 계속 발생했다.

비록 전체 사용자의 10%도 안되는 장애지만, 일단 장애가 발생하니 묵인할 수는 없다.

 

아니 근데 없던 걸 추가했는데 중복 이름이 생기다니

게다가 내 테스트 환경에서는 재현조차 안된다니

이걸 어떻게 해결할지 머리 터지게 고민했다

 

아무 문제없이 업데이트 적용이된 나머지 90% 이용자 케이스를 고려할 때, Migration 코드를 빼면 오히려 늦게나마 업뎃한 이용자에게 문제가 생길 수 있고.. 그냥 안되면 지웠다 다시 깔라고 하면 유저이탈 무조건(B2B라서 상관은 없지만)이고..

 

그냥 문제가 생기면 DB를 지웠다가 생성하는 거로 하기로 해결했다.

 

처음에는 Table을 Delete했다가 Create하는 걸 시도해 보려고 했는데..

그러면 아래와 같은 기-다란 코드를 매번 호출한다.

database.execSQL(
    """
        CREATE TABLE IF NOT EXISTS MyTable (
            AAAA TEXT NOT NULL PRIMARY KEY,
            BBBB TEXT NOT NULL,
            CCCC TEXT NOT NULL,
            DDDD TEXT NOT NULL,
            EEEE TEXT NOT NULL,
            FFFF TEXT NOT NULL,
            GGGG TEXT NOT NULL,
            .
            .
            .
            .
        )
    """
    )

뭐 사실 이런 코드가 실행된다고 문제될 건 없다.

하지만, 컬럼을 추가하거나 제거하는 일이 생길때, data class에서 제거된 걸 이 코드에서도 반드시 추가/제거해 줘야하는 추가 작업이 발생한다.

당장 내가 짠 코드니깐 나는 이해할지 몰라도 후임자나 다른 담당자가 유지보수를 담당하게 되면, 무조건 잊어먹고 처리안해서 오류가 생길수 밖에 없다 판단되어서 기각.

 

그래서 아래처럼 DB 자체를 지웠다가 재생성하는 방식으로 변경했다.

private val database = "MyDatabase" // DB 이름

fun getInstance(context: Context): MyDB {
            return instance ?: synchronized(MyDB::class) {
                instance ?: try {
                    buildDatabase(context).also { db ->
                        db.openHelper.writableDatabase
                        instance = db
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                    
                    context.deleteDatabase(database) // DB 삭제
                    
                    // 데이터베이스 재생성
                    buildDatabase(context).also { db ->
                        db.openHelper.writableDatabase
                        instance = db
                    }
                }
            }
        }

        private fun buildDatabase(context: Context): MyDB {
            return Room.databaseBuilder(
                context.applicationContext,
                MyDB::class.java,
                database
            )
                .addMigrations(Migration_1) // 마이그레이션 함수, 여기서 오류가 발생 할 수 있음.
                .build()
        }

 

 

이러면 문제가 생겼을때 catch문이 실행되면서 DB를 삭제하고 재생성한다.

 

물론 관련 장애 이슈를 해결하는 정답은 아니겠지만, 본인이 작업중인 앱의 경우 DB 내부 테이블이 하나뿐이라 그냥 날리고 재생성 하는 거로 해결했다.

 

 

더 좋은 방법이 있으면 꼭 공유 부탁드립니다.