0%

[React Native] 데이터 저장하기 : AsyncStorage와 SQLite, 그리고 Realm


Design by @imrahelk


안녕하세요. 안피곤입니다.

리액트 네이티브에서 데이터를 저장하고 사용할 수 있는 방법이 필요합니다. 그래서 AsyncStorageSQLite, 그리고 Realm에 대해서 살펴보았습니다. 그리고 각각의 API를 방법을 학습하면서 성능도 비교하였습니다. AsyncStorage와 database를 비교할 수는 없지만, 리액트 네이티브에서 AsyncStorage도 많이 사용되기 때문에 포함하였습니다.


* * *

우선 InsertSelect를 각각 테스트를 하기 위해서, 스팀잇 글 100건을 가져오는 함수를 구현합니다. 이 함수를 공통적으로 사용하여 데이터를 저장하도록 하겠습니다.

1
2
3
4
5
6
7
8
9
// 스팀잇 글 100건 가져오기
_getDiscussionsByBlog() {
return fetch('https://api.steemit.com', {
method: 'post',
body: JSON.stringify({jsonrpc:"2.0",method:"condenser_api.get_discussions_by_blog",params:{tag:"anpigon",limit:100},id:1})
})
.then(r => r.json())
.then(({result}) => result)


* * *

AsyncStorage

AsyncStorage는 리액트 네이티브를 위한 key-value 형식의 스토리지입니다. Window.localStorage 와 매우 유사합니다.

**v0.59**부터는 react-native에 포함된 async-storageDeprecated 되었습니다. 그래서 @react-native-community/async-storage 설치하여 사용하는 것을 권장합니다. 자세한 내용은 공식 문서를 참고하세요.


설치하기

1
$ npm install --save @react-native-community/async-storage


데이터 100건 저장하기

AsyncStorage에는 String만 저장가능합니다. 따라서 Object를 저장할 수 없습니다. 그래서 JSON Object를 String으로 변환하여 저장합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
_insertAll = () =>
this.setState({ loading: true }, async () => {

// 데이터 100건 가져오기
const data = await this._getDiscussionsByBlog()
.then(r => r.map(({
post_id,
permlink,
author,
title,
body
}) => ({post_id, permlink, author, title, body})));

const startTime = Date.now(); // 시작 시간
await AsyncStorage.setItem("DATA", JSON.stringify(data)); // AsyncStorage에 저장
const elapsedTime = Date.now() - startTime; // 경과 시간

this.setState({ loading: false, elapsedTime });
})

데이터 100건을 저장하는데 평균 1.8996초가 소요되었습니다.
10번 테스트하여 평균한 값입니다. 그리고 가장 높은/낮은값은 제외하였습니다.



전체 데이터 가져오기

AsyncStorage에서 전체 데이터를 가져옵니다. 그리고 다시 JSON parse하여 JSON Object로 변환하였습니다.

1
2
3
4
5
6
7
8
9
10
11
_selectAll = () =>
this.setState({ loading: true }, async () => {

const startTime = Date.now(); // 시작 시간
const data = JSON.parse(await AsyncStorage.getItem("DATA"));
const elapsedTime = Date.now() - startTime; // 경과 시간
console.log(data);

this.setState({ loading: false, elapsedTime });
})

데이터 전체를 조회는데 평균 0.0982가 소요되었습니다.



하나의 데이터 가져오기

AsyncStorage는 한 건을 조회하기 위해서도 모든 데이터를 가져와야 합니다. 그래서 전체 Select와 차이가 없었습니다. 성능을 높이기 위해서는 Array와 각 Item을 따로 저장해서 관리하면 될 것 같습니다.

1
2
3
4
5
6
7
8
9
10
11
_selectOne = () =>
this.setState({ loading: true }, async () => {

const startTime = Date.now(); // 시작 시간
const data = JSON.parse(await AsyncStorage.getItem("DATA")).filter(r => r.post_id === 67714463);
const elapsedTime = Date.now() - startTime; // 경과 시간
console.log(data);

this.setState({ loading: false, elapsedTime });
})

데이터를 전체를 가져와서 한 건을 조회하는데 평균 0.0974가 소요되었습니다.


* * *

SQLite

SQLite를 사용하기 위해서 react-native-sqlite-storage를 설치합니다. 자세한 내용은 공식 문서를 참고하세요.


설치하기

1
2
$ npm install --save react-native-sqlite-storage
$ react-native link

SQLite 이슈나 SQL 사용 방법은 다음 사이트를 참고하세요.
- https://github.com/xpbrew/cordova-sqlite-storage



데이터베이스 및 테이블 생성하기

componentDidMount() 함수에서는 데이터베이스를 오픈합니다. _createDatabase() 함수에서 테이블을 생성합니다. 그리고 테스트를 위해서 테이블이 생성되어 있으면 드롭(drop)하고 다시 생성하도록 하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 테이블 생성
_createDatabase = async () => {
const result = await this.state.db.sqlBatch([
`DROP TABLE IF EXISTS TB_DISCUSSIONS`,
`CREATE TABLE IF NOT EXISTS TB_DISCUSSIONS (
post_id INTEGER PRIMARY KEY,
permlink TEXT,
author TEXT,
title TEXT,
body TEXT
)`
]);


// 데이터베이스 오픈
componentDidMount = async () => {
const db = await SQLite.openDatabase({ name: 'testDB' });
this.setState({
loading: false,
db,
}, () => this._createDatabase());



데이터 100건 Insert 하기

SQLite.sqlBatch를 사용하여 데이터 100건을 한번에 insert 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
_insertAll = () =>
this.setState({ loading: true }, async () => {
const insertSQLs = await this._getDiscussionsByBlog()
.then(r => r.map((
post_id,
permlink,
author,
title,
body
) => [
'INSERT INTO TB_DISCUSSIONS VALUES (?1,?2,?3,?4,?5)',
[post_id, permlink, author, title, body]
]));

const startTime = Date.now(); // 시작 시간
const result = await this.state.db.sqlBatch(insertSQLs);
const elapsedTime = Date.now() - startTime; // 경과 시간

this.setState({ loading: false, elapsedTime });
})

데이터 100건을 저장하는데 평균 3.9656초가 소요되었습니다.



데이터 전체 Select 하기

데이터를 모두 select 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
_selectAll() {
this.setState({ loading: true }, async () => {

const startTime = Date.now(); // 시작 시간
this.state.db.executeSql('SELECT * FROM TB_DISCUSSIONS', [],
(rs) =>
const elapsedTime = Date.now() - startTime; // 경과 시간
console.log(rs.rows)
this.setState({ loading: false, elapsedTime });
},
(err) => console.log(err)
);
})

데이터 전체를 조회하는데 평균 0.1086가 소요되었습니다.



데이터 한 건 Select 하기

데이터 한 건을 select 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_selectOne() {
this.setState({ loading: true }, async () => {

const startTime = Date.now(); // 시작 시간
this.state.db.executeSql('SELECT * FROM TB_DISCUSSIONS WHERE post_id = (?1)',
[67714463],
(rs) =>
const elapsedTime = Date.now() - startTime; // 경과 시간
console.log(rs.rows.item(0));
this.setState({ loading: false, elapsedTime });
},
(err) => console.log(err)
);
})

데이터 전체를 조회하는데 평균 0.0072가 소요되었습니다.


* * *

Realm

https://realm.io/
Realm 모바일 사용에 최적화된 내장 데이터베이스 라이브러리입니다. 한글 문서화가 굉장히 잘 되어 있는 오픈 소스입니다. 그리고 Realm 홈페이지에 가보면 속도가 엄청 빠르다고 자랑하고 있습니다. 다음 차트는 초당 쿼리수 입니다.

출처: realm.io


설치하기

1
2
npm install --save realm
react-native link realm


스키마 생성하기

Realm 데이터 모델을 초기화하기 위해서는 스키마를 정의해야 합니다. SQLite에서 생성한 테이블 구조와 유사한 형태로 스키마를 정의하고 생성하였습니다. 스키마 모델과 관련하여 자세한 내용은 공식 문서를 참고하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
componentWillMount() {
Realm.open({
schema: [{
name: 'discussions',
primaryKey: 'post_id',
properties: {
post_id: 'int',
permlink: 'string',
author: 'string',
title: 'string',
body: 'string'
},
}]
}).then(realm => {
this.setState({
loading: false,
realm
});
});



데이터 100건 Insert 하기

데이터 100건을 insert 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
_insertAll = () =>
this.setState({ loading: true }, async () => {
const dataArray = await this._getDiscussionsByBlog()
.then(r => r.map((
post_id,
permlink,
author,
title,
body
) => ({post_id, permlink, author, title, body})));

const { realm } = this.state;
const startTime = Date.now(); // 시작 시간

try
realm.write(() =>
dataArray.forEach(data => realm.create('discussions', data))
});
catch (err) {
console.log(err);


const elapsedTime = Date.now() - this.state.startTime; // 경과 시간
this.setState({ loading: false, elapsedTime });
})

데이터 100건을 저장하는데 평균 2.9978초가 소요되었습니다.



데이터 전체 Select 하기

데이터를 모두 select 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
_selectAll = () =>
this.setState({ loading: true }, async () => {

let data;
const startTime = Date.now(); // 시작 시간
try
data = this.state.realm.objects('discussions');
catch (error) {
console.log(error);


const elapsedTime = Date.now() - this.state.startTime; // 경과 시간
console.log(Array.from(data));

this.setState({ loading: false, elapsedTime });
})

데이터 전체를 조회하는데 평균 0.79가 소요되었습니다.



데이터 한 건 Select 하기

데이터 한 건을 select 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
_selectOne = () =>
this.setState({ loading: true }, async () => {

let data;
const startTime = Date.now(); // 시작 시간

try
data = this.state.realm.objects('discussions').filtered('post_id = 67714463');
catch (error) {
console.log(error);


const elapsedTime = Date.now() - this.state.startTime; // 경과 시간
console.log(Array.from(data));

this.setState({ loading: false, elapsedTime });
})

데이터 한 건을 조회하는데 평균 0.0314가 소요되었습니다.



비교하기 쉽게 테스트 결과를 차트로 그려보았습니다. 세로축은 경과시간(ms)입니다.

chart 2.png

100건의 데이터로 테스트한 결과라서 큰 차이는 없습니다. 데이터 저장의 경우 AsyncStorage가 가장 성능이 좋습니다. 그다음은 Realm입니다. 그리고 전체 데이터를 가져오는 경우에는 SQLite, AsyncStorage 순으로 성능이 좋습니다. 하지만 한 건의 데이터를 조회하는 경우에는 SQLite이 가장 성능이 좋습니다.


* * *

realm에서는 rawSQL을 사용할 수 없습니다. realm에서 제공하는 함수형 API로만 쿼리할 수 있습니다. 그래서 realm를 사용하려면 학습 비용이 발생하네요.ㅠㅠ

간단한 데이터를 저장하고 가져오는 기능을 구현하기 위해서는, AsyncStorage를 사용하는 것이 가장 좋은 선택일 것 같습니다.

그리고 Realm와 SQLite를 더 자세하게 비교한 블로그가 있어 링크를 공유합니다.


여기까지 읽어주셔서 감사합니다.