-
Notifications
You must be signed in to change notification settings - Fork 24
feat: list tenant using RediSearch #616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
# Conflicts: # internal/util/testinfra/redis.go
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
My understanding is that those aren't computed and are instead stored on the object itself. If that's not the case, then I think we should seriously consider refactoring to do so. Are we currently pulling all the tenant destinations in order to compute those fields of GET /:tenant_id? |
|
Sort of, but let me explain the logic to make sure we're on the same page before we decide on the next step. Here are the relevant Redis keys structure: schema for DestinationSummary type DestinationSummary struct {
ID string `json:"id"`
Type string `json:"type"`
Topics Topics `json:"topics"`
Filter Filter `json:"filter,omitempty"`
Disabled bool `json:"disabled"`
}We use the hash that store all DestinationSummary in event ingestion to match the event with the right destination. We can merge this into the Tenant entity itself, but then Tenant will be in the hot path of event ingestion. It's not super significant from a memory standpoint given Tenant should be small, but I figured it's better to have them as 2 different hashes. Now back to the question, on Tenant Retrieve, we do this query logic To refactor and avoid computing This should be fairly doable I think, with the caveat above. Let me know what you think and we can certainly update this accordingly. |
|
That's corrects and actually thought it had already been implemented without fetching the destinations. My bad I must have missed it in the review. |
Summary
Adds a new
GET /tenantsendpoint for listing tenants with cursor-based pagination. This feature requires Redis with RediSearch module and auto-detects availability at startup.Feature
created_at(ascending or descending)next/prevcursorsdestinations_countandtopics)501 Not Implementedwhen RediSearch is unavailableMigration
Existing deployments must run these migrations before using this feature:
002_timestamps: Converts timestamp fields from RFC3339 strings to Unix millisecond timestamps. The RediSearch index requires consistent numeric values for correct sorting.
003_entity: Adds
entityfield to tenant and destination records. This field is used by the RediSearch index FILTER to distinguish tenants from destinations (both share thetenant:key prefix).Implementation
FT._LISTcreated_at NUMERIC SORTABLEPagination Behavior
Forward traversal (
nextcursor): Returns items OLDER than cursor timestamp (exclusive)Backward traversal (
prevcursor): Returns items NEWER than cursor timestamp (exclusive)Note: When you reach an empty page, both cursors are empty. To traverse backward, use the
prevcursor from the last non-empty page. The cursors use exclusive boundaries to prevent duplicate items.Cursor Format
Cursors are opaque, URL-safe strings that encode pagination state:
tntv01:<unix_timestamp_ms>(versioned for future compatibility)eD9YwjRTAWwJL3MZcV36caQdecodes totntv01:1705314600000The version prefix allows cursor format changes without breaking existing clients. Format:
<entity><version>where:tntv01= tenant v01evtv01for events,delv01for deliveries, etc.Tests
Known Limitations
1. Timestamp Collision
Keyset pagination relies on
created_attimestamps for ordering. If many tenants share the exact same millisecond timestamp, pagination may not traverse all records. This is extremely unlikely in normal operation due to millisecond precision—even in high-throughput systems, tenant creation is typically infrequent enough that collisions are theoretical rather than practical.2. Shared Index with Destinations
The RediSearch tenant index includes both tenant and destination records because they share the same key prefix (
tenant:{id}:*). We use anentityfield to distinguish them:entity: "tenant"entity: "destination"The index includes
FILTER '@entity == "tenant"'but this doesn't work as expected. The actual filtering is done at query time with@entity:{tenant}in every FT.SEARCH query.The alternative would be changing the key prefix structure (e.g.,
tenants:{id}vstenant:{id}:destination:{destId}), but this would be a breaking change for existing data and require a more complex migration.TODO