← PRA / report example

Independent demo based on public GitHub data. PRA is not affiliated with or endorsed by Typesense, Inc.

Typesense, October 2026

typesense/typesense · v31 branch · 2026-10-01 to 2026-11-01, UTC

Executive read

The month went into keeping a node up under concurrent load and into access control. Four ways to stall or crash a server were closed. API keys gained a join policy: a key scoped to one collection no longer reaches arbitrary fields of the collections it joins. Three branches from September reached v31.

September integrated twelve units, eleven through pull requests. October integrated eighty-seven commits, sixty-seven of them pushed directly, most of those a rebuild of the test suite and the build pipeline.

Key developments

1. A key carries an explicit policy for the collections it joins

An API key can now carry a joins object naming each collection the key may read through a reference, with the allowed and denied fields for each. A field outside it returns 403, a key created without joins keeps the previous unrestricted access, and a scoped key cannot widen the policy it inherits. The policy covers search, document get and export. Until this change a key described which collections it could act on and nothing about joins, so a key restricted to one collection could read any field of any collection that one referenced. A second way to widen a scoped key closed in the same month, in how the key’s own filter combines with the request’s.

Sources: #3119, #3125, #3111 · review, sections 1 and 2.

2. Document writes stop waiting behind searches and compactions

A turnstile now makes searches that arrive after a pending document write queue behind it, for a bounded share of the time: at the default a write holds searches back for half a second, after which the gate stays open three times as long. The new write-priority-max-percentage setting, from 0 to 100 and defaulting to 25, sets both numbers, and 0 removes the turnstile. Before it, a collection’s lock admitted readers while a writer waited, so a steady stream of searches could keep a write out indefinitely. A second stall, where a write waited out a storage compaction, was fixed separately.

Sources: direct commits 5def5b4 and f15f778, with six more of 24 October · review, section 3.

3. A cascade delete and a joining search both finish

A cascade delete now takes its locks one at a time and releases everything it holds as soon as one of them is contended, which lets a search finish first. It used to lock a collection and then everything referencing it, while a search locked its own collection and then a referenced one, so two requests running in opposite directions could each hold what the other needed. Neither a search cutoff nor an HTTP timeout broke that, because both threads were stuck inside the lock itself.

Sources: #3091, issue #3086 · review, section 4.

4. A crashing housekeeper no longer leaves a process answering health checks

The line that logs a long running query is now built on the request’s own thread, the crash handler skips its dump of queries in flight when the crashing thread holds the lock it needs, and an alarm ends a crash handler that hangs anyway. The background task used to read request state the search threads were free to change, so under enough load it crashed, and the crash handler then waited on a lock the dead task still held. The process stopped there: every search blocked, the replication loop stalled, and /health went on answering ok.

Sources: direct commit 86a3337 · review, section 5.

5. A compressible import body inflates into a bounded buffer

Gzip inflation now runs through a fixed buffer and appends, so the output size no longer depends on the compressed size, and a stream left incomplete is rejected rather than passed on truncated. The buffer used to be sized at ten times the compressed chunk, so any import body compressing better than about ten to one, which a repetitive JSONL import reaches easily, could corrupt the heap or drop documents without saying so. The code dates from 24 September and had no pull request at that cutoff, so it is not in the September report. It was opened as #3117 on 14 October and merged on 28 October.

Sources: #3117 · review, section 6.

6. A node starts from its own cluster state when a peer does not resolve

A node that cannot resolve every peer no longer applies a partial peer list. If it already has a replication log or a snapshot it starts from the membership recorded there, and if it has no state it waits. An unresolvable hostname used to be passed on as written and a failed peer dropped from the list, so a node could reconfigure the cluster from what it had and remove valid members. The same change closes a gap in the proxy endpoint, where an empty peer allowlist used to admit everyone.

Sources: #3103 · review, section 7.

7. The test suite runs in eight shards, and CI stops rebuilding what it has

The C++ suite now runs in eight shards, where the slowest takes 52 seconds against 316 for the whole suite in one process on 32 cores, and a cold build of the server went from 1,062 seconds to 336 on the same machine once a job cap on a dependency was lifted. Before this the suite ran in one process and its tests shared fixed paths, so two test processes at once overwrote each other’s data. These figures are from the commit messages, measured by their author.

Two of these changes concern exposure rather than cost. Workflow permissions were scoped and every action pinned to a commit, and the server logs uploaded when an API test fails are scrubbed of provider API keys first, because those logs carry request payloads and anyone who can read the repository can download the artifacts.

Sources: #3097 and 55 direct commits of 23 to 26 October · review, section 8.

Open considerations

Access control on write paths. The key policy covers search, document get and export (#3119). An open branch reports a separate gap in authorization on single document upsert (#3128), and another resolves a preset referenced by a scoped key before authorization and adds the authorized collections to the result cache key (#3142). Neither had merged at the end of the month.

The write turnstile outside tests. The hold and the cooldown are asserted by timed waits inside one process. Nothing in the sources checked measures search latency or write throughput on a loaded server, or explains how the default of 25 was chosen. The durability side of the same path is still open, as it was in September: the branch where a document becomes searchable before it is stored (#3050, open since 15 September) and the one on import responses that stall indexing (#3121).

Work that went in without a pull request. Sixty-seven of the month’s eighty-seven commits were pushed to the branch directly, including the test and build rebuild and the write turnstile. Public development shows no review for them, and whether they were reviewed elsewhere cannot be seen from this data. Two pull requests on subjects that landed directly were closed without merging in the same period, #3100 on 8 October and #3101 on 30 October, and neither records a reason.

Natural language search and embedders. Two September branches merged, the parameter newer OpenAI models require (#3073) and loading stored models without a live call (#3153). The typed judgment provider took one October commit against fourteen in September (#3072), so the shape of that generation may still change, and the regional embedder key that merged (#3096) came off a branch that also holds and evicts embedders per field and stayed open (#3104).

September branches that did not move. Fourteen pull requests open at the end of September were still open at the end of October with no commits in between, among them four the September report raised as open: the bundled OpenSSL bump (#3064, open since 23 September), the facet identifier collision (#3075) and the work on async reference registrations (#3082, #3066). An absence of commits is not evidence that any of them was abandoned.

Other

Three branches open at the end of September reached v31. A union search reports a timeout from whichever sub-search ran out of budget, and a union cut off with nothing to return answers 408 rather than 200 with an empty body (#3052, section 9). The memory guard asks the allocator to return unused pages before rejecting a write, so writes recover after a bulk delete instead of failing while the documents are already gone (#3080, section 11). Natural language models load at startup without calling their provider, so an outage during a restart no longer drops a configured model (#3073, #3153, section 10).

Four changes alter what a search returns. Pinned hits are no longer dropped when a filter matches fewer documents than there are pins (#3124, section 12). Replacing a nested object array no longer leaves its old values in the index, where a filter could still match a document whose stored array was empty (#2920, section 13). A curation rule keeps its filter_by when the query matched through a stopword, through stemming or after the rule rewrote the query (#2999, section 14). And query_by and group_by honour validate_field_names=false for unresolved reference style names (#3141, section 19).

The rest of the integrated work, each with its own section of the review: a server side --max-query-len (#3108, 15), a negated join computed from the parent collection (#3133, 16), collection metadata written under an exclusive lock after a crash under concurrent load (#3112, 17), embedders keyed by their GCP region (#3096, 18), a startup crash under WSL (#3093, 20), an empty object filter rejected with its own message (21), facet parsing kept inside the facet string (22) and the vector index pinned to a newer build of hnswlib, the nearest neighbour library underneath it (23).

Coverage and limits

Eighty-seven commits entered the v31 branch between 1 October and 1 November UTC, twenty of them integrating a pull request and sixty-seven pushed directly. The window is taken from the date a commit entered the branch, not from when its code was written. The technical review accounts for every one of them, the month’s themes in depth and the rest in a line each, and lists work still on a branch separately, by initiative, counting towards nothing.

Tests were read in the diffs and not run, and every number quoted above is reported by the author of the change. The newest published release is v30.2 from 19 April 2026, so none of this month’s work is in a release, as was true in September. Ten pull requests were closed without merging during October and their branches are not in the data collected, so why each was closed could not be checked. Public development does not show revenue, adoption or private work.

Privacy

Typesense is a trademark of Typesense, Inc.