← Back
Home › Systems › Productivity and collaboration

workplace-search

Answers one query across the documents, chat, tickets, wikis, code and mail a company keeps in many products, showing each person only what the source systems let them see.

Glean

Open the workplace-search pageworkplace-search Roaring bitmapscompression: Compressed set of 32-bit integers in 64K-value chunks, each a sorted array, bitmap or run list; fast intersections, the standard for bitmap indexes.›Hdr histogramdata-structures: Log-linear bucketed histograms with bounded relative error for latency percentilesDeterministic simulationtestingdistributed: Run the whole system in one thread under a seeded scheduler with simulated network, disk and clock, so every failure replays from its seed.›Incremental sync cursorsdistributed: The client keeps one opaque token; each pull returns only the changes after it plus the next token, so sync cost scales with what changed, not with the dataset.›Primary-backup replicationdistributed: One node orders all writes and ships them to standbys; the acknowledgement rule (none, one, all) sets what a crash loses, and an external arbiter decides who takes over.›Process supervisionhardware-os: A parent process starts services, reaps their exits and restarts them under a delay and burst-limit policy, then stops them with SIGTERM before SIGKILL.›Embedding inferencemachine-learning: Serve a pretrained embedding model: tokenize, batch requests, run the encoder, pool and normalize vectors, cache them; no trainingLearning to rankmachine-learning: Train a scorer on query-grouped data so that the order it induces maximizes NDCG; LambdaMART (boosted trees with NDCG-weighted pair gradients) is the production default.›Webhook deliverynetworking: Reliable server-to-server HTTP callbacks: signed POSTs, 2xx-means-done, retries over days with jitter, endpoint disabling, SSRF guards and idempotent receivers.›BM25 rankingsearch-sort: The standard lexical relevance score: IDF-weighted term frequency with saturation (k1) and length normalization (b); default in Lucene, Elasticsearch and SQLite FTS5.›HNSWsearch-sort: Hierarchical Navigable Small World: a layered proximity graph searched greedily from a sparse top layer; the default in-memory approximate nearest-neighbour index.›Hybrid search and rank fusionsearch-sort: Merge a keyword (BM25) result list and a vector (ANN) list into one ranking, by ranks (RRF) or by normalized scores (convex combination).›SimHashsearch-sort: Charikar's random-hyperplane sketch: a 64-bit fingerprint per document whose Hamming distance tracks cosine similarity, searchable in sub-linear time by exact block matches.›Unicode text tokenization forsearchsearch-sort: Turn multilingual text into index terms: segment words (UAX 29 style, CJK bigrams), normalize, case-fold and accent-fold, identically at index and query time.›Authorization models (RBAC,ABAC, ReBAC)security: How to decide who may do what to which object; roles for coarse rules, relationships for sharing, and checks on every object.›Coverage-guided fuzzingsecurity: Mutate inputs, run an instrumented target, and keep any input that reaches a new code edge; feedback turns blind guessing into incremental search.›Envelope encryptionsecurity: Encrypt each object with its own data key and wrap that key under a KMS-held key, so rotation re-wraps 60 bytes and deleting a key shreds data.›Mutual TLSsecurity: TLS where the client also proves a certificate key, so every connection carries a verified, rotatable machine identity.›OAuth 2 and OpenID Connectsecurity: Delegated authorization (OAuth 2) and login on top of it (OIDC); use authorization code + PKCE for every user-facing client.›Entity resolutionstorage: Decide which records describe the same real-world thing: block to cut the pair space, score pairs with a probabilistic model, cluster the matches transitively.›Inverted indexstorage: Map each term to a sorted, compressed list of the documents containing it; the core of full-text search and other fast set queries.›Point-in-time recoverystorage: Periodic base backups plus a continuously archived change log let you rebuild the state as of any instant, then branch from there.›Exponential backoff with jittersystems: Retry after delays that grow geometrically and are randomized, so failed clients spread out instead of hammering a recovering service in synchronized waves.›Graceful drainingsystems: On a termination signal, stop taking new work, finish in-flight work until a deadline, tell clients to reconnect elsewhere, then exit; clients resume from a token.›Token bucketsystems: A bucket of up to b tokens refilled at rate r; requests spend tokens, enforcing an average rate while allowing bursts of b.›Cooperative schedulingconcurrency: Green threads/coroutines yielding at await points on a user-space schedulerFault injectiondistributed: Inject latency, errors, kills and partitions via proxies, netem or hooksRecord replaydistributed: Record every nondeterministic input (time, randomness, I/O, model and tool outputs, per-tick player input) and re-execute deterministically for debugging, replays and state rebuildMessage sequence numbersdistributed: A per-stream counter on every message lets the receiver detect loss, duplicates and reordering, ack cheaply and resume after reconnect with "send from N".›Cursor paginationnetworking: Keyset/opaque-cursor pagination for stable incremental reads from APIs and tablesLeasesdistributed: A lock that expires on its own: bounded-time ownership that survives crashes and partitions, given bounded clock drift and fencing tokens on writes.›Health checkingsystems: Decide per backend whether to route to it using probes and real-traffic errors, with consecutive-count hysteresis, capped ejection and a panic fallback.›Dynamic batchingml-training: Queue independent requests and dispatch them as one batch when it is full or the oldest has waited max_delay; bounded extra latency buys throughput.›Weight quantizationml-training: Store weights as 8/4-bit integers with per-group scales; decode is memory-bound, so 4-bit weights give about 3-4x tokens/s and fit 4x larger models.›Evaluation metricsmachine-learning: Precision, recall, ROC AUC, average precision, log loss, NDCG and pass@k computed correctly, plus paired confidence intervals for comparing models.›Gradient-boosted decision trees(GBDT)machine-learning: Add small regression trees one at a time, each fitted to the loss gradient; histogram splits over 256 bins make it the strongest tabular default.›Loss functionsmachine-learning: The scalar a model minimizes: cross-entropy for classes and tokens, MSE or Huber for values, each with a stable form and simple gradient.›+ 31 moreTransparent interceptionnetworking: Redirect traffic to a local proxy (iptables TPROXY/REDIRECT, eBPF) without app changesSyscall interceptionsecurity: Intercept syscalls via ptrace, seccomp user notify or a user-space kernelFencing tokensdistributed: A number that grows with every lock grant and is checked by the protected resource, so a paused ex-holder's late write is rejected.›MPMC bounded queueconcurrency: Vyukov's array queue for many producers and consumers: each slot carries a sequence number, so one CAS per operation claims a slot without locks.›Backpressuresystems: Bounded queues and credit-based flow so slow consumers throttle producersHierarchical timing wheelsystems: Timers bucketed by expiry into wheels of slots, coarser wheels for far deadlines: O(1) insert and cancel for millions of timeouts.›Low precision number formatsnumerics: Bf16, fp16, fp8 (E4M3, E5M2), MX block formats and int4: ranges, rounding, scalingLU and Cholesky factorizationnumerics: Factor A = PLU (any square matrix, partial pivoting) or A = LLᵀ (SPD) once in O(n³), then solve each right-hand side in O(n²).›Bootstrap confidence intervalsstatistics: Resampling-based confidence intervals for arbitrary statisticsQuantile sketchesdata-structures: Mergeable quantile sketches: t-digest, DDSketch, HDR histogramsRegularizationmachine-learning: Techniques that trade training fit for generalization: weight decay, dropout, early stopping, label smoothing and data mixing, with the exact update rules and defaults.›+ 13 moreEbpfhardware-os: Verified in-kernel programs on hooks (kprobes, XDP, tc) with mapsPtrace process controlhardware-os: Ptrace: attach, read and write registers and memory, single-step, intercept signalsSeccomp bpfsecurity: Syscall filtering with seccomp BPF programsFalse sharingconcurrency: Independent variables written by different cores on one cache line force the line to bounce between cores; pad or regroup hot data to fix it.›Memory orderingconcurrency: What Relaxed, Acquire, Release and SeqCst atomics actually guarantee, what they cost on x86 and ARM, and which one each pattern needs.›Slab allocatorsystems: Pre-carve pages into equal-size slots per size class with an in-place free list: O(1) alloc and free, no external fragmentation, cache-warm reuse.›+ 6 moreCPU caches and the memoryhierarchyhardware-os: Sizes, line widths and latencies of L1/L2/L3/DRAM on modern x86 and Apple M-series, and the data-layout rules that follow from them.›+ 1 more

How it is built

Architecture overview

Workplace search answers one query across all the places a company keeps knowledge: documents, chat, tickets, wikis, code, mail, CRM. It differs from web search in two ways. The content lives in dozens of other products that must be connected and kept fresh, and every result must obey the permissions of the source system: a person may find only what they could already open there. The system therefore has an ingestion half (connectors that crawl or receive changes, normalise documents and copy their access lists) and a query half (a permission-aware index that combines keyword and meaning-based retrieval, then re-ranks with signals about people and behaviour). The idea that makes it work is to treat the access list as part of the document and filter on it inside the index, so a leak is impossible by construction, not prevented by a check at the end.

Parts:

 SaaS apps (docs, chat, tickets, wiki, code)
   │ API + webhooks          ┌────────────── control plane (schedules, rate limits, retries, DLQ) ──────────────┐
   ▼                         │                                                                                  │
 connectors ──docs + ACLs──► processing (clean, tokenize, dedupe, resolve people, embed) ──► index ◄─ ACL sync ─┘
                                                                                          │  keyword + vector + permissions
 user ──SSO──► query service ──query, principal set──► retrieval (BM25 + ANN, filtered) ──► fusion ──► re-ranker ──► results
                  ▲                                                                              ▲ click/dwell logs
                  └──────────────────────────── analytics, evaluation, training ─────────────────┘

How the parts fit together

Connectors

A connector for one source app does four jobs: authenticate, list documents, fetch changes, fetch permissions. Authentication is OAuth 2 and OIDC: an admin grants the product read access once, and the product holds a refresh token per source and per tenant, with scopes as narrow as the source allows. Apps that deliver service credentials or signed assertions use JWT. A first crawl lists everything; after it, the connector stores an opaque cursor from the source (Incremental sync cursors) and asks only for changes since then. Large listings use stable paging (Cursor pagination (no page yet)); event streams use Message sequence numbers to detect missed events. Sources that can push use Webhook delivery: the product exposes an endpoint, verifies the signature (HMAC request signing (no page yet)) and enqueues the work, because a webhook is a hint, never the full truth. The connector's own progress (cursor, last document) is committed through a Write-ahead log in its state store, so a crash resumes mid-crawl rather than restarting.

Every connector outputs the same envelope: a stable document id, source and URL, title, body text, author, times, and an access list of principals (users, groups, "everyone in domain", link-sharing). Normalising permissions into one model is the hardest part of the whole system, because sources differ: Google-style sharing by document, Slack by channel membership, Jira by project role, GitHub by repository.

Work control

Crawls compete for each source's API quota, which is shared by every customer using the same OAuth app. A Token bucket per source and tenant enforces the rate; a 429 response is not an error but a signal to wait for the advertised time, with Exponential backoff with jitter for everything else. Work items (crawl page, fetch document, re-embed) are tasks on a Durable job queue: leased, acknowledged on success, retried on failure, and moved to a Dead-letter queue after repeated failure so one malformed document does not block a million good ones. Tasks carry Idempotency keys so a retry that lands twice indexes once. A Circuit breaker per source stops hammering an unreachable API, and the crawl resumes when the breaker closes. Crawl order matters: recent and frequently viewed documents are fetched first, so a new customer sees useful results in minutes while the long tail fills in for days.

Document processing

Raw text from apps is messy: HTML, markdown, tables, quoted mail threads, boilerplate signatures. Text normalization rewrites it into a canonical form (Unicode normalisation, case folding, whitespace), and Unicode text tokenization splits it into index terms for every language the company uses, including languages without spaces. Documents are cut into passages of a few hundred tokens, because both keyword scoring and embeddings work better on passages than on whole files, and the result view can then jump to the passage.

The same file often exists several times (a slide deck attached in mail, uploaded to a drive, quoted in chat). SimHash gives each document a 64-bit fingerprint whose Hamming distance tracks similarity, so near-duplicates group cheaply; the group shares one ranking slot but keeps each copy's permissions. Content hashes (Crypto hashes) skip re-processing when a document's bytes did not change.

People are the second entity type. "Ana Pereira" in the directory, ana.p in the ticket tool and an email address in the doc comments are one person. Entity resolution blocks candidate pairs, scores them, and merges confirmed matches with Union-find; probabilistic scoring of that kind is the subject of Record linkage (no page yet). The resolved identity powers "documents by this person", boosts for the asker's colleagues, and, most importantly, the mapping from the SSO user to the source-app accounts needed for permissions.

Index

The keyword index is an Inverted index: per term, a sorted list of passage ids with term frequencies. Lists are compressed with Delta encoding of the ids and Bit-packing and PFOR of the gaps, with Varint / LEB128 for the small sparse fields. A multi-term query is a Posting list intersection, skipping through the long list from the short one. The semantic index stores an embedding per passage, produced by embedding-inference (no page yet), in an HNSW graph; its candidate heap is a d-ary heap, distances use SIMD programming, and Scalar quantization (no page yet) cuts vector memory for large tenants at a small recall loss. embedding-inference (no page yet) runs a pretrained embedding model at serving time: it batches the changed passages and the incoming queries, runs the encoder, pools and normalizes the vectors, and caches them, so the vector index and the query path see ready-made unit vectors. The embedding model is never trained here.

Permissions in the index. Each passage carries the ids of the principals who can see it. At query time the caller's principal set (the user plus all their groups) is expanded and the index keeps only passages whose access list intersects it. Both operations are set intersections on integer ids, which Roaring bitmaps do fast: one bitmap per principal of the passages it may read, unioned for the caller and intersected with the term match. The filter is applied before scoring and before counting, so ranking, facets and result counts never reveal what the user cannot open. Group membership changes constantly, so groups are not flattened into each document; document ACLs store group ids, and membership is resolved at query time from a small, fast table.

Ranking

First stage: BM25 ranking over the keyword index and nearest neighbours from the vector index, each returning a few hundred candidates. Hybrid search rank fusion merges them by rank, so no calibration between BM25 scores and cosine similarities is needed, and Top-k selection trims the list. Second stage: Learning to rank re-orders the top candidates with a model trained on query groups, typically Gradient-boosted trees (LambdaMART style) over features such as the keyword and vector scores, document age, source type, author closeness to the asker, the asker's team's recent views, click-through for this query, and title match. Features that depend on the asker are what separates workplace search from web search. Training uses Loss functions built for ordering, and quality is read from Evaluation metrics (NDCG, recall at k) on held-out query sets. Click logs are biased (people click what is shown first), so a model needs position-bias correction or randomised exploration on a small share of traffic; otherwise it learns to reproduce the current order.

Access control

Result filtering handles who sees which document. The rest of the product needs ordinary Authorization models: tenant admins configure connectors, analysts see usage but not content, and every tenant is isolated from every other in storage as well as in queries. Index data holds copies of company text, so it is protected with Envelope encryption: each tenant has its own data key, wrapped by a key service (AES key wrap (no page yet)), encrypting blobs with an authenticated cipher (AEAD ciphers) with nonces from a CSPRNG (no page yet). Deleting a tenant's key makes its data unrecoverable, which is how deletion requests and offboarding are made real in backups. Services call each other over Mutual TLS.

Operations

The relational database holding configuration, cursors and identity uses Primary-backup replication, with Leases and Health checking for failover, Aries recovery after a crash and Point-in-time recovery for operator mistakes (a connector wiped a tenant's cursors). The index is rebuildable from the connectors, but a rebuild takes days, so it is snapshotted (Copy-on-write snapshots (no page yet)) into Object storage. Workers run under Process supervision and finish their current document on Graceful draining. Latency percentiles are recorded as HDR histograms (no page yet), per stage.

Correctness of the crawl scheduler, the ACL sync and the index merge is established with Deterministic simulation testing: a seeded PRNG drives fake source APIs that return errors, reorder events and revoke permissions mid-crawl, with Fault injection (no page yet) and Record and replay (no page yet) reproducing production failures and a Cooperative scheduling (no page yet) runtime making interleavings repeatable. Document parsers (PDF, office formats, HTML) read untrusted bytes and are the likeliest place for a vulnerability, so they are fuzzed (Coverage-guided fuzzing, Code coverage instrumentation, Sanitizers (no page yet), Fast hash functions for the coverage map) and failing inputs are minimised with Delta debugging (no page yet).

Data and control flow

Indexing a changed document:

  1. A webhook (or the next scheduled sync) tells the connector that document D changed. The connector fetches D with its current permissions, using the stored cursor to bound the work.
  2. Processing normalises text, cuts passages, computes fingerprints, drops unchanged passages by hash, and asks the embedding model for vectors of the changed ones.
  3. The index writes new passages and the new access list in one update, then marks old passages deleted. A delete is as important as an insert: a revoked permission must disappear from search quickly.
  4. Only after the write is durable does the connector advance its cursor.

A query:

  1. The user's browser calls the query service with an SSO session. The service resolves user and groups into a principal set (cached for seconds).
  2. Retrieval runs keyword and vector search in parallel, each filtered by the principal set; each returns a few hundred candidates in tens of milliseconds (order of).
  3. Fusion and top-k trimming leave about a hundred; the re-ranker scores them with features computed from a feature store; the top ten get snippets and highlights.
  4. The response goes back; clicks and dwell time are logged for training and evaluation.

A permission change: the source reports that user U was removed from group G (by webhook or the periodic ACL sync). The group table updates, so every document that lists G stops matching U on the next query. Document-level changes (a file un-shared) update the document's ACL within the sync interval; the lag between the two is the system's published permission staleness.

Scaling

  1. Source API quotas are the first limit on freshness, not CPU. Use change feeds and webhooks, prioritise documents by recent use, and spread tenants over separate OAuth apps if a source shares quota per app.
  2. Embedding cost. Re-embedding is the most expensive step. Re-embed only changed passages, use a smaller model for the long tail, and batch on GPUs.
  3. Index size and query latency. Shard by tenant; large tenants shard by document id with scatter-gather. Quantise vectors, keep hot postings in memory, and cache principal-set bitmaps.
  4. Re-ranker latency. Limit candidates to the top hundred and keep features precomputed.
  5. Cold start. A new tenant has no click data; start with a global model and personalise as signals arrive.

Failure modes

FailureSymptomCauseFix
Permission leaka user finds a title they should not seeACL stale, group flattened wrongly, filter applied after rankingfilter inside the index; fail closed when ACL missing; test with a no-access account
Revoked access still searchableremoved user keeps seeing resultsACL sync lag, missed webhookfast path for removals, periodic full ACL reconciliation
Stale resultsan edited document shows old textconnector behind, cursor advanced before index writeadvance cursors after durable write; alert on connector lag
Duplicate floodthe same file ten times in resultsno near-duplicate groupingSimHash grouping, canonical copy by source priority
Rate-limit bana connector is blocked by the sourcetoo aggressive crawl, retries without jittertoken bucket, honour Retry-After, circuit breaker
Poison documenta worker crashes repeatedlymalformed file hits a parser bugsandbox parsers, dead-letter queue, fuzzing
Ranking regressionclicks fall after a model updatemodel trained on position-biased clicks, wrong metricoffline NDCG gate, interleaved online test, rollback
Cross-tenant mixone tenant sees another's snippetshared cache keyed without tenant idtenant id in every key, per-tenant encryption keys

Real implementations

namewhat it isrepolicencenotes
Onyx (formerly Danswer)open-source enterprise search and chat over connected sourcesonyx-dot-app/onyxMIT (Community Edition; see repo)pulls data, metadata and permissions; its contributor guide lists Postgres, OpenSearch, Redis and MinIO
OpenSearchsearch engine with BM25, vector search and document-level securityopensearch-project/OpenSearchApache-2.0the common index layer
Vespasearch and vector engine with hybrid ranking and tensor rankingvespa-engine/vespaApache-2.0ranking expressions in the engine
Apache SolrLucene-based search serverapache/solrApache-2.0mature keyword search
tantivyRust search library in the Lucene designquickwit-oss/tantivyMITBM25 scoring, embed in your own service
Typesensetypo-tolerant search enginetypesense/typesenseGPL-3.0fast, in-memory design
Meilisearchsearch engine with hybrid searchmeilisearch/meilisearchMIT (community edition; see repo)simple to run
Apache ManifoldCFconnector framework with per-document securityapache/manifoldcfApache-2.0a multi-repository crawler with authority connectors

Glean is the best-known product of this type; its internals are not public, so the table lists open parts only. The alternative is a single-source search embedded in each app: it needs no connector layer and no permission mapping, but users must know where to look, which is the problem this system exists to remove.

Sources

  • S. Robertson, H. Zaragoza, The Probabilistic Relevance Framework: BM25 and Beyond, Foundations and Trends in Information Retrieval 3(4), 2009.
  • G. V. Cormack, C. L. A. Clarke, S. Buettcher, Reciprocal rank fusion outperforms Condorcet and individual rank learning methods, SIGIR 2009.
  • Y. A. Malkov, D. A. Yashunin, Efficient and robust approximate nearest neighbor search using Hierarchical Navigable Small World graphs. paper
  • C. J. C. Burges, From RankNet to LambdaRank to LambdaMART: An Overview, Microsoft Research TR-2008-109. report
  • RFC 6749, The OAuth 2.0 Authorization Framework. rfc
  • Onyx documentation: docs.onyx.app.

Similar systems

Share the most building blocks with this one.