앱을 위한 고성능 스토리지: Storage Foundation API

웹 플랫폼은 개발자에게 웹용으로 미세 조정된 고성능 애플리케이션을 빌드하는 데 필요한 도구를 점점 더 많이 제공하고 있습니다. 특히 WebAssembly (Wasm)는 빠르고 강력한 웹 애플리케이션의 문을 열었고, 이제 Emscripten과 같은 기술을 통해 개발자는 웹에서 검증된 코드를 재사용할 수 있습니다. 이러한 잠재력을 제대로 활용하려면 개발자가 스토리지에 관해서도 동일한 성능과 유연성을 가져야 합니다.

이때 Storage Foundation API가 유용합니다. Storage Foundation API는 빠르고 중립적인 새로운 스토리지 API로, 성능이 우수한 데이터베이스 구현, 대용량 임시 파일의 원활한 관리와 같이 웹에서 요청이 많은 새로운 사용 사례를 제공합니다. 이 새로운 인터페이스를 사용하면 개발자가 '자체 저장소'를 웹에 가져와 웹과 플랫폼별 코드 간의 기능 격차를 줄일 수 있습니다.

Storage Foundation API는 매우 기본적인 파일 시스템과 유사하게 설계되어 개발자가 고급 구성요소를 빌드할 수 있는 일반적이고 간단하며 성능이 우수한 프리미티브를 제공하여 유연성을 제공합니다. 애플리케이션은 필요에 맞는 최적의 도구를 활용하여 사용성, 성능, 안정성 간에 적절한 균형을 찾을 수 있습니다.

웹에 다른 Storage API가 필요한 이유는 무엇인가요?

웹 플랫폼은 개발자에게 여러 스토리지 옵션을 제공하며, 각 옵션은 특정 사용 사례를 염두에 두고 빌드됩니다.

  • 이러한 옵션 중 일부는 쿠키sessionStoragelocalStorage 메커니즘으로 구성된 Web Storage API와 같이 아주 소량의 데이터만 저장할 수 있으므로 이 제안서와 명확하게 겹치지 않습니다.
  • File and Directory Entries API 또는 WebSQL과 같은 다른 옵션은 다양한 이유로 이미 지원 중단되었습니다.
  • File System Access API는 유사한 API 노출 영역을 가지고 있지만, 클라이언트의 파일 시스템과 상호작용하고 출처 또는 브라우저 소유권 외부에 있을 수 있는 데이터에 대한 액세스를 제공하는 데 사용됩니다. 이러한 차별점은 보다 엄격한 보안 고려사항과 높은 성능 비용을 수반합니다.
  • IndexedDB API는 일부 Storage Foundation API 사용 사례의 백엔드로 사용할 수 있습니다. 예를 들어 Emscripten에는 IndexedDB 기반의 영구 파일 시스템인 IDBFS가 포함되어 있습니다. 하지만 IndexedDB는 기본적으로 키-값 저장소이므로 상당한 성능 제한이 있습니다. 또한 IndexedDB에서는 파일의 하위 섹션에 직접 액세스하는 것이 훨씬 더 어렵고 더 느립니다.
  • 마지막으로 CacheStorage 인터페이스는 광범위하게 지원되며 웹 애플리케이션 리소스와 같은 대용량 데이터를 저장하도록 조정되지만 값은 변경할 수 없습니다.

Storage Foundation API는 애플리케이션 원본 내에 정의된 변경 가능한 대용량 파일의 성능 향상을 허용하여 이전 스토리지 옵션의 모든 격차를 메우기 위한 것입니다.

Storage Foundation API의 추천 사용 사례

이 API를 사용할 수 있는 사이트의 예는 다음과 같습니다.

  • 대용량 동영상, 오디오 또는 이미지 데이터에서 작동하는 생산성 또는 창의성 앱 이러한 앱은 세그먼트를 메모리에 보관하는 대신 디스크로 오프로드할 수 있습니다.
  • Wasm에서 액세스할 수 있는 영구 파일 시스템을 사용하고 IDBFS에서 보장할 수 있는 것보다 더 많은 성능이 필요한 앱

Storage Foundation API란 무엇인가요?

API에는 두 가지 주요 부분이 있습니다.

  • 파일 및 파일 경로와 상호작용하는 기본 기능을 제공하는 파일 시스템 호출
  • 파일 핸들: 기존 파일에 대한 읽기 및 쓰기 액세스 권한을 제공합니다.

파일 시스템 호출

Storage Foundation API는 window 객체에 있고 여러 함수를 포함하는 새 객체 storageFoundation를 도입합니다.

  • storageFoundation.open(name): 지정된 이름의 파일이 있으면 열고, 그렇지 않으면 새 파일을 만듭니다. 열린 파일로 확인되는 프로미스를 반환합니다.
  • storageFoundation.delete(name): 지정된 이름의 파일을 삭제합니다. 파일이 삭제될 때 확인되는 프로미스를 반환합니다.
  • storageFoundation.rename(oldName, newName): 파일 이름을 이전 이름에서 새 이름으로 원자적으로 바꿉니다. 파일 이름이 변경될 때 확인하는 프라미스를 반환합니다.
  • storageFoundation.getAll(): 모든 기존 파일 이름의 배열로 확인되는 프로미스를 반환합니다.
  • storageFoundation.requestCapacity(requestedCapacity): 현재 실행 컨텍스트에서 사용할 새 용량 (바이트)을 요청합니다. 사용 가능한 남은 용량으로 확인되는 프로미스를 반환합니다.
  • storageFoundation.releaseCapacity(toBeReleasedCapacity): 현재 실행 컨텍스트에서 지정된 바이트 수를 해제하고 남은 용량으로 확인되는 프로미스를 반환합니다.
  • storageFoundation.getRemainingCapacity(): 현재 실행 컨텍스트에 사용할 수 있는 용량으로 확인되는 프라미스를 반환합니다.

파일 핸들

파일 작업은 다음 함수를 통해 이루어집니다.

  • NativeIOFile.close(): 파일을 닫고 작업이 완료될 때 확인되는 약속을 반환합니다.
  • NativeIOFile.flush(): 파일의 메모리 내 상태를 저장소 기기와 동기화 (즉, 플러시)하고 작업이 완료될 때 확인되는 프로미스를 반환합니다.
  • NativeIOFile.getLength(): 파일 길이(바이트)로 확인되는 프로미스를 반환합니다.
  • NativeIOFile.setLength(length): 파일 길이(바이트)를 설정하고 작업이 완료되면 확인되는 프로미스를 반환합니다. 새 길이가 현재 길이보다 작으면 파일의 끝부터 바이트가 삭제됩니다. 그렇지 않으면 파일이 0 값 바이트로 확장됩니다.
  • NativeIOFile.read(buffer, offset): 지정된 버퍼를 전송한 결과인 버퍼를 통해 지정된 오프셋의 파일 콘텐츠를 읽고 분리된 상태로 둡니다. 전송된 버퍼와 성공적으로 읽은 바이트 수가 포함된 NativeIOReadResult를 반환합니다.

    NativeIOReadResult는 두 항목으로 구성된 객체입니다.

    • buffer: read()에 전달된 버퍼를 전송한 결과인 ArrayBufferView입니다. 소스 버퍼와 동일한 유형과 길이입니다.
    • readBytes: buffer에 성공적으로 읽힌 바이트 수입니다. 오류가 발생하거나 읽기 범위가 파일의 끝을 넘는 경우에는 버퍼 크기보다 작을 수 있습니다. 읽기 범위가 파일 끝을 지나는 경우 0으로 설정됩니다.
  • NativeIOFile.write(buffer, offset): 지정된 버퍼의 콘텐츠를 지정된 오프셋에서 파일에 씁니다. 버퍼는 데이터가 쓰여지기 전에 전송되므로 분리된 상태로 유지됩니다. 전송된 버퍼와 성공적으로 쓴 바이트 수가 포함된 NativeIOWriteResult를 반환합니다. 쓰기 범위가 길이를 초과하면 파일이 연장됩니다.

    NativeIOWriteResult는 두 항목으로 구성된 객체입니다.

    • buffer: write()에 전달된 버퍼를 전송한 결과인 ArrayBufferView입니다. 소스 버퍼와 동일한 유형과 길이입니다.
    • writtenBytes: buffer에 성공적으로 쓴 바이트 수입니다. 오류가 발생하면 버퍼 크기보다 작을 수 있습니다.

완성형 예시

위에 소개된 개념을 더 명확하게 이해할 수 있도록 다음은 Storage Foundation 파일 수명 주기의 여러 단계를 안내하는 두 가지 완성된 예입니다.

시작, 작성, 읽기, 종료

// Open a file (creating it if needed).
const file = await storageFoundation.open('test_file');
try {
  // Request 100 bytes of capacity for this context.
  await storageFoundation.requestCapacity(100);

  const writeBuffer = new Uint8Array([64, 65, 66]);
  // Write the buffer at offset 0. After this operation, `result.buffer`
  // contains the transferred buffer and `result.writtenBytes` is 3,
  // the number of bytes written. `writeBuffer` is left detached.
  let result = await file.write(writeBuffer, 0);

  const readBuffer = new Uint8Array(3);
  // Read at offset 1. `result.buffer` contains the transferred buffer,
  // `result.readBytes` is 2, the number of bytes read. `readBuffer` is left
  // detached.
  result = await file.read(readBuffer, 1);
  // `Uint8Array(3) [65, 66, 0]`
  console.log(result.buffer);
} finally {
  file.close();
}

열기, 나열, 삭제

// Open three different files (creating them if needed).
await storageFoundation.open('sunrise');
await storageFoundation.open('noon');
await storageFoundation.open('sunset');
// List all existing files.
// `["sunset", "sunrise", "noon"]`
await storageFoundation.getAll();
// Delete one of the three files.
await storageFoundation.delete('noon');
// List all remaining existing files.
// `["sunrise", "noon"]`
await storageFoundation.getAll();

데모

아래 삽입에서 Storage Foundation API 데모를 사용해 볼 수 있습니다. 파일을 만들고, 이름을 바꾸고, 파일에 쓰고, 읽기를 수행하고, 변경 시 업데이트를 요청한 가용 용량을 확인할 수 있습니다. Glitch에서 데모의 소스 코드를 확인할 수 있습니다.

보안 및 권한

Chromium팀은 사용자 제어, 투명성, 인체공학을 포함하여 강력한 웹 플랫폼 기능에 대한 액세스 제어에 정의된 핵심 원칙에 따라 Storage Foundation API를 설계하고 구현했습니다.

웹의 다른 최신 스토리지 API와 동일한 패턴을 따르는 Storage Foundation API에 대한 액세스는 출처에 연결되어 있습니다. 즉, 출처는 자체 생성된 데이터에만 액세스할 수 있습니다. 또한 안전한 컨텍스트로 제한됩니다.

사용자 제어

스토리지 할당량은 디스크 공간에 대한 액세스 권한을 배포하고 악용을 방지하는 데 사용됩니다. 사용하려는 메모리는 먼저 요청해야 합니다. 다른 스토리지 API와 마찬가지로 사용자는 브라우저를 통해 Storage Foundation API가 차지하는 공간을 비울 수 있습니다.

유용한 링크

감사의 말씀

Storage Foundation API는 Emanuel KrivoyRichard Stotz가 지정하고 구현했습니다. 이 도움말은 피트 르페이지조 메들리가 검토했습니다.

Unsplash마르쿠스 스피스케 제공 히어로 이미지