tidaldb/.sdlc/features/pg1-baseline-comparison/review.md

3.7 KiB

Baseline Comparison Study — Code Review

Summary

The A/B experiment framework is implemented across 6 files with clean separation of concerns: types and hashing in experiment/mod.rs, report aggregation in experiment/report.rs, public API surface in db/experiment.rs, chronological profile in ranking/builtins.rs, signal index extensions in entities/user_signal.rs, and integration tests in tests/pg1_baseline.rs.

Files Reviewed

File Status Notes
tidal/src/experiment/mod.rs PASS Types, FNV-1a hash, validation, 16 unit tests
tidal/src/experiment/report.rs PASS ReportBuilder, read-only aggregation, no allocations in hot path
tidal/src/db/experiment.rs PASS Thin TidalDb wiring, validates config before use
tidal/src/ranking/builtins.rs PASS chronological() profile, Sort::New, no boosts
tidal/src/entities/user_signal.rs PASS user_signal_count + user_activity_split added
tidal/tests/pg1_baseline.rs PASS 7 integration tests covering all QA scenarios
tidal/src/lib.rs PASS Re-exports all public experiment types
tidal/src/db/mod.rs PASS mod experiment declared

Correctness

  • FNV-1a hash determinism: Uses fixed offset basis and prime constants. Combines experiment_id bytes followed by user_id little-endian bytes. No platform-dependent behavior. Verified by assign_group_is_deterministic test (1000 iterations).
  • Balanced split: 10,000 modulo buckets with configurable threshold. Unit tests verify 50/50 and 10/90 splits are within statistical tolerance (+-2.5%).
  • Division by zero: GroupMetrics handles zero views gracefully (returns 0.0 CTR, not NaN). relative_lift handles zero control explicitly (returns 0.0 when both zero, INFINITY when only control zero).
  • Config validation: Rejects out-of-range fractions, empty signal lists. Boundary values (0.0, 1.0) accepted. Validation called in all three TidalDb methods before any work.
  • Report aggregation: Signal type IDs resolved once via resolve_signal_type().ok(), not per-user. Missing signal types (unregistered) are silently skipped, yielding zero counts.
  • Return rate: Uses user_activity_split to check if user has both historical and recent activity within the return window. Users with no history are excluded from the denominator.

Design Quality

  • Read-only: The experiment module never writes to storage. All metrics derived from existing signal data.
  • No new fields on TidalDb: No additional struct fields needed. Uses existing ledger and user_signal_index.
  • Pure functions where possible: assign_group and fnv1a_hash are pure (no &self, no I/O).
  • Minimal API surface: Three methods on TidalDb (experiment_group, experiment_profile, experiment_report). Clean re-exports.
  • Separation of concerns: Types/hashing separate from aggregation separate from TidalDb wiring.

Potential Improvements (Non-blocking)

  1. O(n*m) scan in user_signal_count: Iterates all entries in UserSignalIndex to find matches for a given user. For large deployments, a secondary index keyed by user_id could improve performance. Acceptable for the PG1 pilot scope (hundreds of users).
  2. Return rate approximation: user_activity_split uses Window::OneHour for "recent" detection. This is a heuristic -- users who returned 2 hours ago but not in the last hour would be missed. The design doc acknowledges this trade-off.

Verdict

APPROVED. Clean implementation with thorough test coverage. No correctness issues. The experiment framework is read-only, adds no overhead to the hot path, and integrates cleanly with existing signal infrastructure. Non-blocking improvements noted for future scaling.