# sergiocipher/lexora architecture

> Architecture diagram of the GitHub repository sergiocipher/lexora, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-22). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/sergiocipher/lexora
- Repository: https://github.com/sergiocipher/lexora

## Overview

1. **System shape**
   - Lexora is a two-tier application: a static browser frontend and a Python FastAPI backend.
   - The backend is the orchestration layer that serves the UI, answers autocomplete requests, accepts search submissions, and coordinates persistence and caching.

2. **Frontend boundary**
   - `frontend/index.html`, `app.js`, and `style.css` implement a single-page-style typeahead UI in vanilla JavaScript.
   - The browser talks only to backend HTTP endpoints; it does not access Redis or MySQL directly.
   - The UI also surfaces live operational feedback through metrics returned by the backend.

3. **API and application entrypoint**
   - `app/main.py` defines the FastAPI app and the public routes.
   - `run.py` is the runtime entrypoint used to start the service, with Uvicorn serving the backend.
   - The HTTP surface is small and purpose-driven: suggestions, search submission, cache debug, metrics, and the root page.

4. **Autocomplete read path**
   - `GET /suggest` is the main read path.
   - A prefix is routed through a consistent hash ring to choose one Redis node.
   - The backend checks Redis first; on a miss it falls back to MySQL, then repopulates cache.
   - This makes Redis the latency-critical tier while MySQL remains the source of truth.

5. **Distributed cache topology**
   - `app/redis_client.py` and `app/consistent_hash.py` define the cache routing layer.
   - The project uses multiple Redis instances, started via `docker-compose.yml`, rather than a single cache node.
   - Consistent hashing is the key architectural boundary here: it deterministically assigns prefixes to Redis nodes so the cache can scale horizontally and remain stable as nodes change.

6. **Write-behind buffering path**
   - `POST /search` does not synchronously write every interaction to the database.
   - Instead, the request is appended to an in-memory buffer managed in `app/buffer.py`.
   - A background flush process periodically batches buffered updates into MySQL and refreshes related cache entries.
   - This is the main durability/performance tradeoff in the system: fast ingestion with delayed persistence.

7. **Persistence layer**
   - `app/database.py` holds SQLAlchemy engine/session setup, and `app/models.py` defines the MySQL-backed entities.
   - MySQL is the durable store for the autocomplete dataset and buffered search activity.
   - `app/schemas.py` defines the API contracts exchanged over HTTP, separating transport validation from persistence models.

8. **Data ingestion / bootstrap**
   - `app/loader.py` loads the dataset from `typeahed_dataset.csv` into MySQL.
   - This is a batch bootstrap path, separate from the live request path, and exists to pre-seed the suggestion corpus.
   - In architecture terms, it is the offline data import boundary feeding the live system.

9. **Runtime configuration**
   - `app/config.py` centralizes environment-based settings such as DB credentials, Redis ports, cache TTL, and buffer flush behavior.
   - The system depends on runtime configuration for environment portability, especially because Redis is multi-node and MySQL is external.

10. **Operational visibility**
   - The backend exposes `/metrics` for cache hits, misses, hit rate, and buffer size.
   - `/cache/debug` provides observability into the consistent-hash routing decision for a prefix and the cache status on the selected node.
   - These endpoints make the caching layer and write-behind buffer inspectable, which is important because both are distributed/async behaviors.

11. **Test coverage**
   - `tests/test_api.py`, `test_buffer.py`, `test_consistent_hash.py`, and `test_performance.py` mirror the main architectural concerns.
   - Tests validate HTTP behavior, buffering semantics, hashing correctness, and performance characteristics.
   - That test split reflects the repo’s core boundaries: API, cache routing, batching, and throughput.

12. **Infrastructure and deployment assumptions**
   - `docker-compose.yml` is used specifically for the Redis sidecar topology.
   - The application still expects an externally available MySQL instance, so persistence is not containerized in the same way as cache.
   - Overall, the architecture is a horizontally cacheable read path, an asynchronous write path, and a conventional relational source of truth.

Main entry points: [`app/main.py`](https://github.com/sergiocipher/lexora/blob/main/app/main.py) (API Server), [`docker-compose.yml`](https://github.com/sergiocipher/lexora/blob/main/docker-compose.yml) (Redis Pool), [`frontend/app.js`](https://github.com/sergiocipher/lexora/blob/main/frontend/app.js) (App Logic), [`app/config.py`](https://github.com/sergiocipher/lexora/blob/main/app/config.py) (Config)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_frontend["Frontend"]
  node_frontend_ui["Browser UI<br/>vanilla spa<br/>[index.html]"]
  node_frontend_js["App Logic<br/>vanilla js<br/>[app.js]"]
  node_frontend_css["Styles<br/>css<br/>[style.css]"]
end

subgraph group_backend["Backend"]
  node_main_api["API Server<br/>FastAPI app<br/>[main.py]"]
  node_run_entry["Runner<br/>uvicorn entrypoint<br/>[run.py]"]
  node_api_schemas["Contracts<br/>request/response schemas<br/>[schemas.py]"]
  node_app_config["Config<br/>env settings<br/>[config.py]"]
  node_metrics_ep(("Metrics<br/>observability endpoint<br/>[main.py]"))
  node_cache_debug_ep(("Cache Debug<br/>debug endpoint<br/>[main.py]"))
end

subgraph group_data["Data Plane"]
  node_hash_ring{{"Hash Ring<br/>consistent hashing<br/>[consistent_hash.py]"}}
  node_redis_client["Redis Client<br/>cache routing<br/>[redis_client.py]"]
  node_redis_cluster[("Redis Pool<br/>multi-node cache<br/>[docker-compose.yml]")]
  node_buffer_mgr["Write Buffer<br/>in-memory batcher<br/>[buffer.py]"]
  node_db_layer["DB Layer<br/>SQLAlchemy setup<br/>[database.py]"]
  node_models["Models<br/>mysql entities<br/>[models.py]"]
  node_mysql[("MySQL<br/>source of truth")]
end

subgraph group_ops["Ops"]
  node_loader["Dataset Loader<br/>bootstrap import<br/>[loader.py]"]
  node_dataset["CSV Dataset<br/>seed data"]
  node_compose["Compose<br/>redis topology<br/>[docker-compose.yml]"]
end

node_frontend_ui -->|"uses"| node_frontend_js
node_frontend_js -->|"calls"| node_main_api
node_frontend_js -->|"reads"| node_metrics_ep
node_run_entry -->|"starts"| node_main_api
node_main_api -->|"validates"| node_api_schemas
node_main_api -->|"loads"| node_app_config
node_main_api -->|"reads"| node_redis_client
node_main_api -->|"buffers"| node_buffer_mgr
node_main_api -->|"persists"| node_db_layer
node_main_api -->|"serves"| node_metrics_ep
node_main_api -->|"serves"| node_cache_debug_ep
node_redis_client -->|"routes by"| node_hash_ring
node_hash_ring -->|"selects"| node_redis_cluster
node_redis_client -->|"accesses"| node_redis_cluster
node_buffer_mgr -->|"flushes to"| node_db_layer
node_db_layer -->|"maps"| node_models
node_models -->|"stores"| node_mysql
node_loader -->|"ingests"| node_dataset
node_loader -->|"loads"| node_mysql
node_compose -->|"defines"| node_redis_cluster
node_app_config -->|"configures"| node_redis_cluster
node_app_config -->|"configures"| node_mysql

click node_frontend_ui "https://github.com/sergiocipher/lexora/blob/main/frontend/index.html"
click node_frontend_js "https://github.com/sergiocipher/lexora/blob/main/frontend/app.js"
click node_frontend_css "https://github.com/sergiocipher/lexora/blob/main/frontend/style.css"
click node_main_api "https://github.com/sergiocipher/lexora/blob/main/app/main.py"
click node_run_entry "https://github.com/sergiocipher/lexora/blob/main/run.py"
click node_api_schemas "https://github.com/sergiocipher/lexora/blob/main/app/schemas.py"
click node_app_config "https://github.com/sergiocipher/lexora/blob/main/app/config.py"
click node_hash_ring "https://github.com/sergiocipher/lexora/blob/main/app/consistent_hash.py"
click node_redis_client "https://github.com/sergiocipher/lexora/blob/main/app/redis_client.py"
click node_redis_cluster "https://github.com/sergiocipher/lexora/blob/main/docker-compose.yml"
click node_buffer_mgr "https://github.com/sergiocipher/lexora/blob/main/app/buffer.py"
click node_db_layer "https://github.com/sergiocipher/lexora/blob/main/app/database.py"
click node_models "https://github.com/sergiocipher/lexora/blob/main/app/models.py"
click node_loader "https://github.com/sergiocipher/lexora/blob/main/app/loader.py"
click node_dataset "https://github.com/sergiocipher/lexora/blob/main/typeahed_dataset.csv"
click node_compose "https://github.com/sergiocipher/lexora/blob/main/docker-compose.yml"
click node_metrics_ep "https://github.com/sergiocipher/lexora/blob/main/app/main.py"
click node_cache_debug_ep "https://github.com/sergiocipher/lexora/blob/main/app/main.py"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_frontend_ui,node_frontend_js,node_frontend_css toneBlue
class node_main_api,node_run_entry,node_api_schemas,node_app_config,node_metrics_ep,node_cache_debug_ep toneAmber
class node_hash_ring,node_redis_client,node_redis_cluster,node_buffer_mgr,node_db_layer,node_models,node_mysql toneMint
class node_loader,node_dataset,node_compose toneRose
```

## Components

### Frontend

Static browser UI

- [Browser UI](https://github.com/sergiocipher/lexora/blob/main/frontend/index.html): vanilla spa · Single-page typeahead interface in the browser · `frontend/index.html`
- [App Logic](https://github.com/sergiocipher/lexora/blob/main/frontend/app.js): vanilla js · Client-side interaction and API calls · `frontend/app.js`
- [Styles](https://github.com/sergiocipher/lexora/blob/main/frontend/style.css): css · UI presentation layer · `frontend/style.css`

### Backend

FastAPI orchestration layer

- [API Server](https://github.com/sergiocipher/lexora/blob/main/app/main.py): FastAPI app · Defines HTTP routes and request flow · `app/main.py`
- [Runner](https://github.com/sergiocipher/lexora/blob/main/run.py): uvicorn entrypoint · Starts the backend process · `run.py`
- [Contracts](https://github.com/sergiocipher/lexora/blob/main/app/schemas.py): request/response schemas · HTTP validation and transport models · `app/schemas.py`
- [Config](https://github.com/sergiocipher/lexora/blob/main/app/config.py): env settings · Runtime configuration for DB, Redis, and buffers · `app/config.py`
- [Metrics](https://github.com/sergiocipher/lexora/blob/main/app/main.py): observability endpoint · Exposes cache and buffer health signals · `app/main.py`
- [Cache Debug](https://github.com/sergiocipher/lexora/blob/main/app/main.py): debug endpoint · Shows routing and selected-node cache state · `app/main.py`

### Data Plane

Cache, buffer, and persistence

- [Hash Ring](https://github.com/sergiocipher/lexora/blob/main/app/consistent_hash.py): consistent hashing · Maps prefixes to Redis nodes deterministically · `app/consistent_hash.py`
- [Redis Client](https://github.com/sergiocipher/lexora/blob/main/app/redis_client.py): cache routing · Selects and talks to the assigned Redis node · `app/redis_client.py`
- [Redis Pool](https://github.com/sergiocipher/lexora/blob/main/docker-compose.yml): multi-node cache · Horizontally scaled cache tier · `docker-compose.yml`
- [Write Buffer](https://github.com/sergiocipher/lexora/blob/main/app/buffer.py): in-memory batcher · Buffers search events before persistence · `app/buffer.py`
- [DB Layer](https://github.com/sergiocipher/lexora/blob/main/app/database.py): SQLAlchemy setup · Engine and session management · `app/database.py`
- [Models](https://github.com/sergiocipher/lexora/blob/main/app/models.py): mysql entities · Persistence models for the dataset and buffered activity · `app/models.py`
- **MySQL**: source of truth · Durable relational store

### Ops

Bootstrap and deployment

- [Dataset Loader](https://github.com/sergiocipher/lexora/blob/main/app/loader.py): bootstrap import · Loads the CSV dataset into MySQL · `app/loader.py`
- [CSV Dataset](https://github.com/sergiocipher/lexora/blob/main/typeahed_dataset.csv): seed data · Offline corpus used to prime suggestions · `typeahed_dataset.csv`
- [Compose](https://github.com/sergiocipher/lexora/blob/main/docker-compose.yml): redis topology · Defines the Redis sidecar deployment · `docker-compose.yml`

## Connections

- Browser UI → App Logic: uses · HTML shell loads the client logic
- App Logic → API Server: calls · Browser sends all API requests to the backend
- App Logic → Metrics: reads · UI surfaces live operational feedback
- Runner → API Server: starts · Uvicorn serves the FastAPI app
- API Server → Contracts: validates · Routes use request and response contracts
- API Server → Config: loads · Endpoints depend on runtime settings
- API Server → Redis Client: reads · Autocomplete requests consult cache routing
- API Server → Write Buffer: buffers · Search submissions are staged asynchronously
- API Server → DB Layer: persists · Batch flushes and cache repopulation use the DB layer
- API Server → Metrics: serves · Operational counters exposed over HTTP
- API Server → Cache Debug: serves · Debug endpoint exposes cache routing behavior
- Redis Client → Hash Ring: routes by · Prefix is assigned to a Redis node via consistent hashing
- Hash Ring → Redis Pool: selects · Deterministically picks the target cache node
- Redis Client → Redis Pool: accesses · Reads and repopulates the chosen Redis node
- Write Buffer → DB Layer: flushes to · Buffered updates are batched into MySQL
- DB Layer → Models: maps · SQLAlchemy layer operates over persistence entities
- Models → MySQL: stores · Entities are persisted in MySQL
- Dataset Loader → CSV Dataset: ingests · Bootstrap path reads the CSV corpus
- Dataset Loader → MySQL: loads · Seeder populates the durable store
- Compose → Redis Pool: defines · Deployment topology for multi-node Redis
- Config → Redis Pool: configures · Redis ports and cache settings are environment-driven
- Config → MySQL: configures · Database connectivity is environment-driven
