CRUMB a card from devarno-cloud

Achievement Unlock Pipeline

skyflow intermediate 5 min read

ELI5

Achievements are rows in an achievements table with a criteria_type (timeline_count, xp_total, streak_days, …) and a criteria_threshold. A background job sweeps every five minutes over recently active users and inserts into user_achievements when the threshold is crossed. The unique (user_id, achievement_id) constraint guarantees one unlock per achievement no matter how many times the checker runs.

Technical Deep Dive

Tables

stateDiagram-v2
[*] --> Locked
Locked --> Progressing: criteria partial
Progressing --> Unlocked: threshold met
Locked --> Unlocked: threshold met first sweep
Unlocked --> [*]

achievements (002_gamification_schema.sql):

ColumnTypeNotes
idUUID PK
slugTEXT UNIQUEURL-safe key (first_flow, streak_legend)
rarityenum (common..legendary)
xp_rewardintawarded on unlock
tier_requiredenum tier NULLminimum tier to be eligible
criteria_typetexttimeline_count | xp_total | streak_days | unique_users_tracked | network_size | referrals | special
criteria_thresholdintnumeric goal
criteria_configJSONBtype-specific extras

user_achievements:

CONSTRAINT unique_user_achievement UNIQUE (user_id, achievement_id)

Sweep Sequence

sequenceDiagram
autonumber
participant CRON as 5-min cron
participant G as gamification-service
participant PG as Postgres
participant N as NATS GAMIFICATION
CRON->>G: tick
G->>PG: SELECT DISTINCT user_id FROM xp_transactions WHERE created_at > now()-'5 min'
loop per active user
G->>PG: SELECT achievement_id FROM user_achievements WHERE user_id=?
G->>PG: SELECT * FROM achievements WHERE id NOT IN (locked set)
loop per candidate
G->>G: evaluate criteria_type vs threshold
alt threshold met
G->>PG: INSERT user_achievements (...) ON CONFLICT DO NOTHING
G->>PG: INSERT xp_transactions (+xp_reward, event_type='achievement_unlocked')
G->>N: publish events.achievement.unlocked
G->>N: publish events.realtime.achievement_unlocked
end
end
end

Criteria Resolution

criteria_typeSource query / signal
timeline_countSELECT COUNT(*) FROM timeline_analyses WHERE user_id=?
xp_totalSELECT total_xp FROM user_xp_summary WHERE user_id=?
streak_daysSELECT current_streak FROM streaks WHERE user_id=?
unique_users_trackedDISTINCT target_did count from timeline_analyses
network_sizelatest network_snapshots.follower_count
referralsnot yet schema-backed; reserved
specialmanual / event-driven (e.g. early_adopter, benefactor)

Seed Achievements (excerpt)

slugrarityxp_rewardcriteria
first_flowcommon50timeline_count ≥ 1
flow_masteruncommon500timeline_count ≥ 100
xp_legendlegendary10000xp_total ≥ 100000
streak_legendlegendary5000streak_days ≥ 100
early_adopterrare500special (manual unlock)
benefactoruncommon300special (subscription_created consumer)

Key Terms

  • special criteria_type → not auto-evaluated; unlocked by a specific event handler (e.g. subscription created → benefactor)
  • rarity → cosmetic / sort-order metadata; not used for unlock logic
  • tier_required → eligibility gate; locked achievements still appear in progress lists but cannot fire below the tier
  • ON CONFLICT DO NOTHING → idempotency on the unique (user_id, achievement_id) constraint

Q&A

Q: What stops an achievement from unlocking twice if the checker is racing with itself? A: The unique_user_achievement UNIQUE constraint plus ON CONFLICT DO NOTHING — at most one row can ever exist for a given (user_id, achievement_id).

Q: How often does the checker actually run? A: Every 5 minutes (background goroutine in gamification-service). It only scans users with XP activity in the last 5 minutes to bound the work.

Q: How is the benefactor achievement unlocked? A: Gamification consumes events.subscription.created; if the new tier is paid, the special-handler unlocks benefactor directly without going through criteria evaluation.

Q: Can an achievement award negative XP? A: The schema permits it (xp_reward INT) and xp_transactions.xp_delta is signed, but no seed achievement does. Treat it as theoretical.

Examples

Like Duolingo badges. The system periodically asks “did you cross 7-day streak today?” and stamps a foil sticker into a sticker book that physically cannot hold two of the same sticker. The sticker hand-out also drops loyalty points into the same XP ledger.

neighbors on the map