Prometheus + URLEng
Frugal Metrics with URLEng

Co-Founder and CEO at QXIP, makers of HOMER, HEPIC, QRYN and contributors to many other OSS projects in the monitoring and observability space

"Frugal Metrics"
Metrics can be lots fun, until you have to share them between a bunch of distributed servers... but fear not!
Distributed timeseries/metrics endpoint with UrlEng + ClickHouse
Let's build a serverless prometheus metrics store & publisher for ClickHouse using urleng.com
- no coding, no signups, no logins - batteries included! :battery:
- zero config distributed table, simply accessible from anywhere
- custom HTTP prometheus /metrics scraping endpoint w/ auto-expiration
- persistent storage courtesy of deta cloud
Let's get started!
Storing metrics in UrlEng
UrlEng is a free serverless pastie made for ClickHouse tables.
Let's use it to store a distributed table with a simple schema for our metrics:
| key | help | value | tags | __expires |
| metric name | metric help text | metric value | optional json tags | optional expiration unix ts |
To begin we will keep things basic - performance is not the challenge here but rather flexibiity. A few notes:
- INSERTs for the same key are considered UPDATEs in URL tables
- JSON Tags must be present or empty stringed
- Metrics can auto-expire using the
__expirescolumn and a future unix timestamp - REPLACE
/metrixxxWITH YOUR OWN UNIQUE STRING OR BE DOOMED
Simple it is! Let's INSERT our first little metric:
INSERT INTO FUNCTION url('https://urleng.com/metrixxx', JSONEachRow, 'key String, help String, value Float64, __expire UInt64, tags String') VALUES ('mygauge', 'my little metric', 100, toUnixTimestamp(now()+300), '{"le":"0.05"}')
That's easy. Let's take a closer look at our serverless URL table:
SELECT * FROM url('https://urleng.com/metrixxx', JSONEachRow)
┌─__expires──┬─help──────────────────┬─key──────┬─tags──────────┬─value─┐
│ 1659015366 │ my little metric │ mygauge │ {"le":"0.05"} │ 100 │
└────────────┴───────────────────────┴──────────┴───────────────┴───────┘
Looking good! Our distributed metrics table is ready. Get a materialized view up to insert some fun data.
:tooth: ... but the output is still a bit boring and can't be scraped, so its time to change the output format!
FORMAT Prometheus
ClickHouse comes with a built in suport for the Prometheus format - its a little strict, but it works, so let's use it!
SELECT ifNull(key, 'undefined') as name, ifNull(toFloat64(value),0) as value, help as help, CAST(JSONExtractKeysAndValues(replaceAll(ifNull(tags,''), '\'','\"'), 'String'), 'Map(String, String)') as labels FROM url('https://urleng.com/metrixxx', JSONEachRow) FORMAT Prometheus
# HELP mygauge my little metric
mygauge{le="0.05"} 100
We're almost there - all we need is an endpoint we can scrape and luckily, ClickHouse does that too!
Metrics HTTP Handler
So let's use all the ClickHouse features we can, shall we?
Create a custom handler for our metrics query, ie: /etc/clickhouse-server/config.d/metric_export.xml
<yandex>
<format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>
<custom_urleng>'https://urleng.com/metrixxx</custom_urleng>
<http_handlers>
<rule>
<url>/metrics</url>
<methods>GET</methods>
<handler>
<type>predefined_query_handler</type>
<query>SELECT ifNull(key, 'undefined') as name, ifNull(toFloat64(value),0) as value, help, CAST(JSONExtractKeysAndValues(replaceAll(ifNull(tags,''), '\'','\"'), 'String'), 'Map(String, String)') as labels FROM url(getSetting('custom_urleng'), JSONEachRow) FORMAT Prometheus</query>
</handler>
</rule>
<defaults/>
</http_handlers>
</yandex>
Scrape it and Shake it!
Et Voila'! Our custom endpoint is ready to be scraped. Let's curl a final test:
curl 'http://default:password@localhost:8123/metrics'
# TYPE mygauge gauge
mygauge{le="0.05"} 100
:postbox: Easy and Fun, isn't it? Go ahead, update your metrics from any server/service and scrape away!
Bonus Steps
:warning: This guide is intended as a firestarter - go crazy adding timestamping, output formats and anything else!
URL Engine Table
Going to use your URL engine store daily? Extend the schema and setup a URL Engine table for quicker access:
CREATE TABLE default.prometheus_exporter
(
`key` String,
`value` Float64,
`help` String,
`type` String,
`tags` String,
`__expires` UInt64 DEFAULT toUnixTimestamp(now()+300),
)
ENGINE = URL('https://urleng.com/metrixxx', 'JSONEachRow')
INSERT INTO default.prometheus_exporter VALUES ('mygauge', 100, 'my little metric', 'gauge', '{"le":"0.05"}', toUnixTimestamp(now()+300))


