Legora, a collaborative AI platform for legal work, faced significant engineering challenges in scaling its search capabilities. The platform supports two main types of search: Project Search, for internal documents ranging from tens to millions, and Legal Research, which involves deep dives across billions of laws, regulations, and historical cases.
Initially, Legora used a single Elasticsearch cluster for all search workloads. This simple setup worked well for hundreds of thousands of documents, with raw documents stored in blob storage and indexed in Elasticsearch. However, as Legora expanded into new geographical regions (US, EU, APAC), they faced regulatory requirements for full processing isolation per region. This led to the adoption of a multi-region Elasticsearch setup, where each region had its own independent Elasticsearch cluster, effectively multiplying the initial simple setup by the number of regions. While this satisfied the regulatory requirements and ensured regional data residency, it introduced significant operational overhead due to managing multiple identical setups.
The next set of challenges arose from enterprise requirements, specifically the demand for full physical tenant isolation and customer-managed encryption keys. This meant each client needed their own dedicated database and the ability to control their encryption keys to encrypt and decrypt their data at rest, allowing them to revoke access to their data from Legora if needed. To address this, Legora transitioned to PostgreSQL with PGVector, using `diskANN` and `tsvector` for search. They implemented hash-partitioned tables where the hash key combined the tenant and project IDs, allowing for physical separation of tenant data within PostgreSQL. This approach streamlined operations by consolidating all data within PostgreSQL and leveraging existing OLTP isolation. However, this setup introduced its own problems, such as unbalanced partitions due to varied project sizes and access patterns (hot and cold projects on the same partitions). As data grew, PostgreSQL performance degraded significantly, leading to 20s P99 retrieval and insert latency (from initial 100s of milliseconds), primarily because the working set across partitions could not fit in memory, causing frequent cache thrashing.
This performance bottleneck led Legora to adopt Turbopuffer, an object-storage-native search engine. Turbopuffer's architecture is fundamentally different: it writes directly to object storage (e.g., S3, GCS, Azure Blob), eliminating the need for disk replication or complex consensus protocols. This makes it cost-efficient and enables unlimited read replicas with query-index separation. For Legora, Turbopuffer allowed for one namespace per project, leveraging the object storage's inherent isolation capabilities. This design led to a dramatic improvement in latencies, achieving 294ms average insert latency and 80.9ms average sparse search latency, improving performance by an order of magnitude compared to PostgreSQL. The operational simplicity was a key benefit, as a single Turbopuffer cluster could serve all tenants across different regions, with infrequent access projects residing efficiently in blob storage.
Key Learnings and Architecture Decisions:
1. Retrieval is central to legal reasoning tasks, making search performance and accuracy critical for Legora's value proposition.
2. Turbopuffer excels for Legora because it offers a simple operational model across 70+ tenants with CMK (Customer-Managed Keys) and custom data residency. This is achieved by defining a namespace per project, which maps directly to isolated storage buckets or keys within the object storage system. This allows for isolation-native design where each namespace (or organization, or even sub-organization) can have its own isolated storage and encryption keys, satisfying stringent enterprise requirements.
3. Cost-efficient retrieval for long-tail, infrequently accessed projects/legal sources is a significant advantage. Turbopuffer's object-storage-native design means that cold data can reside in cheaper, slower storage tiers, while hot data is effectively cached higher up the memory hierarchy (CPU registers, L1/L2/L3 Cache, Main Memory DRAM, NVMe SSD). This "puffing up" strategy optimizes performance by keeping frequently accessed data in faster memory tiers and less frequently accessed data in slower, cheaper storage. The goal is to maximize data pushed down the memory hierarchy to achieve the best performance-cost ratio, minimizing round trips to slower storage.
4. Vector search in Turbopuffer uses a tree-like clustering approach (similar to a complicated B-tree) to organize 100s of billions of vectors. The higher levels of the tree (centroids) are kept in faster memory, while the actual data vectors (leaves) reside in SSDs or object storage. This minimizes the latency incurred from navigating the graph, which would be prohibitive with disk-based systems due to multiple 200ms round trips.
5. Fast text search also respects the memory hierarchy. Text search queries involve looking up terms in a hash map (inverted index) and then intersecting document ID lists. Turbopuffer minimizes round trips by optimizing the retrieval and intersection of these lists. By aggressively compressing these lists and intelligently caching relevant parts of the dictionary, it reduces the memory bandwidth needed for intersections. The system dynamically decides what parts of the dictionary are most relevant and keeps them in faster memory, ensuring efficient retrieval even for very rare terms (higher scoring in BM25).
This transition allowed Legora to focus on making Legora the best product, rather than wrestling with search infrastructure and scalability challenges. Furthermore, it resulted in a happy CFO due to significantly reduced operational costs.