stacklok / stacklok/toolhive

Refactor upstream token storage from STRING+SET to HASH with per-field TTL

Open
#4,279 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Go
Stars
2.2k
Forks
300
Avg merge
1d 15h
Merged PRs (30d)
184

Description

Summary

The current upstream token storage in Redis uses one STRING key per provider per session plus a SET index to track all providers for a session. This can be simplified to a single HASH per session, where each field is a provider name and the value is the JSON token blob. Redis 7.4+ / Valkey 9.0+ support per-field HEXPIRE, allowing independent TTLs on each provider's tokens within the hash.

This refactor would:

  • Eliminate the session index SET (upstream:idx:{sid}) entirely
  • Reduce GetAllUpstreamTokens from 2 round trips to 1 (HGETALL instead of SMEMBERS + MGET)
  • Simplify DeleteUpstreamTokens from N+1 DEL commands to a single DEL
  • Remove key-string parsing to extract provider names
  • Net reduction of ~50 lines of code with no interface changes

Prerequisites

  • Redis >= 7.4 for per-field HEXPIRE support
  • Valkey >= 9.0 for per-field HEXPIRE support (valkey.io/commands/hexpire)
  • go-redis v9.18.0 (already in use) supports HExpire
  • miniredis v2.37.0 (already in use) supports HEXPIRE (seconds precision only)

Current Blocker

AWS ElastiCache supports up to Valkey 8.2 as of March 2026. There is no public timeline for Valkey 9.0 support. Since ToolHive deployments may run on ElastiCache, this refactor cannot ship until ElastiCache offers Valkey 9.0+.

Design

The full design is documented in docs/arch/12-upstream-hash-refactor.md. Key design decisions from expert review:

  • Use HEXPIRE (seconds) everywhere, not HPEXPIRE -- second precision is sufficient and has broader support
  • HSET clears any existing field TTL, so HEXPIRE must always follow HSET
  • Skip HEXPIRE when PTTL returns -1 (no-expiry tokens)
  • Migration idempotency: use HEXISTS(hashKey, provider) not EXISTS(newKey)
  • Orphaned index SETs expire via existing TTLs -- no explicit cleanup needed
  • One-way migration; old binaries gracefully degrade (users re-auth)

Estimated Impact

  • ~150 lines removed, ~100 lines added (~50 net reduction)
  • 5 files modified, no interface changes, no caller changes
  • One-shot startup migration from STRING+SET to HASH layout

Generated with Claude Code

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

Read docs/arch/12-upstream-hash-refactor.md first, then locate the upstream token Redis storage and startup migration entry points described by the design. Confirm the Redis and Valkey version prerequisites, including the ElastiCache blocker, before undertaking the five-file refactor; done means the documented HASH layout, per-field TTL behavior, and one-shot migration are implemented without interface changes.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, redis
Domain
backend, databases
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.