Skip to content

AQL Geospatial Guide

github-actions[bot] edited this page Sep 28, 2026 · 25 revisions

Navigation: Home > API & Integration > AQL

AQL Geospatial Optimization Guide

Overview

Phase 6C introduces query optimizer support for geospatial predicates in ThemisDB. This guide explains how to:

  1. Write optimal spatial queries with automatic index selection
  2. Use optimizer hints to guide index selection
  3. Understand cost estimation for spatial operations
  4. Achieve performance targets with geospatial predicates

Quick Start

Basic Spatial Query

FOR doc IN locations
FILTER ST_DISTANCE(doc.location, {lon: 0, lat: 0}) < 100000
RETURN doc

What happens:

  • Optimizer detects ST_DISTANCE predicate
  • Checks for available spatial indexes on doc.location
  • If R-tree index exists: uses index (≀100Β΅s cost)
  • If no index: falls back to full scan (≀50ms cost on 1M points)

Adding Optimizer Hints

FOR doc IN locations
FILTER ST_DISTANCE(doc.location, {lon: 0, lat: 0}) < 100000
    USE_INDEX(doc.location, "geo_idx_rtree")
RETURN doc

Hint Types:

Hint Syntax Effect
USE_INDEX USE_INDEX(field, "index_name") Force use of specific spatial index
FORCE_SCAN FORCE_SCAN(field) Disable indexing, use full scan
INDEX_PRIORITY INDEX_PRIORITY(field, 2.0) Adjust index selection priority (0.1-10.0)
DISTANCE_ORDER DISTANCE_ORDER(field, "ascending") Optimize distance-based ordering

Spatial Predicates & Performance

ST_DISTANCE - Nearest Neighbor Search

Use Case: Find points within a distance radius from a location.

FILTER ST_DISTANCE(doc.location, center) < radiusMeters

Performance:

  • With R-tree index: ≀100Β΅s (1M points)
  • Without index: ≀50ms (full scan)
  • Speedup: 500x with index

Optimization Tips:

  1. Always define spatial index on location field
  2. Use reasonable radius (1km-100km typical)
  3. Combine with other filters for reduction:
    FILTER doc.country == "US" AND ST_DISTANCE(...) < 100km
    

ST_CONTAINS - Point-in-Polygon

Use Case: Find points inside a polygon/region.

FILTER ST_CONTAINS(doc.location, polygonGeometry)

Performance:

  • With R-tree index: ≀150Β΅s (bounding box + verification)
  • Without index: ≀100ms (complex geometry checks)
  • Speedup: 600x with index

Polygon Complexity Impact:

  • Simple (4 vertices): ~70Β΅s
  • Complex (50 vertices): ~120Β΅s
  • Very complex (200+ vertices): ~150Β΅s

Optimization Tips:

  1. Simplify polygons where possible (reduces per-point cost)
  2. Use bounding box pre-filter if available
  3. Place ST_CONTAINS before unindexed filters:
    FILTER ST_CONTAINS(doc.location, zone) AND doc.verified == true
    

ST_INTERSECTS - Geometry Overlap

Use Case: Find geometries overlapping with a query geometry.

FILTER ST_INTERSECTS(doc.boundary, queryGeometry)

Performance:

  • With R-tree index: ≀200Β΅s (bbox check + refinement)
  • Without index: ≀200ms (full geometry comparison)
  • Speedup: 1000x with index

Optimization Tips:

  1. R-tree index handles bbox filtering automatically
  2. Query is decomposed into:
    • Bounding box test (fast)
    • Refined geometry check (precise)
  3. Use simpler geometries when possible

Index Selection Guide

When Do You Need an Index?

Dataset Size Index Type Recommended?
<1,000 NONE Noβ€”full scan acceptable
1K-10K Optional Use if query-heavy workload
10K-1M REQUIRED R-tree or Grid
>1M REQUIRED R-tree recommended

Index Types

R-tree Index (Recommended)

  • Best for most spatial queries
  • Balanced tree structure
  • Good for both distance and containment
  • Storage: ~10% of data size
CREATE INDEX idx_location_rtree ON locations(location) TYPE SPATIAL

Grid Index

  • Best for uniformly distributed data
  • Faster for large-area queries
  • Lower maintenance overhead
  • Storage: ~5% of data size
CREATE INDEX idx_location_grid ON locations(location) TYPE SPATIAL GRID

Quadtree Index

  • Adaptive grid-based
  • Good for clustered data
  • Automatic refinement
  • Storage: ~7% of data size
CREATE INDEX idx_location_qtree ON locations(location) TYPE SPATIAL QUADTREE

Optimizer Selection

The optimizer automatically selects the best index:

For each available spatial index:
  1. Calculate index efficiency score
  2. Estimate query cost with that index
  3. Consider data distribution
  4. Apply cost adjustments from hints
  
Select index with best score
Fall back to full scan if no good option

Factors Considered:

  • Index type vs. predicate (R-tree β†’ distance, Grid β†’ uniform)
  • Data distribution (clustered, skewed, uniform)
  • Index statistics (hit rate, size, maintenance)
  • Hints from query (if provided)

Query Optimization Examples

Example 1: Simple Distance Query

// Unoptimized (let optimizer decide)
FOR doc IN locations
FILTER ST_DISTANCE(doc.location, {lon: 0, lat: 0}) < 50000
RETURN doc

// Cost: ~50Β΅s with index, ~25ms without
// Optimizer will auto-select index

Example 2: Multi-Condition Spatial Query

// Optimal: indexed predicate first
FOR doc IN locations
FILTER ST_CONTAINS(doc.location, zone)
AND doc.verified == true
AND doc.priority > 5
RETURN doc

// Rewritten to:
// 1. ST_CONTAINS (indexed, filters 80%)
// 2. doc.verified (unindexed, filters 50% of remaining)
// 3. doc.priority (unindexed, filters 30% of remaining)

Example 3: Distance + Sorting

FOR doc IN locations
FILTER ST_DISTANCE(doc.location, center) < 100000
SORT BY ST_DISTANCE(doc.location, center) ASC
LIMIT 10
RETURN doc

// Optimization: R-tree nearest-neighbor scan
// Result is pre-sorted by distance
// Cost: ~80Β΅s for both filter + sort

Example 4: Complex Polygon Analysis

FOR zone IN zones
FOR doc IN locations
FILTER ST_CONTAINS(zone.polygon, doc.location)
RETURN {zone: zone._key, count: LENGTH(doc)}

// Optimization stages:
// 1. Create spatial histogram for locations
// 2. Estimate points per zone using histogram
// 3. Predicate pushdown: filter zones before join
// 4. Select R-tree index for contains predicate
// Cost: ~120Β΅s per zone check

Example 5: Using Hints for Custom Optimization

FOR doc IN locations
// Use specific index (override auto-selection)
FILTER ST_DISTANCE(doc.location, center) < 50000
  USE_INDEX(doc.location, "geo_idx_grid")
// Adjust priority if multiple spatial indexes
FILTER ST_CONTAINS(doc.location, zone)
  INDEX_PRIORITY(doc.location, 1.5)
RETURN doc

// Explicitly request full scan (for testing/debugging)
// FILTER ST_DISTANCE(doc.location, center) < 50000
//   FORCE_SCAN(doc.location)

Performance Expectations

Baseline Performance (1M Points, Global Distribution)

Operation With Index Without Index Speedup
ST_DISTANCE (r=10km) 50Β΅s 25ms 500x
ST_DISTANCE (r=100km) 60Β΅s 25ms 400x
ST_CONTAINS (simple) 70Β΅s 50ms 700x
ST_CONTAINS (complex) 120Β΅s 80ms 600x
ST_INTERSECTS 90Β΅s 100ms 1000x

Throughput Targets

  • Distance queries: β‰₯800 queries/second with index
  • Containment queries: β‰₯600 queries/second with index
  • Intersection queries: β‰₯500 queries/second with index

Cost Model Accuracy

  • Selectivity estimation: Within 10% of actual
  • Execution time estimate: Within 20% of actual
  • Index cost advantage: Minimum 3x speedup

Cost Model Details

How Costs Are Estimated

Distance (ST_DISTANCE):

With index: log(N) * 10Β΅s + result_count * 5Β΅s
Without index: N * 50Β΅s

Containment (ST_CONTAINS):

With index: log(N) * 15Β΅s + candidates * (2Β΅s + complexity * 0.5Β΅s)
Without index: N * (100Β΅s + complexity * 10Β΅s)

Intersection (ST_INTERSECTS):

With index: log(N) * 20Β΅s + matches * (3Β΅s + complexity * 1Β΅s)
Without index: N * (150Β΅s + complexity * 15Β΅s)

Selectivity Estimation

Selectivity depends on query radius/area:

Distance Selectivity:

  • 1km radius: 0.5%
  • 10km radius: 5%
  • 100km radius: 20%
  • 1000km radius: 40%

Containment/Intersection:

  • Depends on polygon complexity
  • Typical range: 1-30%
  • Estimated from spatial histogram

Troubleshooting

Query Slow Despite Index?

  1. Check index exists:

    SHOW INDEXES locations
    // Should show SPATIAL type index
    
  2. Verify hint is valid:

    • Check index name matches exactly
    • Verify field name is correct
    • Ensure index supports spatial predicates
  3. Check data distribution:

    • Highly clustered data may benefit from grid index
    • Try: INDEX_PRIORITY(field, 0.5) to reduce cost weight
  4. Query plan inspection:

    • Use EXPLAIN to see optimizer decisions
    • Check if FULL_SCAN or INDEX_SCAN is used

Index Creation Slow?

  1. R-tree indexes: ~1 minute per 1M points
  2. Grid indexes: ~30 seconds per 1M points
  3. Normal for initial creation; maintained incrementally

Unexpected High Selectivity?

  1. Verify search radius/area is reasonable
  2. Use histogram to validate distribution
  3. Check if predicates are redundant

Advanced Topics

Histogram-Based Selectivity

The optimizer builds a spatial histogram to estimate selectivity:

// 10x10 grid of cells
// Each cell tracks: point count, density
// Selectivity = (points in query area) / (total points)

This improves accuracy from 50% to <20% error.

Predicate Rewriting

Optimizer applies 5 rewrite rules:

  1. Index Path Reordering - Move indexed predicates first
  2. Distance Ordering - Combine filter + sort
  3. Intersection Optimization - Bbox + refinement
  4. Redundant Elimination - Remove duplicate predicates
  5. Predicate Pushdown - Move filters closer to source

Multi-Index Selection

If multiple spatial indexes exist:

  1. Score each index (0-100)
  2. Rank by score
  3. Select highest score
  4. Validate with actual cost estimate

Best Practices

  1. Always index geographic fields:

    • R-tree for general use
    • Grid for uniform distributions
    • Quadtree for adaptive needs
  2. Place spatial predicates early in FILTER:

    FILTER ST_DISTANCE(...) < 50km AND doc.type == "poi"  // Good
    FILTER doc.type == "poi" AND ST_DISTANCE(...) < 50km  // Less optimal
    
  3. Use reasonable query parameters:

    • Distance: 1km-1000km typical range
    • Complexity: keep polygons <100 vertices
    • Avoid overly complex geometries
  4. Monitor performance:

    EXPLAIN FILTER ST_DISTANCE(...) < 50km  // See cost estimate
    
  5. Test with FORCE_SCAN for benchmarking:

    // Baseline: full scan performance
    FILTER ST_DISTANCE(...) < 50km FORCE_SCAN(location)
    
    // Indexed: with index
    FILTER ST_DISTANCE(...) < 50km USE_INDEX(location, "idx")
    

See Also


Phase 6C Status: βœ… Complete
Last Updated: 2026-08-05
Target Release: v2.0.0 (Q3 2026)


ThemisDB 1.9.0-beta Β· Home Β· Module-Index Β· GitHub Β· Issues

ThemisDB Wiki

🏠 Overview

πŸ“š Compendium

πŸš€ Getting Started

πŸ“– Tutorials

πŸ“— User Guide

βš™οΈ Operations & Security

πŸ“Ÿ Ops Runbooks

πŸ—οΈ Architecture

πŸ“ ADRs

πŸ”§ Contributing

πŸ“‹ Governance

πŸ” Audit

🧩 Plugins

πŸ”Œ Adapters

πŸ’‘ Examples

πŸ“¦ Client SDKs

πŸŽ“ Training

πŸ› οΈ Tools

πŸ€– Developer LLM Wiki

Clone this wiki locally