[기본으로 돌아가기] WEB STORAGE

shorry·2023년 6월 29일
0

Back to the Basic

목록 보기
1/1

로컬 스토리지, 세션 스토리지 및 쿠키는 모두 최신 브라우저에서 사용할 수 있는 웹 스토리지 메커니즘이지만 목적, 수명 및 강점이 다릅니다. 다음은 이 세 가지 스토리지 옵션을 비교한 것입니다.

공통점:

  1. 세 가지 옵션 모두 서버 측이 아닌 클라이언트 측(사용자 브라우저)에 데이터를 저장합니다.
  2. JavaScript를 통해 액세스할 수 있으며 페이지 로드 또는 세션 간에 데이터를 저장하는 데 사용할 수 있습니다.
  3. 저장할 수 있는 데이터의 양(일반적으로 몇 메가바이트)에 제한이 있습니다.

차이점:

  1. 수명 및 지속성:
    • 쿠키: 쿠키에는 만료일이 있으며 영구적(브라우저가 닫힌 후에도 유지됨) 또는 세션 기반(브라우저 세션이 종료되면 삭제됨)일 수 있습니다.
    • 로컬 저장소: 로컬 저장소에 저장된 데이터는 브라우저를 닫은 후에도 지속되며 후속 세션에서 검색할 수 있습니다.
    • 세션 저장소: 세션 저장소는 로컬 저장소와 유사하지만 데이터는 현재 브라우징 세션 동안만 사용할 수 있습니다. 세션이 종료되면 데이터가 지워집니다.
  2. 범위 및 접근성:
    • 쿠키: 쿠키는 주로 서버와의 통신에 사용됩니다. 이들은 자신이 속한 도메인에 대한 모든 HTTP 요청과 함께 전송되며 서버 및 클라이언트 측 JavaScript 모두에서 액세스할 수 있습니다.
    • 로컬 저장소: 로컬 저장소는 도메인에 따라 다르며 해당 도메인 내의 모든 페이지에서 클라이언트 측 JavaScript로 액세스할 수 있습니다.
    • 세션 저장소: 로컬 저장소와 마찬가지로 세션 저장소도 도메인에 따라 다릅니다. 그러나 데이터는 현재 세션으로 제한되며 여러 브라우저 탭이나 창에서 공유되지 않습니다.

강점과 약점:

  1. 쿠키:
    • 강점: 쿠키는 광범위하게 지원되고 다양한 브라우저 세션에서 작동하며 서버측 및 클라이언트측 코드 모두에서 액세스할 수 있습니다.
    • 약점: 쿠키는 작은 저장 용량(일반적으로 몇 킬로바이트)을 가지며 모든 HTTP 요청과 함께 쿠키를 전송하면 성능에 영향을 미칠 수 있습니다. 또한 XSS(교차 사이트 스크립팅) 공격과 같은 보안 위험에 취약할 수 있습니다.
  2. 로컬 저장소:
    • 장점: 로컬 저장소는 쿠키에 비해 더 큰 저장 용량(수 메가바이트)을 제공합니다. 사용하기 쉽고 클라이언트 측 JavaScript에서 효율적으로 액세스할 수 있습니다.
    • 단점: 로컬 저장소가 특정 도메인으로 제한되며 쿠키와 같이 HTTP 요청마다 데이터가 자동으로 전송되지 않습니다. 서버 측 코드에 액세스할 수 없으므로 서버 측 상호 작용이 필요한 시나리오에는 적합하지 않을 수 있습니다.
  3. 세션 저장:
    • 장점: 세션 저장소는 로컬 저장소와 비슷한 용량을 제공하며 동일한 세션 내에서 액세스할 수 있습니다. 현재 세션 이상으로 필요하지 않은 임시 데이터를 저장하는 방법을 제공합니다.
    • 약점: 로컬 저장소와 마찬가지로 세션 저장소는 특정 도메인으로 제한되며 서버 측 코드에 액세스할 수 없습니다. 또한 다른 브라우저 탭이나 창 간에 공유되지 않습니다.

요약하면,
쿠키는 주로 서버와의 통신에 사용된다.
로컬 저장소 및 세션 저장소는 클라이언트측 데이터 저장소용으로 설계되었다.
로컬 스토리지는 세션 간에 지속되는 반면 세션 스토리지는 일시적이다.
로컬 저장소와 세션 저장소 모두 쿠키보다 더 많은 용량을 제공하지만 접근성과 서버 측 상호 작용에 제한이 있다.
저장 메커니즘의 선택은 응용 프로그램 또는 웹 사이트의 특정 요구 사항에 따라 다다.



23.12.20 추가

Indexed DB

Web Storage 는 적은 양의 데이터를 저장하는데 유용하지만 많은 양의 구조화된 데이터에는 적합하지 않은데, 이런 상황에서 IndexedDB 를 사용할 수 있습니다.

브라우저에 내장된 데이터베이스로 거의 모든 타입의 값을 키에 저장할 수 있으며 색인을 사용하여 데이터에 대한 고성능 검색을 지원합니다.

주요 개념

IndexedDB 작업은 애플리케이션 블록을 방지하기 위해 모두 비동기로 이뤄집니다.

Same-Origin-Policy 를 따르며 온라인 오프라인 환경 모두에서 쿼리를 지원합니다.

Database

  • 이름과 버전 그리고 N개의 Object Store 를 가집니다.
  • 브라우저는 여러 Database 를 가질 수 있습니다.

Object Store

  • 데이터를 저장할 개별 버킷입니다. N개의 레코드(Key-Value) 를 가질 수 있습니다.
  • 기존 관계형 데이터베이스의 테이블과 유사합니다.

Transaction

  • IndexedDB API 작업은 transaction contect 내에서 발생합니다.
  • transaction contect 내에서 작업이 실패하면, 해당 작업 상태는 적용되지 않고, 이전 상태로 돌아갑니다.
💡 IndexedDB가 권장하는 기본 패턴은 다음과 같습니다:
  1. 데이터베이스를 엽니다.
  2. 객체 저장소(Object store)를 생성합니다.
  3. 트랜젝션(Transaction)을 시작하고, 데이터를 추가하거나 읽어들이는 등의 데이터베이스 작업을 요청합니다.
  4. DOM 이벤트 리스너를 사용하여 요청이 완료될때까지 기다립니다.
  5. (요청 객체에서 찾을 수 있는) 결과를 가지고 무언가를 합니다.

참고


indexedDB.ts

  • path: src/asset/util/indexedDB.ts
  • IndexedDB를 래핑한 클래스 입니다.

Validation

protected getIsSupportIndexedDB() {
    return 'indexedDB' in window;
  }

브라우저가 indexedDB를 지원하는지 확인합니다.

Open

protected open(dbName: string) {
    return new Promise<IDBDatabase>((res, rej) => {
      const validateIndexedDB = this.getIsSupportIndexedDB();

      if (validateIndexedDB) {
        const openRequest = indexedDB.open(dbName, INDEXED_DB_VERSION);

        openRequest.onsuccess = () => {
          res(openRequest.result);
        };
        openRequest.onerror = () => {
          this.handleError?.();
          rej(openRequest.error);
        };

        openRequest.onupgradeneeded = () => {
          const db = openRequest.result;
          if (!db.objectStoreNames.contains(this.objectStoreName)) {
            this.createObjectStore(db);
          }
        };
      } else {
        this.handleError?.();
      }
    });
  }

IndexedDB 패턴의 시작인 데이터베이스 접속을 요청하는 메소드 입니다.

  • indexedDB.open(name, version)
    • 첫번째 매개 변수는 데이터베이스 이름, 두번째 매개 변수는 데이터베이스의 버전 입니다.
    • 버전은 1 이상의 정수만 가능합니다.
  • openRequest.onsuccess
    • 오픈 요청이 성공하면 실행되는 콜백 함수 입니다.
    • 성공하면 DB API를 반환합니다.
  • openRequest.onupgradeneeded
    • 새로운 데이터베이스를 만들거나 기존 데이터베이스의 버전 번호를 높일 때 onupgradeneeded 가 트리거됩니다.
    • upgradeneeded 콜백에서 이 버전의 데이터베이스에 필요한 객체 저장소를 만들어야합니다.

ObjectStore

protected getObjectStore(mode?: IDBTransactionMode, options?: IDBTransactionOptions) {
    return new Promise<IDBObjectStore>((res, rej) => {
      this.validateDBOpened()
        ? this.dbPromise.then(db => {
            if (db.objectStoreNames.contains(this.objectStoreName)) {
              const transaction = db.transaction(this.objectStoreName, mode, options);
              const store = transaction.objectStore(this.objectStoreName);
              res(store);
            }
          })
        : rej();
    });
  }
  • 데이터베이스에 지정된 objectStoreName이 존재하는지 확인합니다.
  • modeoptions 매개변수를 사용하여 IndexedDB 트랜잭션을 시작하고, 해당 트랜잭션에서 작동하는 IDBObjectStore를 반환합니다.

CRUD

protected get<T>(query: string) {
    return new Promise<T>((res, rej) => {
      this.getObjectStore('readonly').then(store => {
        const getReq = store.get(query);
        getReq.onsuccess = () => res(getReq.result);
        getReq.onerror = () => rej(getReq.error);
      });
    });
  }

  protected set(query: string, value) {
    return new Promise((res, rej) => {
      this.getObjectStore('readwrite').then(store => {
        const setReq = store.put(value, query);
        setReq.onsuccess = () => res(setReq.result);
        setReq.onerror = () => rej(setReq.error);
      });
    });
  }

  protected remove(query: string) {
    return new Promise((res, rej) => {
      this.getObjectStore('readwrite').then(store => {
        const deleteReq = store.delete(query);
        deleteReq.onsuccess = () => res(deleteReq.result);
        deleteReq.onerror = () => rej(deleteReq.error);
      });
    });
  }

데이터베이스의 트랜잭션을 시작하여 CRUD를 비동기적으로 처리하였습니다.

참고


ApplicationDraftDB

  • path: src/util/applicationForm/applicationForm.class.ts
  • ApplicationDraftDB 클래스는 IndexedDB를 상속하고 있습니다.

Initialize

class ApplicationDraftDB extends IndexedDB {
  private debounceTimer: NodeJS.Timeout;
  constructor(props: ApplicationDraftDBConstructor) {
    const { onError, recruitmentId } = props;

    super({ objectStoreName: APPLICATION_DRAFT_DB_NAME, onError });
    this.init(getDraftDBName(recruitmentId));
  }
}
  • recruitmentId 를 이름으로 데이터베이스를 오픈합니다.
  • 에러 핸들러 onError를 바인딩 합니다.

Key format

private getAnswerKey({ type, title, description }: AnswerKey) {
    let key = `${type}-${title}`;
    if (description) {
      key += `-${description}`;
    }
    return key;
  }
  • 지원서 답변 항목의 Key 입니다.
  • type , title 로 조합하며 description 이 존재한다면 추가합니다.

Debounce Setter

private setDebounce(query: string, value: ApplicationInputAnswer[]) {
    clearTimeout(this.debounceTimer);

    this.debounceTimer = setTimeout(() => {
      this.set(query, value);
      this.setEditedAt();
    }, 300);
  }
  • 입력값에 대한 디바운싱을 구현하고, 지연된 시간 이후에 지정된 작업을 수행합니다.
  • 0.3초 동안 입력이 없을 때에 한 번에 저장하는 방식입니다.

CRUD

setAnswer(keyReq: AnswerKey, value: ApplicationInputAnswer[]) {
    const key = this.getAnswerKey(keyReq);

    this.setDebounce(key, value);
  }

  getAnswer<T>(keyReq: AnswerKey) {
    const key = this.getAnswerKey(keyReq);

    return this.get<T>(key);
  }

  clearAnsers() {
    this.clearObjectStore();
  }

추후 개선 사항

당장 기능 구현에는 문제가 없지만 최적화와 유지보수를 위해서 개선 사항을 정리하였습니다.

버전 관리

웹앱의 버전과 싱크를 맞추어 관리한다면 버전 트랙킹에 도움이 될 것 입니다.

검색 효율 개선

현재 별도의 인덱스를 생성하지 않았고, 또한 외부 키 기반의 key-value 데이터 접근 방식으로 인터페이스를 구축하였습니다. 향후 코드 구조 개선과 함께 Index 및 Cursor API 를 적용한다면 데이터 검색 효율을 개선될 것 입니다.

래퍼 라이브러리

기능이 고도화 되거나 유지보수에 어려움을 느낀다면 래퍼 라이브러리를 고려해볼 필요가 있습니다.

MDN 추천 목록

  • localForage: Polyfill은 백그라운드에서 IndexedDB를 사용하는 클라이언트 측 데이터 저장을 위한 간단한 이름:값 구문을 제공합니다. 그러나 IndexedDB를 지원하지 않는 브라우저에서는 WebSQL로 폴백한 다음 localStorage로 폴백합니다.
  • Dexie.js: 멋지고 간단한 구문을 통해 훨씬 더 빠른 코드 개발을 허용하는 IndexedDB용 래퍼입니다
  • ZangoDB: MongoDB의 익숙한 필터링, 프로젝션, 정렬, 업데이트 및 집계 기능을 대부분 지원하는 MongoDB와 유사한 IndexedDB용 인터페이스입니다.
  • JsStore: SQL과 같은 구문을 사용하는 IndexedDB 래퍼입니다.
  • MiniMongo: http를 통한 서버 동기화와 함께 localstorage에서 지원하는 클라이언트 측 in-memory mongodb입니다. MiniMongo는 MeteorJS에서 사용됩니다.
  • PouchDB: IndexedDB를 사용하여 브라우저에서 CouchDB의 클라이언트 측을 구현한 라이브러리입니다.
  • idb: IndexedDB API를 대부분 미러링하는 작은(~1.15k) 라이브러리이지만 사용성에 큰 차이를 만드는 작은 개선 사항이 있습니다.
  • idb-keyval: IndexedDB로 구현된 매우 간단하고 작은(~600B) promise 기반 keyval 저장소입니다.
  • sifrr-storage: 클라이언트 측 키-값 저장을 위한 작은(~2kB) promise 기반 라이브러리입니다. IndexedDB, localStorage, WebSQL, 쿠키와 함께 작동합니다. 우선 순위에 따라 사용 가능한 지원 스토리지를 자동으로 사용할 수 있습니다.
  • lovefield: Lovefield는 웹 앱을 위한 관계형 데이터베이스입니다. JavaScript로 작성되었으며 크로스 브라우저에서 작동합니다. 빠르고 안전하며 사용하기 쉬운 SQL과 유사한 API를 제공합니다.
  • $mol_db: 작고 (~1.3kB) 정적 타입 유형의 promise 기반 API와 자동 마이그레이션의 기능이 있는 라이브러리입니다.
profile
I'm SHORRY about that

0개의 댓글