본문 바로가기

모바일(Mobile)/안드로이드(Android)

[Android] SQLite와 데이터 베이스 오버 플로우에 관하여

개요

최근 안드로이드 애플리케이션을 만들면서 사용자의 데이터를 영구적으로 저장하고, 분석하는 작업이 필요했는데요.

그래서, 자연스럽게 안드로이드에서 지원하는 데이터베이스, SQLite를 사용해서 DB를 설계해보았습니다.

 

SQLite는 아래와 같이, 직접 헬퍼 클래스 (eg. DBHelper)를 선언하여 데이터베이스를 관리할 수도 있습니다.

 

public class DBHelper extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "mydb.db";
    private static final int DATABASE_VERSION = 1;

    public DBHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 테이블 생성 SQL 쿼리 실행
        db.execSQL("CREATE TABLE IF NOT EXISTS memo (_id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, content TEXT)");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // DB 업그레이드 코드
    }
}



하지만, 보통은 Room이나 Realm같은 라이브러리를 활용하여 데이터베이스, dao 등을 함께 설계 및 작성하는 것이 일반적입니다.

 

그 이유로는 여러 가지가 있겠지만, 아무래도 작업의 생산성, 유지 보수에서의 효용성을 고려한다면

 

확실한 이점이 있기 때문에 안드로이드 애플리케이션에서 데이터베이스를 생성 및 관리할 때,

이러한 라이브러리의 사용이 필연적이라고 생각합니다.

 

// room Entity
@Entity
public class Memo {

    @PrimaryKey(autoGenerate = true)
    public int id;

    public String title;

    public String content;
}

// room dao
@Dao
public interface MemoDao {

    @Insert
    void insertMemo(Memo memo);

    @Query("SELECT * FROM Memo")
    List<Memo> getAllMemos();
}

// room abstarct dabase class

@Database(entities = {Memo.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

    public abstract MemoDao memoDao();
}

 


안드로이드 애플리케이션에서 데이터 베이스를 사용한다면,

직관적으로, 즉흥적으로 엔티티 및 스키마를 설계해서 데이터베이스를 구성할 수도 있을텐데요.

저는 최근에는 이러한 방식 보다는, 조금 시간이 걸리더라도 DB 설계 도구를 활용하여 아래와 같이

데이터베이스를 설계하고 테이블을 구성할 엔티티를 설계하는 습관을 들이고 있습니다.

fig 1.0. ERD Cloud를 사용한 간단한 DB 설계



그 이유로는 지나치게 직관적으로 DB 설계를 수행하면 결국 다시 수정할 일이 많아진다는 것을 느꼈고,

RDBMS에서 중요하게 다루는 '정규화'의 개념에 대해서 직접 효용성을 체감해보고 싶었기 때문입니다.

이를 위해 ERD Cloud와 같은 서비스를 활용해서 위와 같이 DB 설계를 해보았는데요.

이 과정에서 궁금한 점 들이 몇 가지가 생겼고 스스로 해답을 찾는 과정에서

알게된 사실을 정리 및 공유해보고자 이번 포스팅을 작성하게 되었습니다.

 


 

안드로이드와 데이터 베이스

관계형 데이터 베이스는 '질의문'으로 데이터를 조회할 때,

각 테이블을 구성하는 속성(Attribute)을 기준으로 하여 '조건부' 검색을 할 수 있습니다.

데이터베이스의 테이블에서 속성은, 수학에서의 행렬의 기본 개념을 기초로 하기 때문에,

경우에 따라서는 속성을 열 또는 칼럼(Column)이라고 칭하기도 합니다.

 

안드로이드에서도 마찬가지로 데이터를 저장하고 관리하기 위한 데이터베이스를 사용할 수 있으며,

이때 사용되는 데이터 베이스가 바로 SQLite입니다.

이러한 SQLite는 관계형 데이터 베이스 시스템(RDBMS, Relational DataBase Management System)의 일종입니다.


SQLite와 기본키

안드로이드 애플리케이션에서 DB 관리 도구로 사용하는 대표적인 라이브러리에는 Room과 Realm이 있습니다.

이 두 가지 도구는 궁극적으로는 SQLite 데이터 베이스를 사용하기 때문에,

애플리케이션에서 DB를 설계하고 각종 디자인 패턴을 통해 기능을 구현하고 데이터를 추가하면

자연스럽게 SQLite 데이터베이스 파일이 생성됩니다.

그리고 이러한 데이터 베이스에 등록되는 데이터는 행렬, 즉 테이블의 형태로 저장이 됩니다.

이때, 테이블의 '행'에 해당하는 일련의 데이터의 모음을 '레코드(Record)'라고 부르는데,

 

 

fig 1.1. DB 테이블의 구성요소

 

이러한 레코드는 각각을 구분하기 위한 최소한의 '정보',

즉 '기본키(PK, Primary Key)'라고 부르는 데이터가 하나 이상 필요로 합니다.

기본키는 경우에 따라서 하나의 속성이 될 수 도 있으나,

경우에 따라서는 여러 개의 속성을 합쳐서 하나의 키(=복합키)로 관리하기도 합니다.


안드로이드 기본키, 복합키와 관련된 자세한 개념은 다음 포스팅의 주제로 다시 다루도록 하겠습니다.


데이터 베이스는 기본적으로 '많은 양'의 데이터를 저장 및 관리하도록 특화된 '프로그램'이기 때문에
기본키는 많은 양의 데이터로부터 원하는 데이터를 찾는데 유용하게 쓰입니다.

예컨데, SQL 쿼리문에서 WHERE 절에 기본키를 사용하여 데이터를 조회하면,
이는 기본키를 사용하지 않은 다른 속성을 기준으로 검색할 때보다 훨씬 빠른 조회 성능을 보여줍니다.
그리고 이러한 성능의 격차는 데이터가 많아질수록 더욱 분명해집니다.

SQLite에서는 이토록 중요한 역할을 하는 기본키는

엔티티 클래스 라고 부르는 별도의 클래스를 통해서 설계할 수 있는데요.

 

// room Entity
@Entity
public class Memo {

    @PrimaryKey(autoGenerate = true)
    public int id;

    public String title;

    public String content;
}


이러한 기본키는 보통 long의 자료형으로 선언되며 자동으로 생성(auto generated)되도록 할 수 있습니다.

SQlite에서는 기본키(PK, Primary Key)가 overflow되면 어떻게 될까?

오늘의 포스팅을 작성하게 된 계기가 된 질문이기도 한데요.

복합키로 여러 개의 속성을 묶어 기본키를 구성한 경우를 제외한다면,

long의 형태로 기본키를 생성 및 관리하게 되면 한 가지 염려되는 부분이 존재합니다.

바로, 이러한 기본키도 데이터가 계속해서 증가하면 오버플로우(overflow)가 발생하지 않을까? 라는 의문점 입니다.

안드로이드에서 데이터베이스에 저장되는 기본키를 auto generated로 설정했다면,


이는 0보다큰 LONG의 MAX 범위만큼 누적되어 데이터가 증가할 것입니다.

그리고 이론적으로는 데이터가 계속하여 누적되면 결국에는 오버플로우(overflow)가 발생할 수 있고,

오버플로우는 데이터 베이스 뿐만 아니라 다양한 소프트웨어에서 보안상 취약점이 될 수 있기 때문에

이러한 취약점을 관리하기 위해서는 대응책이 필요로 할 것입니다.

 

SQLite의 오버플로우 대처법

SQLite에서 이론상으로는 데이터베이스의 테이블에서 관리하는 기본키가 long이고 auto generated라면,
시간이 지난 후에 오버플로우가 발생할 수 있습니다.

그리고 이렇게 오버 플로우가 발생하는 순간이 도래했다면,

 

SQLite는 우선 새로이 추가한 id 대신 사용되지 않는 다른 id가 있는지 탐색하고 이러한 id가 있다면 대체하여 삽입합니다.
그리고 이러한 몇 번의 시도에도 불구하고 용량을 가득채운 상황이라면 sqlite는 `SQLITE FULL`이라는 오류를 발생시킨다고 합니다.

 

https://stackoverflow.com/questions/10727541/running-out-of-unique-ids-in-sqlite-database

 

Running out of unique IDs in sqlite database

I know that sqlite database automatically generates a unique id (autoincrement) for every record inserted. Does anyone know if there is any possibility of running out of system unique IDs in sqlite3

stackoverflow.com


즉, 이론상으로는 SQLite의 기본키를 long으로 관리한다면 시간이 지나면 오버플로우가 발생할 수 있는 것입니다.

 

그러나 long이라는 자료형의 범위는 정말 큰 수이기 때문에, 정상적인 상황에서라면
기본키가 오버플로우 되는것은 아주 극단적인 케이스이긴 할 것입니다.

그리고, 이러한 수치는 SQLite에 546년간 100밀리초마다 데이터를 삽입시켰을 경우 오버플로우가 발생하는 양이라고 합니다.

보통 안드로이드 앱에서 관리하는 데이터 베이스는 여러 명의 사용자를 대상으로 하는 '서버'와는 다소 환경이 다르기 때문에,

웬만한 엔터프라이즈 급 서비스를 하는 게 아닌 이상에는 오버플로우가 발생할 가능성은 현저히 낮을 것입니다.

더불어서, 안드로이드 앱의 경우 앱 삭제시 관리하던 보통 DB 파일도 초기화 되는데
이런 런타임 조건까지 고려하면 더 0에 수렴한다고 보여집니다.

그럼에도 불구하고 overflow의 가능성은 보안상 취약점이 될 수 있기 때문에,

DB를 안전하게 관리해주는 대비책을 고려해볼 수 있습니다.

이에 대해서 제가 생각한 방법은 두 가지가 있는데요.


첫 번째는, 데이터가 누적해서 쌓이는 테이블에 대한 초기화 기능을 제공하여 0부터 새로 쌓도록 해주는 방법.

두 번째는, 이벤트 스케줄러와 같은 방법을 통해 굳이 사용자와 상호작용 하지 않더라도 DB를 주기적으로 정리하는 작업을 자동으로
실행시키는 방법이 가능할 것 같습니다.

첫 번째 방법의 예시로는, 안드로이드에서 room을 사용한다고 했을 때,
데이터 베이스에 TRUNCATE 연산을 할 수 있도록 DAO 메서드를 구현하는 방법이 있을 것 같습니다.

 

    // truncate
    @Query("DELETE FROM sqlite_sequence where name='my_database'")
    fun resetTable()



두 번째 방법은, SQLite에서는 다른 관계형 데이터 베이스와 다르게 이벤트 스케줄러를 제공하지 않고 있습니다.
그래서, 별도로 서비스(Service)와 브로드 캐스트 리시버(BroadCase Recevier)와 같은 클래스를 적절히 활용하여,
사용자와 굳이 상호작용 하지 않더라도 비동기적으로 작업을 수행하도록 할 수 있을 것입니다.

이에 대한 구체적인 방법으로는
① AsyncTask(Deprecated됨, rxJava나 코루틴 등으로 대체 필요) 혹은 WorkManager와 같은 워커 스레드 라이브러리를 사용하거나,

② AlarmManager를 사용하여 특정 시간에 알람을 설정하고 알람 리시버에서 쿼리를 실행하거나 작업을 수행할 수

③ Firebase Job Scheduler 사용하는 방법이 있습니다.

특히 Firebase Job Scheduler는 네트워크 연결 상태, 배터리 수준 등을 고려하여 작업을 효율적으로 실행할 수 있기 때문에
이러한 이점을 잘 활용해도 좋을 것 같습니다.

 


 

정리

정리하자면, SQLite 데이터 베이스에서 기본키는

오랜 시간과 많은 양의 데이터가 자주 발생 및 추가되는 상황이라면 이론상 오버플로우 될 수 있으나.

 

개발자가 개발 단계에서 적절히 DB 관련 정리 메커니즘을 잘 설계하면 충분히 예방 가능할 것이고,

이러한 방법은 개발자의 취향이나, 개발 환경 등에 따라 다앙햔 방법을 채택할 수 있을 것입니다.