How to efficiently append to a file?

I’m appending data to a log file using OPFS (Origin Private File System browser API) and I noticed it’s very slow.

It takes ~22ms to write 5MB to a new file but it also takes ~23ms to append a single byte.

I didn’t file anything like an append flag. I had to read the file size and start writing at that position to append data. Is there a better way of appending data?

For comparison, in Node.js it takes 1.3ms to write 5MB to a new file and 0.1ms to append a single byte.

OPFS benchmark code:

(async () => {
  async function create5MBFile(fileHandle) {
    console.time('create5MBFile');
    const writable = await fileHandle.createWritable();
    const chunkSize = 5 * 1024 * 1024;

    const chunk = new Uint8Array(chunkSize).fill(0);
    await writable.write(chunk);

    await writable.close();
    console.timeEnd('create5MBFile');
  }

  async function appendByte(fileHandle) {
    console.time('appendByte');
    const file = await fileHandle.getFile();
    const currentSize = file.size;

    const writable = await fileHandle.createWritable({ keepExistingData: true });
    const byteToAppend = new Uint8Array([0]);

    await writable.write({ type: 'write', position: currentSize, data: byteToAppend });
    await writable.close();
    console.timeEnd('appendByte');
  }

  const root = await navigator.storage.getDirectory();
  const fileHandle = await root.getFileHandle('append.log', { create: true });
  await create5MBFile(fileHandle);
  await appendByte(fileHandle);
})();

Node.js benchmark:

const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, 'append.log');
const fileSize = 5 * 1024 * 1024;
const byteToAppend = Buffer.from([0]);

function create5MBFileSync() {
  const fileBuffer = Buffer.alloc(fileSize, 0);
  console.time('write 5MB');
  fs.writeFileSync(filePath, fileBuffer);
  console.timeEnd('write 5MB');
}

function appendByteSync() {
  console.time('appendByteSync');
  fs.appendFileSync(filePath, byteToAppend);
  console.timeEnd('appendByteSync');
}

create5MBFileSync();
appendByteSync();