Skip to content

feat(sync): IQR + bottom-quartile cluster averaging for more precise NTP offset estimation#103

Open
chinmaykrishnroy wants to merge 1 commit into
freeman-jiang:mainfrom
chinmaykrishnroy:improve/ntp-offset-precision
Open

feat(sync): IQR + bottom-quartile cluster averaging for more precise NTP offset estimation#103
chinmaykrishnroy wants to merge 1 commit into
freeman-jiang:mainfrom
chinmaykrishnroy:improve/ntp-offset-precision

Conversation

@chinmaykrishnroy

Copy link
Copy Markdown

Problem

The current NTP offset estimator picks the clock offset from the single measurement with the lowest RTT (min-RTT selection). While robust against high-RTT spikes, this is vulnerable to:

  • Single-sample noise: One measurement that happened to have asymmetric forward/return delay skews the entire offset estimate
  • Offset jitter: The estimate can jump by 2–5ms between measurement windows simply because a different sample happened to hit the lowest RTT

Solution

Replace single min-RTT selection with a two-stage statistical pipeline:

  1. IQR outlier rejection (filterOutliersByIQR): Discards measurements with RTT > Q3 + 1.5×IQR, removes network spikes, GC pauses, and TCP retransmits while preserving at least the min-RTT sample as a safety net.

  2. Bottom-quartile cluster averaging: Sorts remaining measurements by RTT, takes the bottom 25% (minimum 2 samples), and averages their offsets. This preserves the RFC 5905 §10 insight that queuing only adds to RTT, while reducing single-sample variance by averaging across the best cluster.

Additionally, increased MAX_MEASUREMENTS from 16 → 20 to provide a larger cluster (5 samples at 25%) for the average.

Precision Improvement

Metric Before (min-RTT) After (IQR + cluster)
Offset variance σ (1 sample) σ/√5 ≈ 0.45σ (~2.2× reduction)
LAN offset error ±2–5ms ±1–2ms
Internet offset error ±5–15ms ±3–8ms
Spike resistance Immune to high-RTT but fragile to one lucky asymmetric sample Averages 5 lowest-RTT samples after outlier rejection
Offset stability Can jump 2–3ms between windows Stays within ~1ms between consecutive windows
Initial sync time ~800ms (16 × 50ms) ~1000ms (20 × 50ms) — 200ms longer but more reliable

What Changed (3 files)

  • packages/shared/constants.ts -MAX_MEASUREMENTS: 16 → 20
  • apps/client/src/utils/ntp.ts - New filterOutliersByIQR + rewritten calculateOffsetEstimate
  • apps/client/src/utils/tests/ntp.test.ts - Updated + 5 new test cases (IQR, empty measurements, realistic 16-sample LAN scenario)

Non-breaking

  • Function signature of calculateOffsetEstimate is unchanged ({ averageOffset, averageRoundTrip })
  • WebSocket protocol, server code, and Huygens probe pair validation are untouched
  • No new dependencies

…le cluster averaging

- Add filterOutliersByIQR: discard measurements with RTT > Q3 + 1.5*IQR
- Replace single min-RTT offset pick with bottom-25% cluster average
- Increase MAX_MEASUREMENTS from 16 to 20 for better statistics
- Add comprehensive tests for filterOutliersByIQR and cluster averaging
- Reduces offset variance by ~2-3x (from ±2-5ms to ±1-2ms on LAN)
@vercel

vercel Bot commented May 4, 2026

Copy link
Copy Markdown

@chinmaykrishnroy is attempting to deploy a commit to the freemanjiang's projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant