Skip to content

Lindel

Linearize multi-dimensional numeric arrays via Hilbert and Morton (Z-order) space-filling curves.

2,032,600
extension loads · last 90 days
On this page

Technical Overview

Multi-dimensional data, sorted on one integer

One sort key for every dimension at once

Hilbert vs Morton

  • Hilbert — better locality: The Hilbert curve never makes long jumps in input space: consecutive positions along the curve are always neighbours in N-D. That yields tighter row-group bounding boxes at the cost of slightly more CPU per encode. The geometry indexing systems S2 (Google) and H3 (Uber) use Hilbert-style ordering for the same reason, and Delta Lake's liquid clustering moved to it.
  • Morton — faster, simpler: The Morton / Z-order curve is plain bit-interleaving of the input dimensions — trivially cheap to compute, but with occasional long jumps at quadrant boundaries that loosen the bounding boxes. The Bing Maps Tile System uses Morton-style quadkeys, and Delta Lake's original ZORDER BY was Morton.
  • Output width follows the inputs: Both encoders are polymorphic over UTINYINT through UHUGEINT. The result is the smallest unsigned integer that fits bit-width × dimensions — e.g. two 32-bit dimensions (64 bits total) return a UBIGINT, three return a UHUGEINT.

What to know before you sort by it

  • Inputs are unsigned integers: The curves only carry the bits they're handed, and locality is driven by the high bits of each dimension. Quantize floats first ((lat * 1e6)::UINTEGER gives ~10 cm resolution worldwide) and map signed ranges to unsigned, pre-scaling so the precision you care about lands in those high bits.
  • Skew can dominate the ordering: If one input column has far higher cardinality or a far wider range than the others, it dominates the encoded key and the locality benefit on the other columns collapses. Rescale the dimensions to comparable ranges before encoding, or drop the dominant column from the curve and add it as a secondary ORDER BY term.
  • Decoding doesn't carry types: Reconstructing the original array needs the dimension count, float-ness, and signed-ness passed explicitly — none of that is stored in the encoded integer, so the decode call has to be told the shape the value originally had.
  • It's a write-time ordering, not an index: The gain comes entirely from how rows are laid out in the Parquet file, so it only helps data written in encoded order. There is no live index to maintain: updating means rewriting the affected files, and for streaming inserts you batch and re-sort on a cadence rather than expecting one row at a time to improve skipping. Real-world speedups depend on data shape and filter selectivity — the regime Delta Lake liquid clustering targets.

Deep Dive

Technical Details

Install

INSTALL lindel FROM community;
LOAD lindel;

Quick Start

Encode a multi-dimensional point to one sortable integer

-- Hilbert keeps N-dimensional neighbors close in the 1-D ordering
SELECT hilbert_encode([10, 20]::UINTEGER[2]) AS hilbert;

Order rows by the curve — this is the Parquet write-time sort key

-- Neighbors in (x, y) end up adjacent. Wrap this SELECT in
--   COPY (...) TO 'out.parquet' (FORMAT PARQUET)
-- to persist the layout for multi-column row-group skipping.
SELECT x, y, hilbert_encode([x, y]::UINTEGER[2]) AS hilbert
FROM (VALUES (3, 5), (1, 1), (7, 0), (2, 6), (5, 3), (0, 4)) AS t(x, y)
ORDER BY hilbert;

Morton (Z-order) is cheaper to compute, with weaker locality

SELECT x, y, morton_encode([x, y]::UINTEGER[2]) AS morton
FROM (VALUES (3, 5), (1, 1), (7, 0), (2, 6), (5, 3), (0, 4)) AS t(x, y)
ORDER BY morton;

Reference

Extension Contents

Quick reference to all available functions and settings organized by category.

Name Description
Hilbert
hilbert_decode() Reverse the encoding — recover the original N-dimensional array from a Hilbert-encoded integer.
hilbert_encode() Encode a numeric array along the Hilbert space-filling curve into a single sortable integer.
Morton (Z-order)
morton_decode() Reverse Morton encoding — recover the original N-dimensional array from a Z-ordered integer.
morton_encode() Encode a numeric array along the Morton (Z-order) curve.

API Reference

Function Documentation

Practical Examples

Cookbook

Real-world recipes and patterns for common use cases.

Platform Support

Compatibility

Extension availability may vary by platform and DuckDB version. Check below to ensure this extension supports your environment before installation.

Quick Facts

Release status Stable
Software License MIT
Pricing Free
Written In C++
Source Available Yes
View on GitHub
Usage
2,032,600
loads · last 90 days

Platforms

  • Linux x86_64 aarch64
  • Linux (musl) Not available
  • macOS Intel Apple Silicon
  • Windows x86_64
  • WASM eh mvp threads
Compiled binary sizes
Platform Architecture Size
Linux x86_64 4.14 MB
Linux aarch64 3.76 MB
macOS Intel 2.22 MB
macOS Apple Silicon 1.92 MB
Windows x86_64 7.46 MB
WASM eh 78.9 KB
WASM mvp 82.0 KB
WASM threads 66.5 KB

Compressed download size from the Haybarn extension repository.

DuckDB & Haybarn

Release calendar