# How Open Notebook's Async-First Architecture Handles Concurrent Database Queries and API Calls

> Discover how Open Notebook's async-first architecture uses FastAPI and AsyncSurreal for non-blocking, high-throughput concurrent database queries and API calls. Learn more!

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: architecture
- Published: 2026-06-19

---

**Open Notebook leverages FastAPI, AsyncSurreal, and pure async coroutines to execute concurrent database queries and API calls without blocking the event loop, enabling high-throughput I/O parallelism on a single thread.**

Open Notebook is an open-source knowledge management system built on an async-first stack. By combining FastAPI's ASGI server with the asynchronous SurrealDB driver, the application efficiently handles multiple simultaneous HTTP requests and database operations. This article examines how the async-first architecture in `lfnovo/open-notebook` manages concurrency across the database and API layers.

## Event-Driven Request Handling with FastAPI

The foundation of Open Notebook's concurrency model rests on **FastAPI** running atop an ASGI server such as uvicorn. Each incoming HTTP request becomes a coroutine scheduled on the single-threaded event loop.

When a request handler encounters an I/O operation—such as a database query or external API call—it uses the `await` keyword to yield control back to the event loop. This suspension allows the loop to immediately process other incoming requests, creating true parallelism for I/O-bound workloads without the overhead of operating system threads.

## Async Database Connection Management

The database layer in [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py) implements a robust async connection pattern using the `AsyncSurreal` driver. The `db_connection()` function serves as an **asynchronous context manager** that establishes authenticated connections to SurrealDB:

```python

# open_notebook/database/repository.py

@asynccontextmanager
async def db_connection():
    db = AsyncSurreal(get_database_url())
    await db.signin({"username": os.getenv("SURREAL_USER"),
                     "password": get_database_password()})
    await db.use(os.getenv("SURREAL_NAMESPACE"), os.getenv("SURREAL_DATABASE"))
    try:
        yield db
    finally:
        await db.close()

```

Repository helpers such as `repo_query()`, `repo_create()`, and `repo_update()` consume this context manager:

```python
async def repo_query(query_str: str, vars: dict | None = None) -> list[dict]:
    async with db_connection() as conn:
        raw = await conn.query(query_str, vars)
        return parse_record_ids(raw)

```

Because `AsyncSurreal` is fully asynchronous, multiple coroutines can hold independent connections simultaneously. The `await` expression releases the event loop during query execution, allowing other requests to proceed while SurrealDB processes the operation.

## Non-Blocking API Endpoints

All router functions in the API layer are declared with `async def`, ensuring that endpoint handlers never block the event loop. The search endpoint in [`api/routers/search.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/search.py) demonstrates this pattern:

```python

# api/routers/search.py

@router.post("/search", response_model=SearchResponse)
async def search_knowledge_base(search_request: SearchRequest):
    if search_request.type == "vector":
        results = await vector_search(
            keyword=search_request.query,
            results=search_request.limit,
            source=search_request.search_sources,
            note=search_request.search_notes,
            minimum_score=search_request.minimum_score,
        )
    else:
        results = await text_search(...)
    return SearchResponse(results=results or [], total_count=len(results), search_type=search_request.type)

```

The `await` calls to `vector_search()` and `text_search()` suspend the coroutine while database queries execute, permitting the server to handle other concurrent requests during the I/O wait.

## Streaming AI Workflows

Open Notebook extends its async-first design to streaming AI interactions using LangGraph. The "ask" feature streams partial results from a LangGraph workflow without blocking the event loop:

```python

# api/routers/search.py (excerpt)

async def stream_ask_response(question, strategy_model, answer_model, final_answer_model):
    async for chunk in ask_graph.astream(
        input={"question": question},
        config={"configurable": {"strategy_model": strategy_model.id,
                                 "answer_model": answer_model.id,
                                 "final_answer_model": final_answer_model.id}},
        stream_mode="updates",
    ):
        yield f"data: {json.dumps(chunk)}\n\n"

```

The `async for` loop iterates over the graph's async iterator, yielding control back to the event loop between chunks. This incremental streaming allows the server to send Server-Sent Events (SSE) to the client while maintaining responsiveness for other concurrent connections.

## Error Handling and Concurrency Safety

Repository helpers catch `RuntimeError` exceptions—typically indicating transaction conflicts—and log them at the debug level before re-raising. This lightweight error handling approach ensures that exception processing does not block the event loop:

1. **Transient conflict detection** – Errors are caught immediately without complex rollback logic
2. **Debug-level logging** – Minimal overhead prevents logging I/O from stalling other coroutines
3. **Fast propagation** – Exceptions allow awaiting coroutines to retry or propagate without delaying unrelated requests

## Summary

- **FastAPI's ASGI server** converts each HTTP request into a coroutine running on a single-threaded event loop
- **AsyncSurreal connections** in [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py) use `async with` context managers to yield control during database queries
- **Pure async endpoints** in [`api/routers/search.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/search.py) suspend execution during I/O operations, enabling parallel request processing
- **Streaming workflows** leverage `async for` loops to incrementally transmit AI-generated content without blocking
- **Lightweight error handling** maintains event loop responsiveness during database transaction conflicts

## Frequently Asked Questions

### What makes Open Notebook's architecture "async-first"?

Every layer of the application—from the FastAPI endpoints to the database driver to the AI orchestration—uses `async` and `await` primitives. Unlike traditional synchronous web frameworks that block threads during I/O, Open Notebook's async-first design suspends coroutines at I/O boundaries, allowing a single thread to manage thousands of concurrent connections.

### How does the database layer prevent blocking during queries?

The database layer uses the `AsyncSurreal` driver with a context manager pattern in `db_connection()`. When repository helpers call `await conn.query()`, the coroutine yields control to the event loop. SurrealDB's internal connection pooling ensures that multiple queries can execute simultaneously without serializing access through a single blocking thread.

### Can the async architecture handle multiple streaming responses simultaneously?

Yes. The streaming implementation uses `async for` loops over LangGraph's `ask_graph.astream()` method. Each streaming connection operates as an independent coroutine that yields control between chunks. This design allows the server to maintain hundreds of concurrent streaming AI conversations while continuing to process standard API requests and database queries.

### What happens if a database query fails in an async endpoint?

Repository helpers in [`open_notebook/database/repository.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/database/repository.py) catch `RuntimeError` exceptions from the SurrealDB driver, log them at debug level, and re-raise them immediately. This fast-fail approach ensures that database errors do not leave connections in ambiguous states or consume event loop time with complex error recovery logic. The awaiting coroutine receives the exception promptly and can handle it appropriately without affecting other concurrent requests.