Skip to content

database crash #110

Open
Open

Description

@alltheseas

I'm crashing on master when i click on any profile. Only happens with my main database, not a fresh one


Assertion failed: (((uintptr_t)entry % 8) == 0), function ndb_note_meta_find_entry_impl, file metadata.c, line 244.
Process 74465 stopped
* thread #17, stop reason = hit program assert
    frame #4: 0x00000001012cad4c notedeck`ndb_note_meta_clone_with_entry.cold.2 + 36
notedeck`ndb_note_meta_clone_with_entry.cold.3:
->  0x1012cad4c <+0>:  stp    x29, x30, [sp, #-0x10]!
    0x1012cad50 <+4>:  mov    x29, sp
    0x1012cad54 <+8>:  adrp   x0, 7332
    0x1012cad58 <+12>: add    x0, x0, #0x555 ; "ndb_note_meta_clone_with_entry"
(lldb) bt
* thread #17, stop reason = hit program assert
    frame #0: 0x000000018c616388 libsystem_kernel.dylib`__pthread_kill + 8
    frame #1: 0x000000018c64f88c libsystem_pthread.dylib`pthread_kill + 296
    frame #2: 0x000000018c558a3c libsystem_c.dylib`abort + 124
    frame #3: 0x000000018c557c70 libsystem_c.dylib`__assert_rtn + 284
  * frame #4: 0x00000001012cad4c notedeck`ndb_note_meta_clone_with_entry.cold.2 + 36
    frame #5: 0x00000001011c34b8 notedeck`ndb_note_meta_clone_with_entry + 772
    frame #6: 0x00000001011bcadc notedeck`ndb_write_note + 3500
    frame #7: 0x00000001011bb48c notedeck`ndb_writer_thread + 956
    frame #8: 0x000000018c64fc0c libsystem_pthread.dylib`_pthread_start + 136

crap. that assert is bad. Means the database is misaligned

reported by @kernelkind

Activity

  1. linear commented on Dec 30, 2025

    @linear
  2. alltheseas commented on Dec 30, 2025

    @alltheseas
    Author
    Root Cause Analysis
    
      The crash occurs at metadata.c:244:
      assert(((uintptr_t)entry % 8) == 0);
    
      The issue: LMDB does not guarantee 8-byte alignment for stored values. When values are written to LMDB, they're stored contiguously within B-tree pages. While each page is aligned, individual key-value pairs within the page may start at any offset.
    
      Why it affects only certain databases:
      - Fresh databases happen to have metadata stored at aligned addresses (by chance)
      - Older databases with more data have metadata at various offsets, some of which are not 8-byte aligned
    
      The assertion was added in commit e90d933c ("metadata: add online metadata counts") as a defensive check, but it's incompatible with how LMDB stores data.
    
      Technical Details
    
      1. ndb_get_note_meta() returns a pointer directly into LMDB's memory-mapped file
      2. The ndb_note_meta and ndb_note_meta_entry structs are packed (#pragma pack(push, 1))
      3. The assertion expects all entry pointers to be 8-byte aligned
      4. LMDB doesn't guarantee this alignment
    
    
  3. alltheseas commented on Dec 30, 2025

    @alltheseas
    Author
    Option Read Performance Memory Overhead Code Complexity Portability Best For
    1. Align at storage ★★★★★ Fastest High (padding waste) High (storage format changes) Good New designs where you control format
    2. Copy to aligned buffer ★★★★☆ Fast after copy Stack/heap allocation Low (single memcpy) Excellent Hot paths with multiple field accesses
    3. memcpy per field ★★★★☆ Compiler-optimized None Medium (wrapper functions) Excellent Sparse field access, max portability
    4. Accept hardware penalty ★★★☆☆ ~5-15% slower None None (current code) Good (modern CPUs) Cold paths, simple code

    Summary:

    Criteria Winner
    Simplest implementation Option 4 (do nothing)
    Best for hot loops Option 2 (copy once, access many)
    Most portable Option 3 (memcpy per field)
    Best raw performance Option 1 (but rarely practical with LMDB)
    Best for nostrdb's case Option 4 or 2 depending on access patterns
  4. alltheseas commented on Dec 30, 2025

    @alltheseas
    Author

    Root Cause Analysis

    The padding strategy doesn't work because LMDB adds per-node overhead:

      Per key-value pair in LMDB:
      ┌─────────────────┬─────────────┬─────────────┐
      │ MDB_node header │     Key     │    Value    │
      │   (4-8 bytes)   │ (32 bytes)  │ (N bytes)   │
      └─────────────────┴─────────────┴─────────────┘
    

    The MDB_node header size varies:

    • 64-bit systems: 8 bytes (alignment may coincidentally work)
    • 32-bit systems: 4-6 bytes (alignment breaks)

    Even on 64-bit, it's not guaranteed - it depends on LMDB compile options and version.


    The Math (64-bit case)

    Node header: 8 bytes
    Key: 32 bytes
    ────────────────────
    Total: 40 bytes (before value)
    40 % 8 = 0 ✓ (happens to align)

    The Math (32-bit case)

    Node header: 4 bytes
    Key: 32 bytes
    ────────────────────
    Total: 36 bytes (before value)
    36 % 8 = 4 ✗ (misaligned!)


    Why iOS crashes

    Even though modern ARM64 handles most misaligned access, crashes can occur from:

    1. SIMD/NEON instructions (require alignment)
    2. Atomic operations
    3. Compiler optimizations assuming alignment

    Solutions

    If alignment must be global, the options are:

    Option Tradeoff
    1. Pad keys to align values Add 4 bytes to 32-byte keys → 36 bytes, so 8 + 36 = 44, still misaligned on 32-bit
    2. Use MDB_INTEGERKEY for META Keys become 8 bytes, need separate id→key mapping
    3. Store data differently Store offset into a separate aligned arena, not direct values
    4. Accept copy-on-read The dev rejected this

    The fundamental issue: LMDB cannot guarantee 8-byte value alignment without controlling the node header size, which is internal to LMDB.

    Would the dev consider switching META db to MDB_INTEGERKEY like NOTE and PROFILE dbs use?

    Sources:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions