Skip to main content

Choosing an Execution Tier

One API, two engines. Every GeoBrix raster function exists in two interchangeable Execution Tiers, behind the same rst_* Python names and gbx_rst_* SQL names:

  • Lightweight (pyrx) — pure Python/rasterio, no JAR, no init script. Runs everywhere: Serverless, standard/shared clusters, ARM, Lakeflow declarative pipelines. Works with bytes-free virtual tiles so huge rasters ingest without out-of-memory. The recommended default.
  • Heavyweight (rasterx) — JVM-native (Scala + GDAL JNI) on a classic x86 cluster. Operates on materialized (binary) tiles.

Both tiers cover the full raster function set and agree within tolerance across the benchmark suite — so switching is a one-line import change and the rest of your code is identical. Pick the tier that fits your environment; the sections below cover what each provides and how to choose.

The one-line swap

from databricks.labs.gbx.rasterx import functions as rx   # Heavyweight tier
from databricks.labs.gbx.pyrx import functions as rx # Lightweight tier — same alias

# everything below is identical across tiers:
df.select(rx.rst_slope("tile", unit="degrees"))

After an explicit rx.register(spark), the SQL names are identical too (gbx_rst_*), so SQL is portable across tiers.

Heavyweight needs more than the wheel

The one-line import swap is symmetric, but the install is not. The lightweight tier installs a runtime-pinned extra (e.g. [light_env6] on Serverless env 6; see Installation for the full extras table) via %pip — no JAR, no init script. The heavyweight tier additionally requires the GeoBrix JAR as a cluster library and the GDAL init script on a classic x86 cluster — the wheel alone will not resolve the import or the JVM expressions. See Installation for the heavyweight setup.

Function availability

Both tiers implement the full raster set — every rst_* function, including the BNG and quadbin raster-grid functions — so the tier choice is about environment, not capability. The remaining heavyweight-only surfaces are narrow: the vector OGR readers (shapefile_ogr, geojson_ogr, …), the conforming triangulation mode, and the heavy pmtiles DataSource writer. For the per-function breakdown see the Raster Functions availability section; for the tier tile model (virtual vs. materialized), see Virtual Tiles.

Registering a subset (only=)

register() installs every gbx_* SQL name for the tier. To register just the functions a session uses, pass only= (lightweight tiers — pyrx, pygx, pyvx):

from databricks.labs.gbx.pyrx import functions as rx

rx.register(spark, only=["rst_slope", "rst_clip"]) # just these two
rx.register(spark) # all (default)

Names are case-insensitive and accept either the SQL name (gbx_rst_slope) or the short form (rst_slope). An unrecognized name raises ValueError (typo guard). only=[] registers nothing.

Readers and writers register through a separate entry point and take only= too — selected by format name (with or without the _gbx suffix):

from databricks.labs.gbx.ds import register as ds_register

ds_register.register(spark, only=["raster_gbx", "gtiff_gbx"]) # just these formats
ds_register.register(spark, only=["shapefile"]) # 'shapefile' -> 'shapefile_gbx'
ds_register.register(spark) # all readers/writers (default)

Mixing tiers per function. Because both tiers share the gbx_* names (last registration wins), you can register the heavyweight set and then override individual functions with the lightweight implementation:

from databricks.labs.gbx.rasterx import functions as heavy
from databricks.labs.gbx.pyrx import functions as light

heavy.register(spark) # all heavy gbx_rst_*
light.register(spark, only=["rst_slope"]) # gbx_rst_slope now lightweight

The reverse — re-registering a few heavy functions over a lightweight session — is not yet available; only= is currently a lightweight-tier feature (heavy registers its full set). Mixing works for materialized tiles (raster bytes present) — both tiers share the same GTiff payload, so a bytes-carrying tile produced by one tier flows into a function from the other. A lightweight virtual tile (bytes-free path+window) must be materialized before a heavyweight function can use it; see Virtual tiles and the light→heavy bridge.

Tradeoffs

The lightweight raster tier is, in effect, distributed rasterio: see how much of the rasterio/GDAL surface is already distributed — and what stays heavyweight-only for now.

AspectHeavyweight (rasterx)Lightweight (pyrx)
InstallInit script + JARVolume-staged wheel (%pip or cluster library)
Native GDALSystem/PPA installBundled with rasterio (nothing to install)
ARM supportx86 onlyx86 and ARM
Serverless / shared clusters / Lakeflow SDPNot supportedSupported
Execution modelJVM-native (Scala + GDAL JNI)Python-worker UDFs (rasterio + NumPy)
Tile modelMaterialized (binary) tiles onlyMaterialized and bytes-free virtual tiles (lazy windowed reads; no ingest OOM)
Driver coverageFull custom GDAL buildrasterio's bundled build (narrower)
SQL default argumentsSupportedPass all arguments explicitly
Function coverageFull raster setFull raster set — every rst_* function, including the BNG/quadbin raster-grid functions
Readers / Writersgtiff_gdal, gdal, OGR readersraster_gbx / gtiff_gbx native Python DataSource V2 reader + writer (no JAR); vector OGR readers still heavy-only

How to choose

Start with Lightweight (pyrx). It installs as a single wheel (%pip or cluster library) with no GDAL and no JAR, runs everywhere — serverless, standard/shared clusters, ARM, and Lakeflow declarative pipelines — and covers the full raster set: every rst_* function, including the BNG and quadbin raster-grid functions. For most raster work it is the recommended default.

Choose Heavyweight (rasterx) in three cases:

  1. Your environment is already JVM/GDAL-based — you have the init script and JAR in place, or you specifically want JVM-native execution on a dedicated cluster.
  2. You need vector OGR readers or format-specific GDAL options — the OGR vector readers (shapefile_ogr, geojson_ogr, gpkg_ogr, …) and the generic gdal reader with exotic driver options are heavy-only. For raster I/O, the lightweight tier ships native raster_gbx / gtiff_gbx readers and a writer (no JAR; see Lightweight Raster Readers).
  3. You need the heavy-only conforming triangulation mode — GridX is now fully lightweight: the quadbin (gbx_quadbin_*), BNG (gbx_bng_*), and custom-grid (gbx_custom_*) functions all run in both tiers via the lightweight pygx package. The full VectorX function set is now available in the lightweight pyvx tier — vector-tile encoding (gbx_st_asmvt, gbx_st_asmvt_pyramid), TIN surface modeling (gbx_st_triangulate, gbx_st_interpolateelevation*), and legacy-geometry migration (gbx_st_legacyaswkb); only the Steiner-point conforming triangulation mode is heavyweight-only (the default constrained mode runs in both tiers). See GridX and VectorX Function Reference.

The vector OGR readers, the conforming triangulation mode, and the heavy pmtiles DataSource writer are the remaining heavyweight-only surfaces. Raster I/O, the full VectorX function set, all of GridX (quadbin, BNG, and custom grids), and the gbx_pmtiles_agg aggregate are now available in both tiers; heavyweight's unique surface is expected to keep narrowing.

Performance

The one-line swap keeps your code identical across tiers. The lightweight tier is functionally complete for raster — it implements every rst_* function, including the BNG and quadbin raster-grid functions listed below — and for VectorX, implementing every gbx_st_* function (MVT, TIN, legacy migration).

Raster-grid surface (BNG/quadbin) — tier status:

Function familyTierNotes
gbx_rst_bng_rastertogrid{avg,count,max,min,median,sum,variance,stddev}BothLight support via pyrx (pygx._bng cell math + rasterio, EPSG:27700 auto-warp, STRING cell IDs)
gbx_rst_quadbin_rastertogrid{avg,count,max,min,median,sum,variance,stddev}BothLight support available via pyrx
gbx_rst_quadbin_tessellate, gbx_rst_bng_tessellateBothLight support via pyrx (pygx._quadbin / _bng cell math + rasterio)
gbx_rst_quadbin_rasterize_agg, gbx_rst_bng_rasterize_aggBothLight support via pyrx (pygx._quadbin / _bng cell math + rasterio)

On per-operation timing the lightweight tier is competitive-to-faster for the large majority of functions. Band math is dramatically faster, because the heavyweight tier shells out to a subprocess there. Terrain, focal filters, and discrete-grid (H3/quadbin) aggregation are a few times faster following recent vectorization. A small number of algorithm-bound operations (for example viewshed) are slower on the lightweight tier but remain sub-second in absolute terms. Metadata accessors are sub-millisecond on both.

Across the benchmark suite the two tiers agree within tolerance on 115 of 116 functions. Only one differs, at raster edges: rst_convolve differs slightly — the heavyweight tier applies a GDAL block-halo convolution that no single lightweight boundary mode reproduces exactly; interior values match. All three tessellation generators (rst_h3_tessellate, rst_quadbin_tessellate, rst_bng_tessellate) use a positive-area covering keep-test on both tiers: a cell is emitted iff its geometry has greater-than-zero area overlap with the raster — a cell that merely touches the raster along a boundary edge or corner (zero pixel overlap) is excluded, while a within-extent cell whose pixels are all NoData is still emitted (NoData renders in place, it does not punch a gap into the mosaic). With that shared semantic the three tessellators agree exactly across tiers (identical cell sets), including on grid-aligned tiles where the raster edges land on cell boundaries. The BNG and quadbin raster-grid reducers (rst_bng_rastertogrid{avg,count,max,min,median,sum,variance,stddev}) were measured on a British-National-Grid tile with real cells and agree within tolerance across tiers; the two *_rasterize_agg aggregators agree exactly. See Benchmarking for the full per-function heavy-vs-light results and how to run the benchmark on a cluster or locally.

Virtual tiles and the light→heavy bridge

The lightweight tier is for light (virtual) raster tiles; the heavyweight tier is for heavy (binary) raster tiles. Both tiers share the same v2 tile struct — the difference is in the raster field: a virtual tile has raster = null (bytes-free, path + window backed); a materialized tile carries raster bytes (raster is not null).

The heavyweight tier accepts both v1 and v2 materialized tiles as input and always emits the v2 tile struct. Passing a virtual tile (bytes-free, path-backed) to a heavyweight function raises a clear error telling you to materialize it in the lightweight tier first — either with materialize=True, or by writing via any raster writer and reading the output back. The JVM cannot lazily read from a Unity Catalog Volume FUSE path the way a Python worker can, so the materialization step is required before crossing the tier boundary.

If a heavy function does not produce the expected result from a light-tier tile, the tile may be virtual (no bytes). You have two options:

  • Stay in the lightweight tier. For most raster work, pyrx covers the full function set and runs everywhere (serverless, standard clusters, ARM). If you don't specifically need the JVM execution model, there is no reason to cross to heavy.
  • Materialize before crossing. If you do need a heavyweight function: call a tile-returning lightweight function with materialize=True to produce a bytes-carrying tile, or write via any raster writer (raster_gbx, gtiff_gbx, cog_gbx) and read the output back — every writer is a materialization boundary.

The lightweight-tier rst_* functions accept three optional force-output params (virtualize_dir, virtualize_prefix, materialize) to control this; the heavyweight tier has none of them. See Virtual-tile force-output params for the full param reference.

Virtual↔materialized advice

When working with virtual tiles in the lightweight tier, the key question is: which operations need pixels, and which can stay lazy?

Operation typeBehavior under autoNotes
Metadata accessors (rst_width, rst_height, rst_srid, rst_boundingbox, rst_format, rst_georeference, rst_metadata, rst_rotation, rst_scalex, rst_scaley, rst_skewx, rst_skewy, rst_numbands, rst_type, rst_getnodata, rst_upperleftx, rst_upperlefty)Free on virtual tiles — no pixels readThe header is opened lazily; .read() is never called
Pixel accessors / stats (rst_avg, rst_min, rst_max, rst_median, rst_pixelcount, rst_summary, rst_histogram, rst_sample, rst_isempty)Read the window (transient materialize), return scalars/arraysPixels are materialized for the computation only; no bytes in the output row
Reference / passthrough tile ops (rst_clip, rst_setsrid, rst_initnodata, rst_band, identity rst_transform where target CRS == source CRS)Reference/passthrough class — record instructions or clip references; no new pixels readrst_initnodata, rst_setsrid, and rst_band record a pending instruction on the virtual tile and stay bytes-free; the instruction is applied at the next read. rst_clip and identity rst_transform record a region reference. None of these ops produce new pixels on a virtual tile. virtualize_dir has no meaningful effect on them.
Pixel-producing tile ops (slope, aspect, hillshade, terrain, focal, mapalgebra, spectral indices, rasterize, resample, non-identity rst_transform, rst_merge / rst_combineavg / rst_frombands)Materialize and return bytesPass virtualize_dir to write the computed result to a durable path and get a light virtual row — the only way these return a virtual tile
Writers (raster_gbx, gtiff_gbx, cog_gbx)Always a materialization boundaryA virtual DataFrame is directly writable — writers auto-materialize; cog_gbx converts whole-file virtual tiles path-direct (no bytes round-trip)
Crossing to heavyMaterialize firstHeavy consumes only materialized tiles — stay in light (recommended), or materialize first (materialize=True, or write + read back) before handing off

Short rule: chain deferrable ops as long as you like; when you need pixels (or need to cross to heavy), materialize at that point — not before.