kriszyp / lmdb-js

Simple, efficient, ultra-fast, scalable data store wrapper for LMDB
Other
481 stars 39 forks source link

Call `backup` sometimes make the db corrupted #198

Open panmenghan opened 1 year ago

panmenghan commented 1 year ago

I notice there is a compact(backup(path: string, compact: boolean)) API in index.d.ts I tried it (because the database size grows very fast), but it sometimes also make the db corrupted

This is the code I used to compact the db:

// filename: corrupted.mjs

import fs from 'node:fs'
import process from 'node:process'
import {setTimeout} from 'node:timers/promises'
import lmdb from 'lmdb'

const dbPath = 'dist/db'
const backupPath = 'dist/backup'

try {
  // replace db with backup (if exists)
  fs.renameSync(`${backupPath}/data.mdb`, `${dbPath}/data.mdb`)
} catch {}

const flags = process.argv[2] ?? ''
const options = {
  compression: flags.includes('compression'),
  encoder: flags.includes('moreTypes') ? {moreTypes: true} : undefined,
}

if (flags.includes('clearDb')) {
  console.log('clearDb')
  fs.rmSync(dbPath, {recursive: true})
}

console.log('options', options)

const rootDb = lmdb.open(dbPath, {
  ...options,
})

const cacheDb = rootDb.openDB('cache', {
  ...options,
  cache: true,
})

let CLOSED = false
async function main() {
  workload_cacheDb()
  workload_rootDb()
  await setTimeout(5e3)
  try {
    // remove backup (if exists)
    fs.unlinkSync(`${backupPath}/data.mdb`)
  } catch {}

  fs.mkdirSync(backupPath, {recursive: true})
  CLOSED = true
  await rootDb.backup(backupPath, true)
  await rootDb.close()
  console.log('done')
}

main()

async function workload_cacheDb() {
  const key = Symbol('key1')
  while (true) {
    await setTimeout(50)
    if (CLOSED) return
    cacheDb.put(key, Array.from({length: 500_000}).fill('1234567890abcdef'))
  }
}

async function workload_rootDb() {
  let i = 1
  while (true) {
    const key = (++i).toString()
    await setTimeout(100)
    if (CLOSED) return
    rootDb.put(key, {
      list: Array.from({length: 5000}).fill('1234567890abcdef'),
    })
  }
}

I tried with different options combinations (compression: true, encoder: {moreTypes: true}), but it seems not related to the options

Default options:

# first time
$ node .\corrupted.mjs clearDb
options { compression: false, encoder: undefined }
done

# second time
$ node .\corrupted.mjs  
options { compression: false, encoder: undefined }
done

# third time
$ node .\corrupted.mjs  
options { compression: false, encoder: undefined }
<no error message, just crashed>

Enable compression options:

# first time
$ node .\corrupted.mjs compression,clearDb
options { compression: true, encoder: undefined }
done

# second time
$ node .\corrupted.mjs compression
options { compression: true, encoder: undefined }
[Error: MDB_PAGE_NOTFOUND: Requested page not found]
file:///node_modules/.pnpm/lmdb@2.6.8/node_modules/lmdb/write.js:504
                                let error = new Error("Commit failed (see commitError for details)");
                                             ^
Error: Commit failed (see commitError for details)
    at rejectCommit (file:///node_modules/.pnpm/lmdb@2.6.8/node_modules/lmdb/write.js:504:17)
    at resolveWrites (file:///node_modules/.pnpm/lmdb@2.6.8/node_modules/lmdb/write.js:470:5)
    at Object.<anonymous> (file:///node_modules/.pnpm/lmdb@2.6.8/node_modules/lmdb/write.js:376:4) {
    commitError: Promise {
      <rejected> [Error: MDB_PAGE_NOTFOUND: Requested page not found],
      reject: [Function (anonymous)]
    }
}

Enable compression and moreTypes options(the error is same as the above):

# first time
$ node .\corrupted.mjs compression,moreTypes,clearDb
options { compression: true, encoder: { moreTypes: true } }
done

# second time
$ node .\corrupted.mjs compression,moreTypes
options { compression: true, encoder: { moreTypes: true } }
[Error: MDB_PAGE_NOTFOUND: Requested page not found]
file:///node_modules/.pnpm/lmdb@2.6.8/node_modules/lmdb/write.js:504
                                let error = new Error("Commit failed (see commitError for details)");
                                             ^
Error: Commit failed (see commitError for details)
    at rejectCommit (file:///node_modules/.pnpm/lmdb@2.6.8/node_modules/lmdb/write.js:504:17)
    at resolveWrites (file:///node_modules/.pnpm/lmdb@2.6.8/node_modules/lmdb/write.js:470:5)
    at Object.<anonymous> (file:///node_modules/.pnpm/lmdb@2.6.8/node_modules/lmdb/write.js:376:4) {
    commitError: Promise {
      <rejected> [Error: MDB_PAGE_NOTFOUND: Requested page not found],
      reject: [Function (anonymous)]
    }
}

Some other error I encountered, after the db corrupted (sometimes): D:\a\lmdb-js\lmdb-js\dependencies\lmdb\libraries\liblmdb\mdb.c:2631: Assertion 'rc == 0' failed in mdb_page_dirty()

Version information:

os: windows 11
lmdb: v2.6.8
node: v16.18.0
kriszyp commented 1 year ago

Thank you for the detailed report. I am definitely able to reproduce this. Working on trying to find the root cause.