typesense / typesense/typesense

Failed async-reference alias swap rollback is not persisted across restart

Open
#3,027 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
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

  1. Create parent_v1 with a code string field.
  2. Create alias parent -> parent_v1.
  3. Create children with:
{
  "name": "parent_code",
  "type": "string",
  "reference": "parent.code",
  "async_reference": true
}
  1. Index parent_v1 document {"id":"p1","code":"ok"} and child document {"id":"c1","parent_code":"ok"}.
  2. Create parent_bad with only an other string field, so it does not contain the referenced code field.
  3. 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`."}
  1. Before restart, verify that rollback appears successful:
  • GET /aliases/parent returns parent_v1.
  • The JOIN still returns the child and referenced parent.
  • The child helper field still resolves to sequence ID 0.
  1. Restart Typesense and check again.

Actual behavior after restart

  • GET /aliases/parent now returns parent_bad.
  • The child's parent_code_sequence_id becomes the unresolved sentinel 4294967295.
  • 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():

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.