Skip to content

feat!: normalize timestamps from TEXT to epoch millisecond INTEGER#75

Merged
deepjoy merged 1 commit into
mainfrom
timestamp-epoch
Mar 22, 2026
Merged

feat!: normalize timestamps from TEXT to epoch millisecond INTEGER#75
deepjoy merged 1 commit into
mainfrom
timestamp-epoch

Conversation

@deepjoy
Copy link
Copy Markdown
Owner

@deepjoy deepjoy commented Mar 22, 2026

Summary

  • Convert all 9 timestamp columns (created_at, started_at, completed_at, expires_at, run_after) across tasks and task_history tables from TEXT to INTEGER epoch milliseconds
  • Replace all datetime('now') / strftime(...) SQL expressions with bound i64 parameters computed in Rust, and all TEXT formatting (format("%Y-%m-%d %H:%M:%S")) with .timestamp_millis() calls
  • Replace the hand-rolled parse_datetime / parse_2 / parse_4 / parse_frac_nanos TEXT parser with epoch_ms() / from_epoch_ms() helpers
  • Add explicit completed_at bind to insert_history (previously relied on DEFAULT (datetime('now')) which is removed)
  • Fix idx_tasks_expires partial index to include 'blocked' status (pre-existing bug — expire_tasks() queries status IN ('pending', 'paused', 'blocked') but the index only covered ('pending', 'paused'))
  • TTL arithmetic now uses integer math (? + (ttl_seconds * 1000)) instead of datetime('now', '+' || ttl_seconds || ' seconds')

Motivation

  • SQL arithmetic: integer comparisons and addition replace strftime('%s', col) conversions — simpler, faster, less error-prone
  • Index efficiency: INTEGER range scans outperform TEXT string comparisons for expiry, run_after gating, and pruning
  • Format consistency: submission run_after used second precision (%H:%M:%S) while retry backoff used millisecond precision (%H:%M:%S%.3f) — epoch millis unify both
  • Forward compatibility: plan 042 (pause-by-group) will add new columns as epoch INTEGER; converting existing columns now avoids a two-format codebase

Breaking change

Storage-level only — public Rust types remain DateTime<Utc>. Requires database recreation (no live migration). The active tasks table is transient (work queue drains) and task_history is diagnostic, so data loss on recreation is acceptable.

Replace all nine timestamp columns (created_at, started_at, completed_at,
expires_at, run_after) across tasks and task_history tables from TEXT
format to INTEGER epoch milliseconds. This unifies the inconsistent
second/millisecond TEXT formats and enables direct integer arithmetic
in SQL (e.g. TTL computation via `? + (ttl_seconds * 1000)`) instead
of strftime/datetime conversions.

Breaking storage change — requires database recreation.

- Add epoch_ms/from_epoch_ms helpers, remove parse_datetime and friends
- Bind all timestamps from Rust as i64 epoch millis
- Replace all datetime('now')/strftime(...) SQL expressions with bound params
- Add explicit completed_at to insert_history (was DEFAULT-based)
- Fix idx_tasks_expires partial index to include 'blocked' status
@deepjoy deepjoy enabled auto-merge (squash) March 22, 2026 17:55
@deepjoy deepjoy merged commit 4d1073b into main Mar 22, 2026
2 checks passed
@github-actions github-actions Bot mentioned this pull request Mar 22, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Benchmark Comparison

Click to expand
group                                       current
-----                                       -------
backoff_delay/constant                      1.00     49.9±0.48ns        ? ?/sec
backoff_delay/exponential                   1.00    187.1±0.71ns        ? ?/sec
backoff_delay/exponential_jitter            1.00    272.6±5.17ns        ? ?/sec
backoff_delay/linear                        1.00     76.1±0.78ns        ? ?/sec
batch_submit_1000                           1.00     34.7±3.44ms        ? ?/sec
byte_progress/byte_reporting_500            1.00    258.8±5.52ms        ? ?/sec
byte_progress/noop_500                      1.00    255.7±5.06ms        ? ?/sec
byte_progress_snapshot_100_tasks            1.00     85.4±2.44ms        ? ?/sec
concurrency_scaling/1                       1.00    389.5±6.60ms        ? ?/sec
concurrency_scaling/2                       1.00    326.6±5.14ms        ? ?/sec
concurrency_scaling/4                       1.00    285.5±5.30ms        ? ?/sec
concurrency_scaling/8                       1.00   267.0±30.34ms        ? ?/sec
count_by_tags/100                           1.00    132.8±5.10µs        ? ?/sec
count_by_tags/1000                          1.00    219.9±6.06µs        ? ?/sec
count_by_tags/5000                          1.00    595.1±6.63µs        ? ?/sec
dep_chain_dispatch/10                       1.00     11.9±0.14ms        ? ?/sec
dep_chain_dispatch/25                       1.00     28.8±0.47ms        ? ?/sec
dep_chain_dispatch/50                       1.00     57.1±0.95ms        ? ?/sec
dep_chain_submit/10                         1.00      3.5±0.21ms        ? ?/sec
dep_chain_submit/200                        1.00     81.6±5.77ms        ? ?/sec
dep_chain_submit/50                         1.00     18.2±1.31ms        ? ?/sec
dep_fan_in_dispatch/10                      1.00      7.8±0.10ms        ? ?/sec
dep_fan_in_dispatch/100                     1.00     56.4±1.28ms        ? ?/sec
dep_fan_in_dispatch/50                      1.00     29.4±0.53ms        ? ?/sec
dispatch_and_complete_1000                  1.00    513.0±9.23ms        ? ?/sec
dispatch_group_scaling/1                    1.00    442.4±7.17ms        ? ?/sec
dispatch_group_scaling/10                   1.00    440.9±5.84ms        ? ?/sec
dispatch_group_scaling/100                  1.00    441.3±7.28ms        ? ?/sec
dispatch_group_scaling/50                   1.00    440.2±5.98ms        ? ?/sec
dispatch_no_groups_500                      1.00    256.2±4.98ms        ? ?/sec
dispatch_one_group_500                      1.00    442.6±6.57ms        ? ?/sec
dispatch_permanent_failure_500              1.00    388.4±4.94ms        ? ?/sec
history_by_type/100                         1.00   861.4±25.84µs        ? ?/sec
history_by_type/1000                        1.00   917.9±25.18µs        ? ?/sec
history_by_type/5000                        1.00   952.7±22.58µs        ? ?/sec
history_query/100                           1.00   539.2±16.71µs        ? ?/sec
history_query/1000                          1.00    546.1±9.02µs        ? ?/sec
history_query/5000                          1.00    554.2±8.96µs        ? ?/sec
history_stats/100                           1.00    151.1±1.25µs        ? ?/sec
history_stats/1000                          1.00    359.9±1.42µs        ? ?/sec
history_stats/5000                          1.00   1274.5±4.47µs        ? ?/sec
mixed_priority_dispatch_500                 1.00    284.6±5.62ms        ? ?/sec
peek_next/100                               1.00    123.2±3.81µs        ? ?/sec
peek_next/1000                              1.00    123.9±4.21µs        ? ?/sec
peek_next/5000                              1.00    123.5±3.53µs        ? ?/sec
query_by_tags/100                           1.00  1164.3±91.25µs        ? ?/sec
query_by_tags/1000                          1.00     10.9±1.55ms        ? ?/sec
query_by_tags/5000                          1.00     52.3±4.37ms        ? ?/sec
retryable_dead_letter/constant              1.00    159.3±1.85ms        ? ?/sec
retryable_dead_letter/exponential           1.00    159.2±1.81ms        ? ?/sec
retryable_dead_letter/exponential_jitter    1.00    159.7±3.68ms        ? ?/sec
retryable_dead_letter/linear                1.00    159.3±2.64ms        ? ?/sec
submit_1000_tasks                           1.00    187.4±6.69ms        ? ?/sec
submit_dedup_hit_1000                       1.00   247.0±10.45ms        ? ?/sec
submit_with_tags/0                          1.00     94.1±4.51ms        ? ?/sec
submit_with_tags/10                         1.00   248.0±14.20ms        ? ?/sec
submit_with_tags/20                         1.00   404.1±24.88ms        ? ?/sec
submit_with_tags/5                          1.00   171.9±10.54ms        ? ?/sec
tag_values/100                              1.00    138.9±6.23µs        ? ?/sec
tag_values/1000                             1.00    200.2±6.11µs        ? ?/sec
tag_values/5000                             1.00    473.2±7.77µs        ? ?/sec

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