CRUMB a card from devarno-cloud

Tier Model & Resource Limits

skyflow beginner 4 min read

ELI5

Skyflow has four tiers like airline seating: Drift (free, basic), Lift ($5), Jet ($9), and Orbit ($25, unlimited). Each tier upgrades how many timelines you can analyse per hour and how much XP each analysis earns. The tier lives on the user row and is enforced by middleware before the endpoint runs.

Technical Deep Dive

Enum Definitions

The tier enum is declared in two places and they MUST stay aligned:

SourceType
proto/skyflow/common/v1/types.protoenum Tier { TIER_UNSPECIFIED, TIER_DRIFT, TIER_LIFT, TIER_JET, TIER_ORBIT }
database/postgres/001_core_schema.sqlCREATE TYPE tier AS ENUM ('drift','lift','jet','orbit')

users.tier defaults to 'drift' on insert.

TierLimits Message

classDiagram
class TierLimits {
+Tier tier
+int32 requests_per_hour
+int32 max_concurrent
+int32 posts_analyzed
+int32 audience_results
+int32 api_calls_monthly
+bool real_time_updates
+bool api_access
}
class Tier {
<<enum>>
DRIFT
LIFT
JET
ORBIT
}
TierLimits --> Tier

Documented Per-Tier Quotas

From services/01-core-api.md § 4.3:

TierTimeline analyses / hourXP multiplierStreak freezes
DRIFT51.0×0
LIFT201.5×0
JET502.0×3 / month
ORBITunlimited3.0×3 / month

XP multipliers are stored in xp_transactions.xp_multiplier as integer hundredths (100 = 1.0×, 300 = 3.0×).

Enforcement Path

flowchart TD
R[Request] --> JWT[JWTAuth middleware]
JWT --> T[RequireTier or rate limiter]
T -->|tier OK & under quota| H[Handler]
T -->|tier too low| F1[403 upgrade_required]
T -->|over quota| F2[429 rate_limit_exceeded]

The RequireTier(minTier) and RateLimit() Fiber middlewares both look up the tier from c.Locals("user_id") → cached users.tier row; rate windows are stored as ratelimit:{user_id}:{path} Redis counters with 1-hour TTL.

Key Terms

  • TIER_UNSPECIFIED → proto3 default zero value; never written to the DB, used as a “field absent” sentinel
  • xp_multiplier (100 = 1.0×) → integer-encoded multiplier in the XP ledger to avoid float drift
  • Streak freeze → JET+ feature granting 3 missed-day passes per calendar month (see skyflow-011)
  • Real-time updates flagTierLimits.real_time_updates gates SSE access for low-tier users

Q&A

Q: What tier is created when a user signs up? A: TIER_DRIFT. The DB column has DEFAULT 'drift' and UserRegisteredEvent.tier carries that value through the event chain.

Q: How is the rate limit reset window implemented? A: A Redis counter keyed ratelimit:{user_id}:{path} is INCR’d on every request; on the first hit EXPIRE is set to 1 hour. There is no rolling window — it’s a fixed bucket.

Q: Where is the per-tier XP multiplier applied? A: In the Gamification Service when writing the xp_transactions row — it stores the multiplier integer (100/150/200/300) so downstream sums stay exact.

Q: Can a user be on two tiers at once? A: No. subscriptions has the partial unique constraint one_active_subscription_per_user UNIQUE (user_id) WHERE status = 'active'.

Examples

Think of tiers as gym memberships. DRIFT is the free trial — you can use a few machines a day. LIFT is the basic membership; JET is premium with three “skip-day” passes. ORBIT is the all-access founder pass: come whenever you want, lift triple the points.

neighbors on the map