# Change revisions: GET /api/rev and kdb_rev() > A cheap number per table and per symbol that changes when committed rows change, so a chart polls it and reads its candles again only when they changed. - Category: HTTP API - Keywords: revision, revisions, rev, GET /api/rev, api/rev, kdb\_rev, candle rev, candle-rev, change signal, did it change, poll, polling, chart refresh, table\_rev, boot, rev column, per-symbol revision - See also: [Performance tips (result-cache)](performance-tips.md#result-cache), [Pinned queries](pinned-queries.md), [Run SQL over HTTP (/exec, /exp)](http-exec.md), [CREATE TABLE](create-table.md) A chart that shows NABIL's candles does not need to read them every few seconds. It can ask for NABIL's **revision** instead, and read the candles again only when the revision changed. - A revision is a short text such as `a67f6ff8e720.1532`. Compare it for **equality** only; the number is not a count and not a time. - It changes after every **committed** change of rows holding that value: `INSERT`, `UPDATE`, `DELETE`, `POST /write`, `POST /imp`, a trigger, a `CALL`. - A change **inside an open transaction** does not move it. It moves at `COMMIT`. A rollback may move it too; the rows are then the same as before, and a read again simply gives them. - Another value's writes never move it: a new NEPSE candle leaves NABIL's revision alone. - A **restart** changes every revision (the part before the dot is new). - It never waits for the database lock: it answers in well under a millisecond, even while a long `CALL` or bulk load writes. ## Which column has revisions Each table keeps revisions for the values of **one** column, when it has one of these: | Table | Column with revisions | |---|---| | `PARTITION BY ..., SYMBOL(symbol)` | `symbol` (the column named in `SYMBOL(...)`) | | `SORT BY (symbol, time)`, `DEDUP KEY (symbol, time)`, or a `PRIMARY KEY` whose first column is a `SYMBOL` | that first column | | anything else (`SORT BY (time, symbol)`, no SYMBOL key) | none: every value gets the table's revision | A table without a rev column still works: its values then change together, whenever any row of the table changes. Revisions per value are for columns with **up to 10,000 values**, such as the symbols of a market. They live in memory, outside `--memory`. When a table's rev column gets more values than that (a `SYMBOL` primary key of customer ids, say), the table drops its list and goes on as if it had no rev column: every write then moves every value's revision. `rev_values` in `GET /api/stats` counts the values kept. The same values decide what the [result cache](performance-tips.md#result-cache) keeps: a cached `WHERE symbol = 'NABIL'` result stays valid while only other symbols are written. ## GET /api/rev Syntax: ```sql GET /api/rev?table=&symbol=[&symbol= ...][&column=][&db=] ``` | Parameter | Meaning | |---|---| | `table` | the table (required) | | `symbol` | a value of the rev column; repeat it for more (up to 1000). `value=` is the same | | `column` | optional: the rev column's name. A different name is a 400 that names the right one | | `db` | the database; the default database without it | The answer: | Field | Meaning | |---|---| | `table`, `column` | the table and its rev column (`null` when it has none) | | `boot` | changes when the database opens again | | `table_rev` | changes after every committed change of the table | | `revs` | one revision per value asked for | It needs read rights on the table: role `read` on the database, or `GRANT SELECT` on the table. An unknown table is a 404. **HTTP: The revisions of two symbols** Setup: ```text CREATE TABLE candles (time TIMESTAMP, symbol SYMBOL, close DOUBLE) TIMESERIES(time) PARTITION BY YEAR, SYMBOL(symbol) SORT BY (symbol, time) DEDUP KEY (symbol, time); INSERT INTO candles VALUES ('2026-09-24T10:00:00Z', 'NABIL', 1510), ('2026-09-24T10:00:00Z', 'NEPSE', 2750); ``` ```http GET /api/rev?table=candles&symbol=NABIL&symbol=NEPSE ``` Response: HTTP 200 (application/json) ```text { "database": "demo", "table": "candles", "column": "symbol", "boot": "52589fb436f3", "table_rev": "52589fb436f3.0", "revs": {"NABIL": "52589fb436f3.0", "NEPSE": "52589fb436f3.0"} } ``` **HTTP: Asking by the wrong column says which one has revisions** Setup: ```text CREATE TABLE candles (time TIMESTAMP, symbol SYMBOL, close DOUBLE) TIMESERIES(time) PARTITION BY YEAR, SYMBOL(symbol) SORT BY (symbol, time) DEDUP KEY (symbol, time); ``` ```http GET /api/rev?table=candles&column=close&symbol=NABIL ``` Response: HTTP 400 (application/json) ```text {"error": "table 'candles' keeps revisions per value of column 'symbol', not 'close'"} ``` A page that polls: ```js let seen = null; setInterval(async () => { const r = await (await fetch('/api/rev?table=candles&symbol=NABIL')).json(); if (r.revs.NABIL !== seen) { seen = r.revs.NABIL; drawChart(await readCandles('NABIL')); // only when NABIL changed } }, 2000); ``` ## kdb_rev Syntax: ```sql kdb_rev('') kdb_rev('
', '') ``` The same revisions in SQL, for clients that only speak the [PostgreSQL protocol](postgres-protocol.md). With one argument: the table's revision (`table_rev`). With two: the revision of that value of the rev column. It gives the same text as `GET /api/rev`, and needs the same right: reading the table (a user whose `SELECT` on it was revoked gets an error). Unlike the route it runs as a statement, so it waits for the database lock like any `SELECT`. A NULL argument gives NULL; an unknown table is an error. **Example: A symbol's revision and the table's** (sample data: sample) ```sql SELECT length(kdb_rev('ochl_minutes', 'CITY')) > 0 AS has_rev, kdb_rev('ochl_minutes', 'CITY') = kdb_rev('ochl_minutes', 'CITY') AS stable; ``` Result: | has_rev (BOOL) | stable (BOOL) | |---|---| | true | true | (1 row)