# `Electric.ShapeCache.ShapeStatus.ShapeDb.WriteBuffer`
[🔗](https://github.com/electric-sql/electric/tree/%40core/sync-service%401.6.2/packages/sync-service/lib/electric/shape_cache/shape_status/shape_db/write_buffer.ex#L1)

Buffers SQLite metadata writes using ETS for immediate return to callers,
then batches and flushes to SQLite in the background.

This prevents timeout cascades when many concurrent clients create shapes
after a redeploy.

## Architecture

Two ETS tables are used:

1. **Operations table** (ordered_set) - queue of operations to flush to SQLite
2. **Shapes table** (set) - buffered shapes, comparable index, and tombstones with namespaced keys

## How it works

1. When a shape is added, insert into shapes table and queue :add operation
2. When a shape is removed, insert tombstone and queue :remove operation
3. GenServer polls every 50ms and flushes pending operations to SQLite
4. After successful flush, clean up entries from shapes table

## Crash recovery

If the system crashes, all in-flight writes in the operations table will be
lost. On reboot clients of the in-flight shapes will receive `must-refetch`
responses and the shapes will be re-inserted into the buffer.

This will leave orphaned shape data in the storage implementation, as we are
losing all references to the handle. We will need some background
reconciliation process that culls orphaned storage data.

## Operations table format

`{{monotonic_time, unique_int}, operation, flushing}`

## Shapes table key formats

- `{:pending_count, integer}` - count of shapes pending addition
- `{{:shape, handle}, shape, comparable}` - shape data
- `{{:comparable, comparable}, handle}` - reverse index for O(1) lookup
- `{{:tombstone, handle}, timestamp}` - handles marked for deletion

# `add_shape`

Add a shape to the buffer

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `clear`

Clear all data from all tables

# `flush_sync`

Synchronously flush all pending writes. Useful for testing and graceful shutdown.

# `handles_for_relations`

Returns handles from the buffer that match any of the given relations (by OID)

# `has_handle?`

Check if a handle exists in the buffer.
Returns:
  - `false` if tombstoned (being deleted)
  - `true` if in shapes table (buffered, not yet in SQLite)
  - `:unknown` if not in buffer (may or may not exist in SQLite)

Note: Checks tombstone last since it's the authoritative "delete in progress"
signal, avoiding a race where tombstone is added between checks.

# `is_tombstoned?`

Check if a handle is in the tombstones (marked for deletion)

# `list_buffered_shapes`

Returns all buffered shapes as a list of {handle, shape} tuples, excluding tombstoned handles, sorted by handle

# `lookup_handle`

Look up a handle by comparable shape binary. Returns {:ok, handle} or :not_found.

# `lookup_shape`

Look up a shape by its handle. Returns {:ok, shape} or :not_found.

# `name`

# `op_key`

Returns a unique, ordered key for operations table entries

# `operations_table_name`

# `pause_flush`

Disable flushing of buffered actions to db. Useful for testing

# `pending_count_diff`

Gives the change to the total count of shapes in the database once all buffered writes are applied

# `pending_operations_count`

Returns the number of pending operations in the buffer

# `queue_snapshot_complete`

Queue a snapshot_complete operation

# `remove_shape`

Mark a shape for removal

# `resume_flush`

Re-enable flushing of buffered actions to db. Useful for testing

# `shapes_table_name`

# `start_link`

# `timestamp`

Returns a monotonic timestamp for ordering writes

# `tombstoned_handles`

Returns all tombstoned handles as a MapSet

---

*Consult [api-reference.md](api-reference.md) for complete listing*
