typesense / typesense/typesense
Failed async-reference alias swap rollback is not persisted across restart
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 26.6k
- Forks
- 973
- Avg merge
- 18h 45m
- Merged PRs (30d)
- 4
Description
Summary
When an alias used by an async_reference is swapped to an incompatible collection, Typesense returns an error and appears to roll the alias back in memory. However, the rejected alias target is persisted. After restarting Typesense, the alias points to the rejected target and JOINs using the reference break.
This was reproduced twice, including once in a fresh isolated container.
Environment
- Typesense:
31.0.rc14 - Docker image:
typesense/typesense:31.0.rc14 - Image digest:
sha256:6f63c3de844ce3c399dee23e04530f38e80a7462de621f1c8444245325320baa - Single-node Docker deployment
Reproduction
- Create
parent_v1with acodestring field. - Create alias
parent -> parent_v1. - Create
childrenwith:
{
"name": "parent_code",
"type": "string",
"reference": "parent.code",
"async_reference": true
}
- Index
parent_v1document{"id":"p1","code":"ok"}and child document{"id":"c1","parent_code":"ok"}. - Create
parent_badwith only anotherstring field, so it does not contain the referencedcodefield. - Attempt to swap the alias:
PUT /aliases/parent
{"collection_name":"parent_bad"}
Typesense returns HTTP 500:
{"message":"Referenced field `code` not found in the collection `parent_bad`."}
- Before restart, verify that rollback appears successful:
GET /aliases/parentreturnsparent_v1.- The JOIN still returns the child and referenced parent.
- The child helper field still resolves to sequence ID
0.
- Restart Typesense and check again.
Actual behavior after restart
GET /aliases/parentnow returnsparent_bad.- The child's
parent_code_sequence_idbecomes the unresolved sentinel4294967295. - The JOIN fails with:
Referenced collection `parent` not found.
Relevant startup log:
Field `code` not found in the collection `parent_bad` which is referenced in `children.parent_code`.
Code pointer / possible fix direction
The risky ordering appears to be in CollectionManager::upsert_symlink():
- The candidate alias target is written to the store and published in memory.
- Only afterwards does the fallible deferred-reference resolution / target rebind run.
- The missing-field failure is detected while building the rebind plan, after the rejected target has already been persisted.
- The failure path does attempt to write the previous target back, but the restart reproduction shows that this rollback is not durable.
A possible direction is to preflight the full target-rebind plan (including referenced-field validation) before persisting the candidate alias, or otherwise delay the durable alias write until rebind succeeds. A regression test should reload/restart from the same store after a failed swap, not only assert the in-memory alias immediately after the error.
Expected behavior
If the alias update returns an error, the rollback should be durable. Both the in-memory alias and persisted alias should continue pointing to parent_v1, including after restart or snapshot restoration.
Impact
A deployment can receive an error and verify that the old alias and JOINs still work, yet a later restart silently activates the rejected target and breaks reference-based searches. This makes the rollback response misleading and turns a rejected schema deployment into a delayed outage.
Related
- #2827
- PR #2963, which added alias-target rebind validation and rollback behavior
The original successful alias-swap scenarios from #2827 work correctly in 31.0.rc14; this report is specifically about persistence after a failed swap.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/collection_manager.cpp at CollectionManager::upsert_symlink(), then trace the deferred-reference resolution and rebind-plan validation around the cited lines. Reproduce the failed alias swap and inspect the existing alias-target rollback behavior from PR #2963. Add or update a regression test that reloads or restarts from the same store, confirming the alias still targets parent_v1 and JOINs continue working after the failed update.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100