pg_nanotime

PostgreSQL 奈秒精度時間戳擴充套件,靈感來自 timestamp9,並在此基礎上進一步發展。授權:PostgreSQL License。

狀態:v0.2.0 已釋出(2026-10-23,Phase 1–4 完成)。原始碼:https://github.com/bunko666-cloud/pg_nanotime。CI English summary

只支援 PostgreSQL 17 與 18(已在 17.11、18.6 上建置並通過全部回歸測試)。PG 16 以下會在建置時直接報錯:Makefile 會檢查 pg_config --version,原始碼也有 #error。程式中沒有任何舊版相容碼。

每個物件由哪些測試涵蓋、已知缺口與改動時要重跑哪些測試,見 COVERAGE.md。

使用原則(重要)

奈秒欄位請一律使用 nanotimestamptz(或 nanotimestamptz8),並搭配 now_ns() / statement_ns() / clock_ns() 產生時間。不要和 now() / timestamptz 混用:

  • now_ns() 是交易中第一個查詢開始執行時由本擴充套件讀取的時鐘,而 now() 是 PostgreSQL 在交易開始時讀取的另一個時鐘值。兩者通常相差幾微秒,now_ns() 比較晚。如果同一筆資料一欄用 now()、另一欄用 now_ns(),或拿 now() 去和 now_ns() 寫入的值比較,就會出現幾微秒的不一致。
  • statement_ns() 同理,是陳述式開始時讀取的值,同一陳述式內不變。
  • clock_ns() 的語意和 clock_timestamp() 相同:每次呼叫都即時讀取牆鐘(CLOCK_REALTIME),只是精度到奈秒。
  • 這是使用慣例,擴充套件不會阻止混用。timestamptz → nanotimestamptz 是 IMPLICIT 轉型,所以 INSERT INTO t(ts) VALUES (now())、clock_ns() - now()、WHERE ts > now() 都能執行、不會報錯,但比較的是兩個不同來源的時鐘(而且 now() 只有微秒精度),結果可能差幾微秒。請自行在程式碼審查或欄位 DEFAULT 中統一使用 pg_nanotime 的函式。

型別 nanotimestamptz

項目 說明
儲存 16 bytes:{ int64 sec; int64 nsec; },對齊 double,STORAGE plain
紀元 Unix 紀元 1970-01-01 00:00:00 UTC;sec 為向下取整(floor)的秒數
nsec 永遠正規化為 0..999999999(1969-12-31 23:59:59.999999999 = {-1, 999999999})
範圍 與 timestamptz 相同:4714-11-24 00:00:00 BC(PG 文件稱「4713 BC」,為儒略曆寫法)到 294276-12-31 23:59:59.999999999
閏秒 v1 不處理(與 PostgreSQL 一致);CLOCK_TAI 選項規劃於 Phase 3

文字輸入/輸出

  • 輸入:接受 timestamptz 能接受的任何格式,秒數後可帶 1–9 位小數;第 10 位以後只允許 0,否則報錯(不靜默截斷)。時區、BC 的解析完全交給 PostgreSQL 本身。
  • 輸出:固定 ISO 8601 樣式 YYYY-MM-DD HH:MI:SS.fffffffff±TZ,永遠 9 位小數,依 session TimeZone 顯示偏移(與 timestamptz 在 DateStyle=ISO 時相同)。不跟隨 DateStyle。
  • 負值輸出無歧義:(-1)::bigint::nanotimestamptz → 1969-12-31 23:59:59.999999999+00。
  • infinity / -infinity:v0.1 不支援,會報錯。

二進位 I/O

send/recv:兩個 big-endian int64(sec、nsec)。recv 會檢查 nsec 範圍與時間範圍。

精簡型別 nanotimestamptz8

項目 說明
儲存 8 bytes,傳值(pass by value),int64 Unix 奈秒,與 bigint 的二進位格式相同
範圍 1677-09-21 00:12:43.145224192 到 2262-04-11 23:47:16.854775807 UTC,沒有 1970 下限;超出範圍一律報錯,不會溢位繞回
I/O 文字輸入/輸出的程式碼與 16 B 型別共用;binary 格式與 int8 相同
索引 B-tree、hash、BRIN minmax(比較直接使用 PG 內建 int8 函式)
轉型 轉成 nanotimestamptz 精確且為 IMPLICIT(所以兩型別可以直接比較);nanotimestamptz → 8 為 ASSIGNMENT,超出範圍報錯;與 timestamptz 雙向為 ASSIGNMENT,轉成 timestamptz 時 floor;與 bigint 雙向為明確轉型,WITHOUT FUNCTION,零成本
時鐘 clock_ns8()、now_ns8()、statement_ns8(),與 16 B 版本是同一次時鐘讀取

TimescaleDB(2.31.0 實測)

  • 可以直接作為 hypertable 的時間維度,不需要分區函數:create_hypertable('t', by_range('ts', 86400000000000))。chunk 排除、壓縮、drop_chunks(older_than => '...'::nanotimestamptz8) 都正常,預設索引為 (ts DESC)。
  • 若建立時指定了 partition_func => 'nanotime_part8'(要用 set_integer_now_func 或保留策略時需要),drop_chunks 的 older_than 必須是 bigint:寫 '...'::nanotimestamptz8::bigint;直接給 nanotimestamptz8 會報 invalid time argument type "nanotimestamptz8"。此時 TimescaleDB 的預設索引建在 nanotime_part8(ts) 運算式上,直接用 ts 查詢用不到它(chunk 排除仍有效,但 chunk 內是 seq scan),請另外建立 CREATE INDEX ON t (ts DESC);。
  • 不能使用 set_integer_now_func 與連續聚合:TimescaleDB 不把它當成整數維度。time_bucket 也沒有這個型別的版本,請用 time_bucket(bigint, ts::bigint)::nanotimestamptz8 查詢。需要連續聚合時,請把時間欄位存成 bigint。

運算子與索引

  • < <= = <> >= >、min()/max()、greatest/least。
  • B-tree、hash、BRIN minmax 運算子類別(都是預設);= 支援 hash join 與 merge join。
型別 B-tree hash BRIN minmax GiST / GIN
nanotimestamptz ✓ ✓ ✓ ✗
nanotimestamptz8 ✓ ✓ ✓ ✗
nanotimestamp ✓ ✓ ✓ ✗
nanointerval ✓ ✓ ✗ ✗
  • 不支援 BRIN minmax-multi 與 bloom;int8 的 opclass(如 int8_minmax_multi_ops)也不接受 nanotimestamptz8。
  • GiST/GIN 沒有運算子類別(安裝 btree_gist 也一樣)。需要「時段不可重疊」時,可以自訂 range 型別 CREATE TYPE nanotstzrange AS RANGE (subtype = nanotimestamptz),再用 EXCLUDE USING gist;這是 PostgreSQL 的通用機制,不在回歸測試範圍內。
  • 參考(PG 18.6,100 萬列依時間寫入的 nanotimestamptz8):B-tree 21 MiB、hash 32 MiB、BRIN 24 KiB。

型別轉換

轉換 語意 情境
timestamptz → nanotimestamptz 精確 IMPLICIT
nanotimestamptz → timestamptz 向下取整到微秒(1970 前亦為 floor) ASSIGNMENT
bigint → nanotimestamptz bigint = 1970 起算的奈秒數 僅 EXPLICIT
nanotimestamptz → bigint 超出 1677-09-21 00:12:43.145224192 ~ 2262-04-11 23:47:16.854775807 時報錯 僅 EXPLICIT

所有超出範圍的情況一律報錯,不會溢位繞回(wraparound)。

時鐘函式

函式 易變性 說明
clock_ns() VOLATILE 每次呼叫讀 clock_gettime(CLOCK_REALTIME)
now_ns() STABLE 交易開始時間(奈秒)。在該交易第一次 executor 啟動時讀取時鐘並快取
statement_ns() STABLE 目前陳述式開始時間(奈秒),同一陳述式內不變

注意:now_ns() 是由擴充套件在 ExecutorStart_hook 讀取的時鐘,不是 PostgreSQL now() 的同一次讀取,因此通常比 now() 晚數微秒(now_ns()::timestamptz >= now())。如果函式庫在陳述式開始後才首次載入,第一次呼叫時才讀時鐘。建議在 postgresql.conf 設定 shared_preload_libraries = 'pg_nanotime' 或 session_preload_libraries,讓時間更貼近交易開始。

CREATE TABLE events (
  id bigserial,
  ts nanotimestamptz NOT NULL DEFAULT clock_ns(),
  tx nanotimestamptz NOT NULL DEFAULT now_ns()
);

安裝

需要 PostgreSQL 17 或 18 的開發套件(Debian/Ubuntu 為 postgresql-server-dev-17 或 postgresql-server-dev-18)與 C 編譯器。

git clone https://github.com/bunko666-cloud/pg_nanotime pg_nanotime && cd pg_nanotime
make PG_CONFIG=/usr/lib/postgresql/18/bin/pg_config
sudo make install PG_CONFIG=/usr/lib/postgresql/18/bin/pg_config
# 或從 PGXN 安裝:pgxn install pg_nanotime(發佈後)

若要使用 clock_ns_global(),或想讓 now_ns() 更貼近交易開始時間,請在 postgresql.conf 加上以下設定,然後重新啟動:

shared_preload_libraries = 'pg_nanotime'      # 已用 TimescaleDB 時:'timescaledb, pg_nanotime'

快速上手

CREATE EXTENSION pg_nanotime;

CREATE TABLE trades (
  id  bigserial PRIMARY KEY,
  t   nanotimestamptz  NOT NULL DEFAULT clock_ns(),        -- 完整範圍,16 B
  t8  nanotimestamptz8 NOT NULL DEFAULT clock_ns8(),       -- 1677–2262,8 B
  seq nanotimestamptz8 NOT NULL DEFAULT clock_ns8_global() -- 跨連線嚴格遞增(需要 preload)
);
INSERT INTO trades DEFAULT VALUES;
SELECT t, t - lag(t) OVER (ORDER BY t) AS gap FROM trades;
SELECT time_bucket_ns('1 second', t), count(*) FROM trades GROUP BY 1;

-- 從 timestamp9 遷移:
CALL nanotime_migrate_column('old_table', 'ts', new_default => 'clock_ns()');

建置與測試

make installcheck PGHOST=/tmp PGPORT=5432 PGUSER=postgres
  • 加上 WITH_TIMESCALEDB=1 會多跑 timescale 測試。
  • 有沒有 preload 都能跑:global 測試準備了兩份預期結果。
  • make dist 會從 HEAD 產生 PGXN 用的 zip。

文件

尚未完成

  • infinity,以及依 DateStyle 輸出。
  • TimescaleDB 連續聚合:需要向上游提案。

Phase 2:nanointerval、算術、分桶

nanointerval

  • 只存固定奈秒數(沒有月、日欄位),16 bytes {int64 sec, int64 nsec}。
  • 為什麼不用 int64:int64 奈秒只涵蓋約 ±292 年,但 nanotimestamptz 涵蓋約 30 萬年,任兩個值相減都必須放得下。16B 的範圍是 ±(max − min) ≈ ±9.4e21 ns,剛好容納所有 ts − ts 的差值,超出時嚴格報錯 nanointerval out of range。
  • 輸出:1 day 02:03:04.123456789、-1 day -02:00:00.000000000、-14 days,小數固定 9 位。
  • 輸入:單位字(ns/us/ms/s/min/h/d/w 及全名)、HH:MM[:SS.f]、@、ago、純數字(秒)、ISO 8601 P..T..。month/year 會報錯並提示改用 interval;比 1 ns 更細也會報錯。
  • 運算子:比較、B-tree、hash、一元 -、+、-、* / int(/ 四捨五入,遠離 0)、* / numeric。smallint 需要先轉型(不然會有 operator is not unique)。
  • 轉型:interval ↔ nanointerval 是 ASSIGNMENT 轉型。從 interval 轉入時,有月份或 infinity 會報錯,1 day 當作 24 小時。轉成 interval 時會向下取整到 µs。bigint ↔ nanointerval(奈秒)只能明確轉型。
  • nanointerval_ns(x) → numeric、nanointerval_from_ns(numeric | bigint | double precision):bigint 與 numeric 版本是精確的(整數參數會選到 bigint 版本;bigint 與 double precision 版本 0.2.0 起才有,0.1.0 的整數參數走 numeric),先四捨五入到整數奈秒(遠離 0),超出範圍會報錯。沒有 numeric → nanointerval 轉型,1500::numeric::nanointerval 會報 cannot cast type numeric to nanointerval;請寫 nanointerval_from_ns(1500)(→ 00:00:00.000001500),整數奈秒也可以寫 1500::bigint::nanointerval。
    • double precision 版本(0.2.0)同樣四捨五入(遠離 0)。double 只有 53 位元尾數,超過 253 ns(約 104 天)就無法表示每個整數奈秒,例如 9007199254740993::float8 會變成 …992;需要精確時請傳 bigint 或 numeric。
    • NaN 報 cannot convert NaN to nanointerval,±Infinity 報 cannot convert infinity to nanointerval(numeric 與 double precision 都一樣;0.1.0 對 numeric 報的是 nanointerval out of range)。有限但超出範圍的值仍報 nanointerval out of range。
  • abs(nanointerval)(0.2.0):nanointerval 的範圍是對稱的(±NI_MAX),所以最小值也能取負,abs 與一元 - 都不會溢位。
  • generate_series(start, stop, step nanointerval)(0.2.0),start/stop 可為 nanotimestamptz、nanotimestamptz8、nanotimestamp:語意與 PostgreSQL 的 timestamptz 版本相同——包含 stop;step 為 0 報 step size cannot equal zero;負的 step 往回數;方向不對時回傳空集合;任一參數 NULL 時回傳空集合。內部用 int128 計算,輸出值一定介於 start 與 stop 之間,所以在型別範圍邊緣(例如 nanotimestamptz8 的 2262-04-11)不會繞回也不會報錯。附有 planner support 函式:三個參數都是常數時,列數估計就是精確的序列長度(例如 1 小時/1 分鐘 → 61 列),否則用預設估計。step 是固定奈秒數,不處理月份等日曆單位(那是 interval 的事)。
  • 聚合(0.2.0 起):min、max、sum、avg。sum/avg 以 int128 精確累加奈秒並計數,不會有浮點誤差;結果超出範圍時報錯 nanointerval out of range,不會繞回。avg 四捨五入到最近的奈秒,剛好一半時遠離 0(與 nanointerval / bigint 一致:1 ns 與 2 ns 平均是 2 ns,-1 ns 與 -2 ns 是 -2 ns)。NULL 會被忽略,沒有資料列或全為 NULL 時回傳 NULL。支援平行查詢(combine/serialize)與視窗函式的移動聚合(inverse transition)。

算術(兩種時間型別都有,溢位都會報錯)

  • ts − ts → nanointerval
  • ts ± nanointerval:固定奈秒
  • ts ± interval:依 session 時區做日曆運算(與 timestamptz 相同,DST 下 +1 day 會保留牆上時間),保留 µs 以下的位數。因為受時區影響,所以是 STABLE。
  • ⚠️ 字面值陷阱:ts + '1 day' 這類未標型別的字串會解析成內建的 interval(日曆語意、µs 精度)。需要奈秒精度或固定長度時,請寫 '...'::nanointerval。ts − '2024-01-01' 會解析成 ts − ts。

分桶與相關函式

  • time_bucket_ns(width nanointerval, ts [, origin]):兩種型別都有,回傳與輸入相同的型別。預設 origin 是 1970-01-01 UTC,向下取整(1970 年以前也正確)。width ≤ 0 會報錯。若桶起點低於型別最小值,會夾到最小值(例如 nt8 在 1677-09-21 那一天)。
  • date_trunc(text, ts [, zone]):µs 以上的單位交給 PG 的 timestamptz_trunc;'nanosecond' 則原值回傳。
  • extract_ns(text, ts|nanointerval) → numeric(EXTRACT(... FROM ...) 語法在 PG 中寫死,無法擴充):epoch 精確到 9 位小數;nanosecond、microsecond、millisecond、second 都含奈秒,而且和 PG 的 EXTRACT(microsecond ...) 一樣包含整數秒:extract_ns('nanosecond', '...01.000000500+00') 回傳 1000000500(1 秒 + 500 ns),'microsecond' 回傳 1000000.500;只要秒以下的部分請用 extract_ns('nanosecond', ts) % 1000000000;其他欄位交給 PG 處理。nanointerval 支援 epoch、day、hour、minute 與秒以下的欄位。

nanotimestamp(不含時區)

  • 16 bytes {int64 sec, int64 nsec},與 nanotimestamptz 共用程式碼。sec 是 1970-01-01 00:00:00 起算的牆上時間秒數(不含時區),語意與 PG timestamp 相同。範圍等同 timestamp。
  • text I/O 會忽略輸入中的時區('...+05' 直接取牆上時間),輸出沒有時區後綴;IMMUTABLE。
  • 比較、min/max、B-tree、hash、BRIN minmax。
  • 轉型(context 與核心的 timestamp/timestamptz 相同):
    • timestamp → nanotimestamp IMPLICIT;反向是 ASSIGNMENT,floor 到 µs。
    • date → nanotimestamp IMPLICIT;反向是 ASSIGNMENT。date ↔ nanotimestamptz 也有,都是 ASSIGNMENT。
    • nanotimestamp → nanotimestamptz IMPLICIT;反向是 ASSIGNMENT。兩者都依 session 時區換算(DST 的 gap 和 overlap 處理和核心相同),µs 以下的位數保留。
    • bigint ↔ nanotimestamp 只能明確轉型(牆上時間 ns)。
  • AT TIME ZONE:這個語法在 PG 中寫死為 pg_catalog.timezone,擴充套件無法支援。請改用 timezone(zone, nanotimestamp) → nanotimestamptz 與 timezone(zone, nanotimestamptz) → nanotimestamp,結果與核心一致。
  • 算術:ts − ts、ts ± nanointerval、ts ± interval(純日曆運算,不受時區與 DST 影響,IMMUTABLE)、time_bucket_ns、date_trunc(沒有 zone 參數)、extract_ns。

為什麼沒有 8 B 不含時區版本:8 B 型別存在的理由,是 TimescaleDB 維度和 bigint 二進位相容,加上大量 tick 資料。這些資料都是絕對時刻,應該存 nanotimestamptz8。不含時區的牆上時間多半是排程、本地事件,量小,而且需要 1677–2262 以外的範圍。每多一個型別,轉型與運算子的組合就會成倍增加,候選函式也更容易產生歧義。真的需要時,可以用 nanotimestamptz8 搭配 timezone('UTC', …)。

遷移工具

  • nanotime_migration_expr(src regtype, target regtype, col name) → text:產生 ALTER TABLE ... USING 的運算式。
    • timestamp9 經由它自己的二進位 bigint 轉型 col::bigint::nanotimestamptz。pg_nanotime 不依賴 timestamp9,只是欄位存在時 timestamp9 必須已安裝。
    • bigint ns 直接轉型,不做任何換算(col::nanotimestamptz 或 col::nanotimestamptz8,依目標型別)。
    • timestamptz、timestamp、date 是精確轉換(µs × 1000);跨越有無時區時,依 session 時區。
  • CALL nanotime_migrate_column(tbl, col, target DEFAULT 'nanotimestamptz', new_default DEFAULT NULL, dry_run DEFAULT false):
    • 預設目標一律是 16 B nanotimestamptz,來源是 bigint 時也一樣;要轉成 8 B 請明確傳入 'nanotimestamptz8',例如 CALL nanotime_migrate_column('t', 'c', 'nanotimestamptz8');。
    • 用一個 ALTER TABLE ... TYPE ... USING 完成。PG 會在同一個陳述式中,用新型別的預設 opclass 重建該欄位的 B-tree、hash、BRIN、unique 索引,失敗時整個回滾。
    • 舊的預設值如果無法轉換(例如 timestamp9 常數),會報錯並提示用 new_default => 'clock_ns()'。
    • 有 view 或 rule 依賴該欄位時,ALTER 會失敗(PG 的限制)。
    • dry_run 只印出 SQL 和會重建的索引。
  • 手動寫法:ALTER TABLE t ALTER COLUMN c TYPE nanotimestamptz USING c::bigint::nanotimestamptz;(timestamp9),或 USING c::nanotimestamptz8(bigint ns)。
  • timestamp9 繞回損毀:timestamp9 會把超出範圍的輸入靜默繞回,例如 1500-01-01 變成 2084-07-20 23:34:33.709551616。繞回後仍是合法時刻,無法可靠偵測。nanotime_suspect_count(tbl, col, lo, hi) 只能統計落在你預期時間窗 [lo, hi) 之外的列數,預設窗是 1990–2100。窗要依照資料實際的時間跨度來設,越窄越有用。
  • 實測(PG 18.6、timestamp9 1.4.0,2026-10-23):
    • 20 萬 + 4 列,含 1677/2262 兩端、1969 與 1500 的繞回值,3 個索引,約 0.5 秒完成。
    • 200004/200004 列的 ns 值與文字輸出完全相同。
    • 預設窗只抓到 3 列(1677、1969、2262),抓不到 2084 的繞回值,正好說明它的限制。
    • 回歸測試用一個名為 timestamp9 的 bigint domain 模擬,因為 PG 17 上沒有安裝 timestamp9。

Phase 3:嚴格遞增時鐘、時鐘來源、TimescaleDB 輔助

嚴格遞增時鐘

函式 保證 需求
clock_ns_monotonic() / clock_ns8_monotonic() 同一連線內嚴格遞增:時鐘沒有前進時取上一個值 + 1 ns。不加鎖。16 B 和 8 B 版本共用同一個計數器 無
clock_ns_global() / clock_ns8_global() 跨所有連線嚴格遞增且不重複:在共享記憶體上用 atomic CAS 取 max(now, last + 1) shared_preload_libraries = 'pg_nanotime',否則會報出清楚的錯誤

注意事項: - 保證的是「呼叫順序」,不是 commit 順序。先取到時間的交易可能較晚 commit。 - global 的計數器存在共享記憶體,伺服器重啟後會重來。 - 多個連線同時大量呼叫時,global 的總速率只有一般時鐘的約 14–28%。實測數字見 docs/TESTING.md §8。

時鐘倒退與監控

SELECT * FROM nanotime_clock_status(); 回傳以下欄位: - 目前的時鐘來源、核心 TAI 偏移(秒)、是否已 preload。 - global:最後發出的值、global_lead_ns(領先實際時鐘的奈秒數,持續為正表示時鐘倒退過或呼叫非常密集)、呼叫次數、bumps(時鐘沒前進、改用 +1 ns 的次數)、倒退次數與最大倒退幅度。 - 本連線:同樣一組數值。

倒退的判定方式:同一連線讀到的時鐘值比上一次小。切換時鐘來源不算倒退。nanotime_reset_stats() 用來歸零計數,預設只有 superuser 能執行。時鐘倒退時,monotonic 與 global 會持續 +1 ns,直到實際時鐘追上;一般的 clock_ns() 則會跟著時鐘倒退。

nanotime.clock_source = realtime | tai

  • realtime(預設):CLOCK_REALTIME,也就是 UTC。
  • tai:CLOCK_TAI。Linux 預設的 TAI 偏移是 0,這時讀到的值和 realtime 相同。偏移要由 chrony(leapsectz right/UTC)或 ptp4l/phc2sys 設定,目前約為 37 秒,可以從 nanotime_clock_status().tai_offset_s 確認。
  • 用 tai 寫入的值是 TAI 時間,但輸出仍標示為 UTC 格式,與 realtime 的值相差偏移量。請不要在同一個欄位混用兩種來源。
  • 這個設定會影響所有時鐘函式,可以在每個連線各自設定。

nanotime.debug_clock_offset_us 只供測試使用(限 superuser),會在所有時鐘讀值上加上一個偏移,用來模擬時鐘倒退。

TimescaleDB 輔助

ns(nanointerval)、nanotime_now_int8()、nanotime_part()、nanotime_part_notz()、nanotime_part8()。用法範例見 docs/TIMESCALEDB.md。

English

pg_nanotime is a PostgreSQL 17/18 extension providing nanosecond timestamps, inspired by timestamp9 and building on that idea. Source: bunko666-cloud/pg_nanotime.

  • Types:
    • nanotimestamptz: 16 bytes, the full PostgreSQL timestamp range.
    • nanotimestamptz8: 8 bytes, int64 Unix ns, 1677–2262; binary-compatible with bigint, so TimescaleDB uses it as a native dimension.
    • nanotimestamp: without time zone.
    • nanointerval: fixed nanoseconds.
    • Out-of-range input, casts and arithmetic raise errors instead of wrapping around.
  • Indexes and functions: B-tree, hash and BRIN; ts - ts, ± nanointerval and ± interval; time_bucket_ns, date_trunc, extract_ns, timezone().
  • Clocks:
    • now_ns(), statement_ns(), clock_ns() (and *_ns8 variants).
    • clock_ns_monotonic() is strictly increasing within a session.
    • clock_ns_global() is strictly increasing and unique across sessions; it needs shared_preload_libraries.
    • The clock is selected with nanotime.clock_source = realtime | tai.
    • nanotime_clock_status() reports clock regressions and drift.
  • Migration: nanotime_migrate_column() converts timestamp9, bigint ns, timestamptz and timestamp columns in a single ALTER TABLE, rebuilding indexes.
  • TimescaleDB: ns(), nanotime_now_int8(), nanotime_part*(). See docs/TIMESCALEDB.md.

Install with make && sudo make install (PGXS), then CREATE EXTENSION pg_nanotime;. Run the tests with make installcheck. Detailed docs are in Traditional Chinese; test results, with real measured numbers, are in docs/TESTING.md. License: PostgreSQL.