Skip to content

Drop Redis Stack dependency — RedisJSON usage is shallow enough to replace with plain Redis + json.dumps #160

@howethomas

Description

@howethomas

Summary

The codebase depends on the redis/redis-stack image (RedisJSON module) but uses only a tiny, trivial subset of RedisJSON. Replacing every redis.json().set/get/mget/delete with stringified-JSON over plain Redis would:

  • let the conserver run on stock Redis, Valkey, KeyDB, AWS ElastiCache, or any managed Redis-protocol service
  • remove the RSALv2 licensing footprint that Redis Stack carries
  • eliminate the 4 pre-existing common/tests/test_api.py failures developers hit on machines without a RedisJSON-enabled local Redis
  • shrink the container image and speed up startup
  • remove one specialized skill for new contributors

Audit numbers

Grepping for every .json().* call across the codebase (filtered for actual Redis client calls, excluding response.json() etc.):

8  .json().set
5  .json().get
2  .json().mget
1  .json().delete
———
16 total call sites

Distribution: common/redis_mgr.py, common/lib/vcon_redis.py, api/api.py.

Every set uses path "$". Every get uses Path.root_path() / ".". No projection, no subtree extraction.

What is NOT used

  • No JSONPath queries (e.g. JSON.GET key "$.analysis[?(@.type=='summary')]")
  • No atomic partial updates: JSON.ARRAPPEND, JSON.ARRINSERT, JSON.NUMINCRBY, JSON.STRAPPEND, JSON.OBJKEYS, JSON.TYPE
  • No conditional sets (NX/XX on paths)
  • No nested-path JSON.SET

In practice, RedisJSON is being used as "plain Redis with a JSON-shaped value" — equivalent to SET key json.dumps(obj) / GET key → json.loads(...).

Proposed replacement

A ~10-line shim in common/redis_mgr.py:

import json

def json_set(key, value):
    return redis.set(key, json.dumps(value))

def json_get(key):
    raw = redis.get(key)
    return json.loads(raw) if raw else None

def json_mget(keys):
    raws = redis.mget(keys)
    return [json.loads(r) if r else None for r in raws]

def json_delete(key):
    return redis.delete(key)

Then mechanical search-and-replace at the 16 call sites. VconRedis is already the chokepoint for most of them so the change concentrates there.

Trade-offs

Upside: portability, no module licensing, no test-env friction, smaller image.

Downside: loses the theoretical future option to do server-side JSONPath. No current code does that. "We might want to someday" is a weak constraint and easy to reintroduce if a real need appears.

Suggested scope

  • Replace the 16 call sites
  • Drop redis/redis-stack:latest in docker-compose.yml in favor of redis:7-alpine (or current stable plain Redis)
  • Verify test_api.py passes locally against plain Redis
  • One PR; should be ~50 LOC total

Audit recipe (reusable)

grep -rn "\.json()\." conserver/ common/ api/ \
  | grep -v "\.json()\.dumps\|response\.json\|jsonresponse" \
  | grep -oE '\.json\(\)\.[a-z_]+' \
  | sort | uniq -c | sort -rn

Generalizes to any "is this datastore extension actually used?" question.

Context

Surfaced during the refactor/speckit-compliance-and-redis-abstraction work where every link was migrated off raw redis_mgr.redis access onto VconQueue / VconRedis. With reads/writes now flowing through a single chokepoint, swapping the underlying storage primitive becomes a localized change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions