Skip to content

Read integer %Y%m%d times without formatting them as text - #223

Open
Vedaant-J wants to merge 4 commits into
mainfrom
vedaant/int-ymd-fastparse
Open

Vedaant-J wants to merge 4 commits into
mainfrom
vedaant/int-ymd-fastparse

Conversation

@Vedaant-J

@Vedaant-J Vedaant-J commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

An integer time column declared with timestamp_format: "%Y%m%d" (values like 20240301) is read in RowAccess::timestamp_in by formatting each integer as a string (a heap allocation) and parsing it with chrono, first as a date-time, which always fails for a date-only format, and then as a date. That costs roughly 300 ns per value on a per-row path. Ingestion runs it for every row of such a column, for example when the column is an edge's sort key time. Temporal queries will also run it for every candidate row once they read declared formats (#205).

String columns with the same format already go through a byte-level fast parser (TimestampFormatParser), but integer columns never reached it.

The fix: before the text round-trip, an eight-digit integer under the %Y%m%d parser is split into year, month and day arithmetically and checked with chrono's NaiveDate::from_ymd_opt. Everything else takes the existing text path unchanged: other formats, values that are not eight digits, and invalid dates such as 20231345 or 20230229. The existing string fast parser parse_compact_ymd_fast is not reused because it does not validate month or day (it accepts month 13).

Change map

#223 (comment)

Measured

Synthetic users -> events graph with 10M events and an int64 t_ymd column declared %Y%m%d, on a 64-core box using release wheels. Each figure is a median of 5 runs.

Case main this PR
Ingest with t_ymd as the edge sort key time 6.5 s 4.9 s

Temporal queries on main read this column as epoch seconds and never reach this path. With #205 merged locally underneath, the cases that read the column from row data speed up. The sort-key path doesn't parse it, so that case is unchanged. These temporal numbers come from a single round and may be off by a few percent.

Query (8,192 or 65,536 anchors, 30-day window) #205 #205 + this PR
query_temporal, time column not the sort key 3415 ms 2008 ms
query_temporal_lengths, time column not the sort key 3292 ms 1882 ms
query_temporal, time column is the sort key 2036 ms 1577 ms
query_temporal_lengths, time column is the sort key 992 ms 987 ms

Back-compat

Results are identical. chrono's %Y%m%d reads exactly four year digits, then two month digits and two day digits, and accepts the value exactly when from_ymd_opt does. So the new path returns a value exactly when the old one did, with the same seconds. A sweep over all 90,000,000 eight-digit values against chrono 0.4.45 found no mismatches. Shorter values that chrono also reads as dates (for example the seven-digit 2024031, which it reads as 2024-03-01) still take the old path.

Indexes ingested on main and on this branch return the same temporal results for every user at three cutoffs: the same lengths, row ids and times, through the sort-key path. Their files are not byte-comparable because ingest output differs from run to run on main too.

Testing Done

  • New integer_yyyymmdd_matches_text_parse compares the result against the text parse (falling back to the epoch reading when it fails) over valid and invalid months and days in six years, plus 7-, 9-digit, zero, negative and i64 extreme values.
  • cargo test -p diskgraph-rowpack (319 tests), the timestamp and date tests of cargo test -p diskgraph-ingest, and cargo clippy -p diskgraph-rowpack --all-targets -- -D warnings pass.

Hotfix Context

Not a hotfix or backport.

Checklist

  • I listed all the breaking changes introduced by the PR: none.
  • I made sure to update examples and documentation with my changes: not needed.
  • I have added and modified unit tests required to cover these changes.

🤖 Generated with Claude Code

Vedaant-J and others added 3 commits November 5, 2026 18:17
An integer time column declared with timestamp_format "%Y%m%d" was
formatted as a string and parsed by chrono for every row. Eight-digit
values that form a valid date now go straight to the date; everything
else takes the existing text parse, so results are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@copy-pr-bot

copy-pr-bot Bot commented Nov 5, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@Vedaant-J

Copy link
Copy Markdown
Contributor Author

Change map

Observed failure: reading an integer %Y%m%d time column formats every value as a string and parses it with chrono, about 300 ns per row in ingestion and temporal queries.

Existing behavior: RowAccess::timestamp_in sends integers with a format through value.to_string() and parse_timestamp_text_parts, falling back to an epoch reading when that fails. String columns already use the compiled TimestampFormatParser.

Smallest sufficient change: for the compiled %Y%m%d parser only, read eight-digit integers that form a valid date arithmetically, before the text round-trip. All other values keep the existing path.

Focused verification: a unit test compares against the text parse over valid and invalid dates and edge values; an exhaustive sweep of all eight-digit values found no mismatches; indexes ingested on main and on this branch return identical temporal results.

Stopping condition: integer %Y%m%d values no longer allocate or call chrono's string parser, with results unchanged. Other formats are out of scope.

File Function / scope What changed Why
packages/diskgraph/diskgraph-rowpack/src/parser.rs TimestampFormatParser::parse_compact_ymd_int New: epoch seconds of an eight-digit integer that %Y%m%d reads as a valid date. Skip the text round-trip with the same result.
packages/diskgraph/diskgraph-rowpack/src/access.rs RowAccess::timestamp_in, integer arm Try parse_compact_ymd_int before formatting the integer as text. Use the fast read on the per-row path.
packages/diskgraph/diskgraph-rowpack/src/tests_unit.rs integer_yyyymmdd_matches_text_parse New test. Pin parity with the text parse.

@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests November 5, 2026 18:39 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Explain Change →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 9ec4b54 and 8797d26.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

When a format hint is present, timestamp_in first tries to parse an eligible eight-digit integer as a %Y%m%d date. Valid dates convert to midnight UTC epoch seconds. Other values continue through the existing formatted-text parsing and epoch-unit fallback. A unit test compares hinted integer parsing with text parsing and parsing without a hint across valid and invalid inputs.

Merge Risk: Minimal · previous review 9ec4b

No merge-blocking issue was identified in the integer date fast path.

Pre-merge checks: Passed 2 · Inconclusive 1

Failed Failed checks: 1 inconclusive

Check name Status Explanation Resolution
Change Map Design Review Inconclusive The reviewed code is available and shows a focused three-file change: TimestampFormatParser::parse_compact_ymd_int validates eight-digit %Y%m%d integers, RowAccess::timestamp_in uses it before t… Provide the author-authored change-map conversation comment, including its table and the required failure, existing-behavior, smallest-change, verification, and stopping-condition details. Then reassess each proposed change against the insp…
Passed Passed checks: 2 passed

Check name Status Explanation
Pr Size Passed The pull request changes 3 files. This count does not exceed 40.
Public Resource Access Passed The pull request adds no URL, repository location, or package source location. The changed lines only add Rust code, comments, and unit tests. No dependency or package manifest changes were made, so t…
Full details: Public Resource Access

Explanation

The pull request adds no URL, repository location, or package source location. The changed lines only add Rust code, comments, and unit tests. No dependency or package manifest changes were made, so there is no external resource to verify.

Full details: Change Map Design Review

Explanation

The reviewed code is available and shows a focused three-file change: TimestampFormatParser::parse_compact_ymd_int validates eight-digit %Y%m%d integers, RowAccess::timestamp_in uses it before the existing text path, and a parity test covers fallback cases. However, the required PR conversation comment beginning &lt;!-- kumo-pr-change-map --&gt; is not present in the supplied context or repository. Therefore, I cannot verify the proposed design's per-change rationale, observed failure, focused verification, optional refinements, or stopping condition in that same comment.

Resolution

Provide the author-authored change-map conversation comment, including its table and the required failure, existing-behavior, smallest-change, verification, and stopping-condition details. Then reassess each proposed change against the inspected diff.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 6aafd69 and 9ec4b54.

Files selected for processing: 3

  • packages/diskgraph/diskgraph-rowpack/src/access.rs
  • packages/diskgraph/diskgraph-rowpack/src/parser.rs
  • packages/diskgraph/diskgraph-rowpack/src/tests_unit.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Vedaant-J
Vedaant-J temporarily deployed to gha-kumo-workflow-tests November 6, 2026 01:06 — with GitHub Actions Inactive

This branch was previously deployed

1 inactive deployment
gha-kumo-workflow-tests — 8797d266 Deployed Nov 6, 2026 by Vedaant-J via import #1621
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.

2 participants