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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions