Files
meshview/docs/TIMESTAMP_MIGRATION.md
Joel Krauska e77428661c Version 3.0.0 Feature Release - Target Before Thanksgiving! (#96)
* Add alembic DB schema management (#86)

* Use alembic
* add creation helper
* example migration tool

* Store UTC int time in DB (#81)

* use UTC int time

* Remove old index notes script -- no longer needed

* modify alembic to support cleaner migrations

* add /version json endpoint

* move technical docs

* remove old migrate script

* add readme in docs:

* more doc tidy

* rm

* update api docs

* ignore other database files

* health endpoint

* alembic log format

* break out api calls in to their own file to reduce footprint

* ruff and docs

* vuln

* Improves arguments in mvrun.py

* Set dbcleanup.log location configurable

* mvrun work

* fallback if missing config

* remove unused loop

* improve migrations and fix logging problem with mqtt

* Container using slim/uv

* auto build containers

* symlink

* fix symlink

* checkout and containerfile

* make /app owned by ap0p

* Traceroute Return Path logged and displayed (#97)


* traceroute returns are now logged and /packetlist now graphs the correct data for a return route
* now using alembic to update schema
* HOWTO - Alembic

---------

Co-authored-by: Joel Krauska <jkrauska@gmail.com>

* DB Backups

* backups and cleanups are different

* ruff

* Docker Docs

* setup-dev

* graphviz for dot in Container

* Summary of 3.0.0 stuff

* Alembic was blocking mqtt logs

* Add us first/last timestamps to node table too

* Worked on /api/packet. Needed to modify
- Store.py to read the new time data
- api.py to present the new time data
- firehose.html chat.html and map.html now use the new apis and the time is the browser local time

* Worked on /api/packet. Needed to modify
- Store.py to read the new time data
- api.py to present the new time data
- firehose.html chat.html and map.html now use the new apis and the time is the browser local time

* Improves container build (#94)

* Worked on /api/packet. Needed to modify
- Store.py to read the new time data
- api.py to present the new time data
- firehose.html chat.html and map.html now use the new apis and the time is the browser local time

* Worked on /api/packet. Needed to modify
- Store.py to read the new time data
- api.py to present the new time data
- firehose.html chat.html and map.html now use the new apis and the time is the browser local time

* Worked on /api/packet. Needed to modify
- Added new api endpoint /api/packets_seen
- Modified web.py and store.py to support changes to APIs.
- Started to work on new_node.html and new_packet.html for presentation of data.

* Worked on /api/packet. Needed to modify
- Added new api endpoint /api/packets_seen
- Modified web.py and store.py to support changes to APIs.
- Started to work on new_node.html and new_packet.html for presentation of data.

* Finishing up all the pages for the 3.0 release.

Now all pages are functional.

* Finishing up all the pages for the 3.0 release.

Now all pages are functional.

* fix ruff format

* more ruff

* Finishing up all the pages for the 3.0 release.

Now all pages are functional.

* Finishing up all the pages for the 3.0 release.

Now all pages are functional.

* pyproject.toml requirements

* use sys.executable

* fix 0 epoch dates in /chat

* Make the robots do our bidding

* another compatibility fix when _us is empty and we need to sort by BOTH old and new

* Finishing up all the pages for the 3.0 release.

Now all pages are functional.

* Finishing up all the pages for the 3.0 release.

Now all pages are functional.

* Remamed new_node to node. shorter and descriptive.

* Remamed new_node to node. shorter and descriptive.

* Remamed new_node to node. shorter and descriptive.

* Remamed new_node to node. shorter and descriptive.

* Remamed new_node to node. shorter and descriptive.

* Remamed new_node to node. shorter and descriptive.

* More changes... almost ready for release.

Ranamed 2 pages for easy or reading.

* Fix the net page as it was not showing the date information

* Fix the net page as it was not showing the date information

* Fix the net page as it was not showing the date information

* Fix the net page as it was not showing the date information

* ruff

---------

Co-authored-by: Óscar García Amor <ogarcia@connectical.com>
Co-authored-by: Jim Schrempp <jschrempp@users.noreply.github.com>
Co-authored-by: Pablo Revilla <pablorevilla@gmail.com>
2025-11-28 11:17:20 -08:00

5.7 KiB

High-Resolution Timestamp Migration

This document describes the implementation of GitHub issue #55: storing high-resolution timestamps as integers in the database for improved performance and query efficiency.

Overview

The meshview database now stores timestamps in two formats:

  1. TEXT format (import_time): Human-readable ISO8601 format with microseconds (e.g., 2025-03-12 04:15:56.058038)
  2. INTEGER format (import_time_us): Microseconds since Unix epoch (1970-01-01 00:00:00 UTC)

The dual format approach provides:

  • Backward compatibility: Existing import_time TEXT columns remain unchanged
  • Performance: Fast integer comparisons and math operations
  • Precision: Microsecond resolution for accurate timing
  • Efficiency: Compact storage and fast indexed lookups

Database Changes

New Columns Added

Three tables have new import_time_us columns:

  1. packet.import_time_us (INTEGER)

    • Stores when the packet was imported into the database
    • Indexed for fast queries
  2. packet_seen.import_time_us (INTEGER)

    • Stores when the packet_seen record was imported
    • Indexed for performance
  3. traceroute.import_time_us (INTEGER)

    • Stores when the traceroute was imported
    • Indexed for fast lookups

New Indexes

The following indexes were created for optimal query performance:

CREATE INDEX idx_packet_import_time_us ON packet(import_time_us DESC);
CREATE INDEX idx_packet_from_node_time_us ON packet(from_node_id, import_time_us DESC);
CREATE INDEX idx_packet_seen_import_time_us ON packet_seen(import_time_us);
CREATE INDEX idx_traceroute_import_time_us ON traceroute(import_time_us);

Migration Process

For Existing Databases

Run the migration script to add the new columns and populate them from existing data:

python migrate_add_timestamp_us.py [database_path]

If no path is provided, it defaults to packets.db in the current directory.

The migration script:

  1. Checks if migration is needed (idempotent)
  2. Adds import_time_us columns to the three tables
  3. Populates the new columns from existing import_time values
  4. Creates indexes for optimal performance
  5. Verifies the migration completed successfully

For New Databases

New databases created with the updated schema will automatically include the import_time_us columns. The MQTT store module populates both columns when inserting new records.

Code Changes

Models (meshview/models.py)

The ORM models now include the new import_time_us fields:

class Packet(Base):
    import_time: Mapped[datetime] = mapped_column(nullable=True)
    import_time_us: Mapped[int] = mapped_column(BigInteger, nullable=True)

MQTT Store (meshview/mqtt_store.py)

The data ingestion logic now populates both timestamp columns using UTC time:

now = datetime.datetime.now(datetime.timezone.utc)
now_us = int(now.timestamp() * 1_000_000)

# Both columns are populated
import_time=now,
import_time_us=now_us,

Important: All new timestamps use UTC (Coordinated Universal Time) for consistency across time zones.

Using the New Timestamps

Example Queries

Query packets from the last 7 days:

-- Old way (slower)
SELECT * FROM packet 
WHERE import_time >= datetime('now', '-7 days');

-- New way (faster)
SELECT * FROM packet
WHERE import_time_us >= (strftime('%s', 'now', '-7 days') * 1000000);

Query packets in a specific time range:

SELECT * FROM packet
WHERE import_time_us BETWEEN 1759254380000000 AND 1759254390000000;

Calculate time differences (in microseconds):

SELECT 
    id,
    (import_time_us - LAG(import_time_us) OVER (ORDER BY import_time_us)) / 1000000.0 as seconds_since_last
FROM packet
LIMIT 10;

Converting Timestamps

From datetime to microseconds (UTC):

import datetime
now = datetime.datetime.now(datetime.timezone.utc)
now_us = int(now.timestamp() * 1_000_000)

From microseconds to datetime:

import datetime
timestamp_us = 1759254380813451
dt = datetime.datetime.fromtimestamp(timestamp_us / 1_000_000)

In SQL queries:

-- Datetime to microseconds
SELECT CAST((strftime('%s', import_time) || substr(import_time, 21, 6)) AS INTEGER);

-- Microseconds to datetime (approximate)
SELECT datetime(import_time_us / 1000000, 'unixepoch');

Performance Benefits

The integer timestamp columns provide significant performance improvements:

  1. Faster comparisons: Integer comparisons are much faster than string/datetime comparisons
  2. Smaller index size: Integer indexes are more compact than datetime indexes
  3. Range queries: BETWEEN operations on integers are highly optimized
  4. Math operations: Easy to calculate time differences, averages, etc.
  5. Sorting: Integer sorting is faster than datetime sorting

Backward Compatibility

The original import_time TEXT columns remain unchanged:

  • Existing code continues to work
  • Human-readable timestamps still available
  • Gradual migration to new columns possible
  • No breaking changes for existing queries

Future Work

Future improvements could include:

  • Migrating queries to use import_time_us columns
  • Deprecating the TEXT import_time columns (after transition period)
  • Adding helper functions for timestamp conversion
  • Creating views that expose both formats

Testing

The migration was tested on a production database with:

  • 132,466 packet records
  • 1,385,659 packet_seen records
  • 28,414 traceroute records

All records were successfully migrated with microsecond precision preserved.

References