In a large scale app, there are chances of my db file (created through android room lib), getting corrupted. For such a user whats the fallback ?
Will room delete the db file and re-create from scratch in production mode or will I have to handle that myself ?
CodePudding user response:
For such a user whats the fallback ?
Backups and restore, noting that backups should not be taken when there are any open transactions. It is best to ensure that if in WAL mode (which is the default mode with room) that the database is fully committed (i.e. the WAL file is empty or doesn't exist).
- The backup could be a simple file copy or you can use the VACUUM INTO the latter having the advantage of potentially freeing space but the disadvantage is that it could be more resource intensive.
You could maintain additional databases where changes are applied to other databases as and when the main database is changed. Obviously there would be an overhead. This would have the advantage that if corruption were to occur that it would be less likely to occur in the other databases.
You could maintain a log that allowed roll back and roll forward of changes. The latter being used to roll forward from a backup to the point of corruption or close to the point of corruption.
Will room delete the db file and re-create from scratch in production mode or will I have to handle that myself ?
if detected (when opening) then yes :-
onCorruption The method invoked when database corruption is detected. Default implementation will delete the database file.
What happens when room android db gets corrupted?
here's an example of file corruption
2021-10-27 10:36:19.281 7930-7930/a.a.so69722729kotlinroomcorrupt E/SQLiteLog: (26) file is not a database
2021-10-27 10:36:19.285 7930-7930/a.a.so69722729kotlinroomcorrupt E/SupportSQLite: Corruption reported by sqlite on database: /data/user/0/a.a.so69722729kotlinroomcorrupt/databases/thedatabase
2021-10-27 10:36:19.286 7930-7930/a.a.so69722729kotlinroomcorrupt W/SupportSQLite: deleting the database file: /data/user/0/a.a.so69722729kotlinroomcorrupt/databases/thedatabase
2021-10-27 10:36:19.306 7930-7930/a.a.so69722729kotlinroomcorrupt D/TAG: onCreate Invoked.
2021-10-27 10:36:19.312 7930-7930/a.a.so69722729kotlinroomcorrupt D/TAG: onOpen Invoked.
As can be seen by logging from the callbacks that after the file is deleted that onCreate is invoked thus creating a new empty database.
The code used for the above, which you may wish to adapt for testing, is :-
A simple @Entity Something :-
@Entity
data class Something(
@PrimaryKey
val id: Long?=null,
val something: String
)
A Simple @Dao AllDao
@Dao
abstract class AllDao {
@Insert
abstract fun insert(something: Something)
@Query("SELECT * FROM something")
abstract fun getAllFromSomething(): List<Something>
}
The @Database TheDatabase
@Database(entities = [Something::class],version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
const val DATABASENAME = "thedatabase"
const val TAG = "DBINFO"
private var instance: TheDatabase? = null
var existed: Boolean = false
fun getInstance(context: Context): TheDatabase {
existed = exists(context)
if (exists(context)) {
Log.d(TAG,"Database exists so corrupting it before room opens the database.")
corruptDatabase(context)
}
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java, DATABASENAME)
.allowMainThreadQueries()
.addCallback(cb)
.build()
}
instance!!.openHelper.writableDatabase // Force open
return instance as TheDatabase
}
object cb: Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Log.d("TAG","onCreate Invoked.")
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
Log.d("TAG","onDestructiveMigration Invoked.")
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
Log.d("TAG","onOpen Invoked.")
}
}
fun exists(context: Context): Boolean {
val db = File(context.getDatabasePath(DATABASENAME).path)
return db.exists()
}
/**
* Corrupt the database by
* copying the file via buffered reads and write
* BUT only write part of a buffer
* AND skip the 3rd block
* Furthermore, delete the -wal file (possible that this )
* Note that often it is the -wal file deletion that corrupts
*/
fun corruptDatabase(context: Context) {
Log.d("TAG","corruptDatabase Invoked.")
var db: File = File(context.getDatabasePath(DATABASENAME).path)
logFileInfo(db,"Initial")
var tempdb = File(context.getDatabasePath("temp" DATABASENAME).path)
logFileInfo(tempdb,"Initial")
val blksize = 128
var buffer = ByteArray(blksize)
var i: InputStream
var o: FileOutputStream
try {
i = FileInputStream(db)
o = FileOutputStream(tempdb)
var blocks = 0;
var writes = 0;
while (i.read(buffer) > 0) {
if(blocks % 2 == 1) {
writes
o.write(buffer,buffer.size / 4,buffer.size / 4)
}
}
Log.d(TAG,"${blocks} Read ${writes} Written")
o.flush()
o.close()
i.close()
db = File(context.getDatabasePath(DATABASENAME).path)
logFileInfo(db,"After copy")
tempdb = File(context.getDatabasePath("temp${DATABASENAME}").path)
logFileInfo(tempdb,"After copy")
} catch (e: IOException) {
e.printStackTrace()
}
db.delete()
//(context.getDatabasePath(DATABASENAME "-wal")).delete()
logFileInfo(db,"After delete")
tempdb.renameTo(context.getDatabasePath(DATABASENAME))
logFileInfo(tempdb,"After rename")
logFileInfo(context.getDatabasePath(DATABASENAME),"After rename/new file")
}
fun logFileInfo(file: File, prefix: String) {
Log.d(TAG,"${prefix} FileName is ${file.name}\n\tpath is ${file.path}\n\tsize is ${file.totalSpace} frespace is ${file.freeSpace}")
}
}
}
Finally an invoking Activity MainActivity
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this);
dao = db.getAllDao()
dao.insert(Something(something = "Something1 " TheDatabase.existed))
dao.insert(Something(something = "Something2 " TheDatabase.existed))
for(s: Something in dao.getAllFromSomething()) {
Log.d(TheDatabase.TAG,"Something with ID " s.id " is " s.something)
}
}
}
- Note it's the -wal deletion that corrupts for the database above. It is quite resilient in regards to blocks being deleted removed, at least for smaller databases where the WAL file is of sufficient size to recover as such by applying the changes stored within. However, testing based upon the above but with a second table and 100000 rows inserted, then the partial block writes and missed block writes do corrupt the database.