perf(postgres-driver): Fast date, timestamp/tz parsers#10737
Conversation
|
Claude finished @ovr's task in 2m 19s —— View job Code Review: perf(postgres-driver) — Fast date/timestamp parsers
Overall: This is a clean, well-motivated performance optimization. The implementation is correct and the approach is sound. Replacing CorrectnessThe three parsers produce output identical to the old
The fractional-second pad/truncate trick ( Subclass Impact
PerformanceThe Items to ConsiderI left three inline comments:
SummaryThis is a high-quality PR — well-scoped, well-tested, well-benchmarked. The issues above are all minor/nit-level. Nice win dropping the |
`getTypeParser` routed all three timestamp OIDs through a single `moment.utc(val).format(moment.HTML5_FMT.DATETIME_LOCAL_MS)` call, which was the dominant per-row cost for result sets with any timestamp columns. Each OID has a fixed text shape, so we can parse directly: - OID 1082 (DATE): single template literal appending `T00:00:00.000`. - OID 1114 (TIMESTAMP): slice + pad/truncate fractional, no TZ scan. - OID 1184 (TIMESTAMPTZ): parse fields, `Date.UTC` for the offset shift, hand-rolled `pad2`/`pad3` for formatting (padStart measured ~15-20% slower in the hot path). Microbenchmark (2M iters/variant, Node, `bench-timestampTypeParser.js`): | input | moment | new | speedup | | -------------------------- | ------- | ------ | ------- | | DATE | ~2700ns | ~7ns | ~400x | | TIMESTAMP (no frac) | ~4300ns | ~24ns | ~180x | | TIMESTAMP (ms) | ~4800ns | ~41ns | ~115x | | TIMESTAMP (us -> ms trunc) | ~4900ns | ~40ns | ~120x | | TIMESTAMPTZ (+02) | ~4700ns | ~360ns | ~13x | | TIMESTAMPTZ (ms +05:30) | ~5500ns | ~480ns | ~11x | | TIMESTAMPTZ (us -03) | ~5300ns | ~400ns | ~13x |
04e1852 to
1af79bc
Compare
45cf24d to
39393cf
Compare
39393cf to
941e778
Compare
Codecov Report✅ All modified and coverable lines are covered by tests.
Additional details and impacted files@@ Coverage Diff @@
## master #10737 +/- ##
===========================================
- Coverage 83.40% 58.02% -25.39%
===========================================
Files 251 215 -36
Lines 75297 16689 -58608
Branches 0 3358 +3358
===========================================
- Hits 62802 9683 -53119
+ Misses 12495 6514 -5981
- Partials 0 492 +492
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
getTypeParserrouted all three timestamp OIDs through a singlemoment.utc(val).format(moment.HTML5_FMT.DATETIME_LOCAL_MS)call, which was the dominant per-row cost for result sets with any timestamp columns. Each OID has a fixed text shape, so we can parse directly:T00:00:00.000.+00on the wire — a fast path detects all-zero offsets, strips the suffix, and delegates to the TIMESTAMP parser. Non-UTC sessions fall through toDate.UTC+ hand-rolledpad2/pad3/pad4formatting, withsetUTCFullYearhandling theDate.UTC"years 0-99 remapped to 1900+year" legacy quirk for pre-100 AD dates.Microbenchmark (Node 22, Apple Silicon;
momentbaseline =moment.utc(val).format(moment.HTML5_FMT.DATETIME_LOCAL_MS)).Warm (N=2M, with warmup)
Steady-state throughput after V8's JIT has fully optimized both paths — representative of large result sets.
Cold (N=100, no warmup)
No warmup, fresh module cache — representative of the first few rows / short-lived queries. moment sits at ~15 µs/call cold (its locale tables and regex compilation happen lazily on first use), which is where the biggest absolute wins live.
Real showcase of the problem, parsing dates tooks ~10% of the whole request, spending 390ms.