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

in kr •  6 years ago  (edited)


Design by @imrahelk


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

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



* * *


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

  // 스팀잇 글 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 설치하여 사용하는 것을 권장합니다. 자세한 내용은 공식 문서를 참고하세요.


설치하기

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




데이터 100건 저장하기

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

  _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로 변환하였습니다.

  _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을 따로 저장해서 관리하면 될 것 같습니다.

  _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를 설치합니다. 자세한 내용은 공식 문서를 참고하세요.


설치하기

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

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




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

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

  // 테이블 생성
  _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 합니다.

  _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 합니다.

  _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 합니다.

  _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


설치하기

npm install --save realm
react-native link realm




스키마 생성하기

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

  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 합니다.

  _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 합니다.

  _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 합니다.

  _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를 더 자세하게 비교한 블로그가 있어 링크를 공유합니다.



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


WHAN DEV TEAM

[출범식] WDT(WHAN DEV TEAM) 공식 활동 개시



Sponsored ( Powered by dclick )

dclick-imagead

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

!dramatoken

앗 코딩에도 드라마틱 ㅋㅋㅋ

드라마토큰 감사합니다.

오늘도 수고 많으셨어요~
행복한 ♥ 오늘 보내셔용~^^

Posted using Partiko Android

감사합니드앙~! 오늘도 행복한 하루 되세요

SQLite를 선호합니다. ^^

저도 SQLite를 선호합니다. 개발하기 편한게 우선이고 성능은 나중이죠. ㅋ

아놬 ㅋㅋㅋㅋㅋㅋㅋㅋㅋ


You have DRAMA!

To view or trade DRAMA go to steem-engine.com.

리액트 네이티브에서, 안드로이드 앱 개발 셋팅하면 항상 이런 에러가 시뻘겋게 뜨는데요. 그냥 QR로 하긴 합니다만. 엄청 스트레스네요. 혹시 이거 어떻게 잡는지 아세요? ㅇ_ㅇ Couldn't start project on Android: Error running adb: No Android device found. Please connect a device and follow the instructions here to enable USB debugging:
https://developer.android.com/studio/run/device.html#developer-device-options. If you are using Genymotion go to Settings -> ADB, select "Use custom Android SDK tools", and point it at your Android SDK directory.

안드로이드 디바이스를 인식 못하네요.
혹시 윈도우 환경인가요? 그럼 디바이스 제조사 홈페이지에서 USB 드라이버를 설치해줘야합니다.
USB 드라이버가 설치되어 있는데 안잡힌다면, adb kill-server 하고 다시 연결해보세요.

참고로 맥북은 USB 드라이버를 따로 설치하지 않아도 잘됩니다. 맥북이 진리입니다.

사실은 저도 이게 귀찮아서 expo로 개발합니다.
expo는 wifi만 연결하면 원격에서도 확인 가능하니 정말 편합니다.ㅋ

헐 그렇군요. ㅎㅎㅎ USB 드라이버가 없단 얘기였군요. ㅇㅇ;;; 다시 핸드폰 충전 USB를 연결하니깐, 에러가 없어집니다. ㅇㅇ;;;; 몰랐네요.

PC에? USB 드라이버를 이미 설치했는지 아는 건 뭘로 볼 수 있나요? ㅇ_ㅇ 설치했을 것 같긴 한데요.

제어판 장치관리자에 가면 볼수 있어요. 알수없는장치가 보이면 드라이버가 없는거죠. ㅋ

알 수 없는 장치는 없는데요. 이 중에 뭐죠? ㅇ_ㅇ?
12.jpg

범용 직렬 버스 컨트롤러에 디바이스명이 보이지 않나요?
그리고 참고로 USB 케이블 중에 데이터 전송과 충전이 둘다 되는 케이블이 있고, 충전만 되는 케이블도 있어요.
충전만 가능한 케이블은 디바이스 USB 연결이 불가능해요.

아 USB니깐 당연한 거네요. ㅋㅋㅋ ^^ 연결하면 나오는 거군요. ㅎㅎㅎ 이제 뭔가 감이 잡히네요. ㅎㅎㅎ 감사합니다. ㅎㅎㅎㅎㅎ

지금은 읽어도 잘모르겠지만 ㅠㅠ 이전 글도 먼가 흥미로운 글들이 많은 것 같아요! 틈틈히 배우고 가겠습니다. 좋은 글 감사합니다!~

저도 블로그를 보면서 틈틈히 배우겠습니다. 앞으로도 재미난 개발글 부탁드려요. ㅎㅎ

곰돌이가 @anpigon님의 소중한 댓글에 $0.008을 보팅해서 $0.015을 살려드리고 가요. 곰돌이가 지금까지 총 4308번 $49.466을 보팅해서 $54.301을 구했습니다. @gomdory 곰도뤼~


@anpigon님 넘치는 사랑 감사합니다~

Hi @anpigon!

Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your UA account score is currently 2.975 which ranks you at #10686 across all Steem accounts.
Your rank has improved 662 places in the last three days (old rank 11348).

In our last Algorithmic Curation Round, consisting of 339 contributions, your post is ranked at #214.

Evaluation of your UA score:
  • Only a few people are following you, try to convince more people with good work.
  • The readers like your work!
  • Good user engagement!

Feel free to join our @steem-ua Discord server

감사합니다