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

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

이때 Storage Foundation API가 사용됩니다. Storage Foundation API는 빠르고 의견이 없는 새로운 스토리지 API로, 성능이 우수한 데이터베이스 구현, 대규모 임시 파일의 원활한 관리 등 웹에서 많이 요청된 새로운 사용 사례를 지원합니다. 이 새로운 인터페이스를 통해 개발자는 웹에 '자체 스토리지를 가져올' 수 있으므로 웹과 플랫폼별 코드 간의 기능 격차가 줄어듭니다.

스토리지 파운데이션 API는 매우 기본적인 파일 시스템과 유사하도록 설계되어 개발자가 더 높은 수준의 구성요소를 빌드할 수 있는 일반적이고 간단하며 성능이 우수한 기본 요소를 제공하여 유연성을 제공합니다. 애플리케이션은 필요에 가장 적합한 도구를 활용하여 사용성, 성능, 안정성 간의 적절한 균형을 찾을 수 있습니다.

웹에 다른 스토리지 API가 필요한 이유는 무엇인가요?

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

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

스토리지 파운데이션 API는 애플리케이션의 출처 내에 정의된 변경 가능한 대용량 파일을 성능이 우수한 방식으로 저장할 수 있도록 하여 이전 스토리지 옵션의 모든 격차를 해소하기 위한 시도입니다.

Storage Foundation API의 권장 사용 사례

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

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

Storage Foundation API란 무엇인가요?

API는 두 가지 주요 부분으로 구성되어 있습니다.

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

파일 시스템 호출

스토리지 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에 성공적으로 작성된 바이트 수입니다. 오류가 발생하면 버퍼 크기보다 작을 수 있습니다.

완성형 예시

위에서 소개한 개념을 더 명확하게 설명하기 위해 스토리지 파운데이션 파일의 수명 주기에 있는 여러 단계를 안내하는 두 가지 완전한 예시를 소개합니다.

열기, 쓰기, 읽기, 닫기

// 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();

보안 및 권한

Chromium팀은 사용자 제어, 투명성, 인체 공학 등 강력한 웹 플랫폼 기능에 대한 액세스 제어에 정의된 핵심 원칙을 사용하여 스토리지 파운데이션 API를 설계하고 구현했습니다.

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

사용자 제어

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

유용한 링크

감사의 말씀

스토리지 파운데이션 API는 Emanuel KrivoyRichard Stotz가 지정하고 구현했습니다. 이 도움말은 Pete LePageJoe Medley가 검토했습니다.

Unsplash마르쿠스 스피스케를 통한 히어로 이미지