Skip to content

Commit

Permalink
[BREAKING] New bounds calculation methods
Browse files Browse the repository at this point in the history
* Remove `--disable-bounds` flag and `disable_bounds` config parameters.
* Add `--bounds` param/config with a 5s `quick` as default:
  * `quick`: Compute table geometry bounds, but abort if it takes longer than 5 seconds
  * `calc`:  Compute table geometry bounds. The startup time may be significant. Make sure all GEO columns have indexes
  * `skip`:  Skip bounds calculation. The bounds will be set to the whole world
  • Loading branch information
nyurik committed Oct 21, 2023
1 parent e377bd6 commit e3151b9
Show file tree
Hide file tree
Showing 16 changed files with 176 additions and 38 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion debian/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ worker_processes: 8
# default_srid: 4326
# pool_size: 20
# max_feature_count: 1000
# disable_bounds: false
# bounds: skip

# pmtiles:
# paths:
Expand Down
8 changes: 5 additions & 3 deletions docs/src/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ postgres:
# Limit the number of table geo features included in a tile. Unlimited by default.
max_feature_count: 1000

# Control the automatic generation of bounds for spatial tables [default: false]
# If enabled, it will spend some time on startup to compute geometry bounds.
disable_bounds: false
# Control the automatic generation of bounds for spatial tables [default: quick]
# 'calc' - compute table geometry bounds on startup.
# 'quick' - same as 'calc', but the calculation will be aborted if it takes more than 5 seconds.
# 'skip' - do not compute table geometry bounds on startup.
bounds: skip

# Enable automatic discovery of tables and functions.
# You may set this to `false` to disable.
Expand Down
26 changes: 22 additions & 4 deletions docs/src/run-with-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,51 @@ You can configure Martin using command-line interface. See `martin --help` or `c
Usage: martin [OPTIONS] [CONNECTION]...

Arguments:
[CONNECTION]... Connection strings, e.g. postgres://... or /path/to/files
[CONNECTION]...
Connection strings, e.g. postgres://... or /path/to/files

Options:
-c, --config <CONFIG>
Path to config file. If set, no tile source-related parameters are allowed

--save-config <SAVE_CONFIG>
Save resulting config to a file or use "-" to print to stdout. By default, only print if sources are auto-detected

-s, --sprite <SPRITE>
Export a directory with SVG files as a sprite source. Can be specified multiple times

-k, --keep-alive <KEEP_ALIVE>
Connection keep alive timeout. [DEFAULT: 75]

-l, --listen-addresses <LISTEN_ADDRESSES>
The socket address to bind. [DEFAULT: 0.0.0.0:3000]

-W, --workers <WORKERS>
Number of web server workers
-b, --disable-bounds
Disable the automatic generation of bounds for spatial PG tables

-b, --bounds <BOUNDS>
Specify how bounds should be computed for the spatial PG tables. [DEFAULT: quick]

Possible values:
- quick: Compute table geometry bounds, but abort if it takes longer than 5 seconds
- calc: Compute table geometry bounds. The startup time may be significant. Make sure all GEO columns have indexes
- skip: Skip bounds calculation. The bounds will be set to the whole world

--ca-root-file <CA_ROOT_FILE>
Loads trusted root certificates from a file. The file should contain a sequence of PEM-formatted CA certificates

-d, --default-srid <DEFAULT_SRID>
If a spatial PG table has SRID 0, then this default SRID will be used as a fallback

-p, --pool-size <POOL_SIZE>
Maximum connections pool size [DEFAULT: 20]

-m, --max-feature-count <MAX_FEATURE_COUNT>
Limit the number of features in a tile from a PG table source

-h, --help
Print help
Print help (see a summary with '-h')

-V, --version
Print version
```
2 changes: 1 addition & 1 deletion martin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "martin"
# Once the release is published with the hash, update https://github.com/maplibre/homebrew-martin
version = "0.9.3"
version = "0.10.0"
authors = ["Stepan Kuzmin <[email protected]>", "Yuri Astrakhan <[email protected]>", "MapLibre contributors"]
description = "Blazing fast and lightweight tile server with PostGIS, MBTiles, and PMTiles support"
keywords = ["maps", "tiles", "mbtiles", "pmtiles", "postgis"]
Expand Down
3 changes: 2 additions & 1 deletion martin/src/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ mod srv;

pub use connections::{Arguments, State};
pub use environment::{Env, OsEnv};
pub use root::Args;
pub use pg::{BoundsCalcType, DEFAULT_BOUNDS_TIMEOUT};
pub use root::{Args, MetaArgs};
29 changes: 22 additions & 7 deletions martin/src/args/pg.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
use std::time::Duration;

use clap::ValueEnum;
use log::{info, warn};
use serde::{Deserialize, Serialize};

use crate::args::connections::Arguments;
use crate::args::connections::State::{Ignore, Take};
use crate::args::environment::Env;
use crate::pg::{PgConfig, PgSslCerts, POOL_SIZE_DEFAULT};
use crate::utils::{OptBoolObj, OptOneMany};

// Must match the help string for BoundsType::Quick
pub const DEFAULT_BOUNDS_TIMEOUT: Duration = Duration::from_secs(5);

#[derive(PartialEq, Eq, Default, Debug, Clone, Copy, Serialize, Deserialize, ValueEnum)]
#[serde(rename_all = "lowercase")]
pub enum BoundsCalcType {
/// Compute table geometry bounds, but abort if it takes longer than 5 seconds.
#[default]
Quick,
/// Compute table geometry bounds. The startup time may be significant. Make sure all GEO columns have indexes.
Calc,
/// Skip bounds calculation. The bounds will be set to the whole world.
Skip,
}

#[derive(clap::Args, Debug, PartialEq, Default)]
#[command(about, version)]
pub struct PgArgs {
/// Disable the automatic generation of bounds for spatial PG tables.
/// Specify how bounds should be computed for the spatial PG tables. [DEFAULT: quick]
#[arg(short = 'b', long)]
pub disable_bounds: bool,
pub bounds: Option<BoundsCalcType>,
/// Loads trusted root certificates from a file. The file should contain a sequence of PEM-formatted CA certificates.
#[arg(long)]
pub ca_root_file: Option<std::path::PathBuf>,
Expand Down Expand Up @@ -41,11 +60,7 @@ impl PgArgs {
connection_string: Some(s),
ssl_certificates: certs.clone(),
default_srid,
disable_bounds: if self.disable_bounds {
Some(true)
} else {
None
},
bounds: self.bounds,
max_feature_count: self.max_feature_count,
pool_size: self.pool_size,
auto_publish: OptBoolObj::NoValue,
Expand Down
2 changes: 1 addition & 1 deletion martin/src/args/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub struct MetaArgs {
/// By default, only print if sources are auto-detected.
#[arg(long)]
pub save_config: Option<PathBuf>,
/// [Deprecated] Scan for new sources on sources list requests
/// **Deprecated** Scan for new sources on sources list requests
#[arg(short, long, hide = true)]
pub watch: bool,
/// Connection strings, e.g. postgres://... or /path/to/files
Expand Down
23 changes: 15 additions & 8 deletions martin/src/pg/config.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::ops::Add;
use std::time::Duration;

use futures::future::try_join;
use log::warn;
use serde::{Deserialize, Serialize};
use tilejson::TileJSON;

use crate::args::{BoundsCalcType, DEFAULT_BOUNDS_TIMEOUT};
use crate::config::{copy_unrecognized_config, UnrecognizedValues};
use crate::pg::config_function::FuncInfoSources;
use crate::pg::config_table::TableInfoSources;
Expand Down Expand Up @@ -42,7 +44,7 @@ pub struct PgConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub default_srid: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disable_bounds: Option<bool>,
pub bounds: Option<BoundsCalcType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_feature_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -123,13 +125,18 @@ impl PgConfig {

pub async fn resolve(&mut self, id_resolver: IdResolver) -> crate::Result<TileInfoSources> {
let pg = PgBuilder::new(self, id_resolver).await?;
let inst_tables = on_slow(pg.instantiate_tables(), Duration::from_secs(5), || {
if pg.disable_bounds() {
warn!("Discovering tables in PostgreSQL database '{}' is taking too long. Bounds calculation is already disabled. You may need to tune your database.", pg.get_id());
} else {
warn!("Discovering tables in PostgreSQL database '{}' is taking too long. Make sure your table geo columns have a GIS index, or use --disable-bounds CLI/config to skip bbox calculation.", pg.get_id());
}
});
let inst_tables = on_slow(
pg.instantiate_tables(),
// warn only if default bounds timeout has already passed
DEFAULT_BOUNDS_TIMEOUT.add(Duration::from_secs(1)),
|| {
if pg.bounds() == BoundsCalcType::Skip {
warn!("Discovering tables in PostgreSQL database '{}' is taking too long. Make sure your table geo columns have a GIS index, or use '--bounds skip' CLI/config to skip bbox calculation.", pg.get_id());
} else {
warn!("Discovering tables in PostgreSQL database '{}' is taking too long. Bounds calculation is already disabled. You may need to tune your database.", pg.get_id());
}
},
);
let ((mut tables, tbl_info), (funcs, func_info)) =
try_join(inst_tables, pg.instantiate_functions()).await?;

Expand Down
13 changes: 7 additions & 6 deletions martin/src/pg/configurator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use futures::future::join_all;
use itertools::Itertools;
use log::{debug, error, info, warn};

use crate::args::BoundsCalcType;
use crate::pg::config::{PgConfig, PgInfo};
use crate::pg::config_function::{FuncInfoSources, FunctionInfo};
use crate::pg::config_table::{TableInfo, TableInfoSources};
Expand Down Expand Up @@ -59,7 +60,7 @@ pub struct PgBuilderTables {
pub struct PgBuilder {
pool: PgPool,
default_srid: Option<i32>,
disable_bounds: bool,
bounds: BoundsCalcType,
max_feature_count: Option<usize>,
auto_functions: Option<PgBuilderFuncs>,
auto_tables: Option<PgBuilderTables>,
Expand Down Expand Up @@ -97,7 +98,7 @@ impl PgBuilder {
Ok(Self {
pool,
default_srid: config.default_srid,
disable_bounds: config.disable_bounds.unwrap_or_default(),
bounds: config.bounds.unwrap_or_default(),
max_feature_count: config.max_feature_count,
id_resolver,
tables: config.tables.clone().unwrap_or_default(),
Expand All @@ -107,8 +108,8 @@ impl PgBuilder {
})
}

pub fn disable_bounds(&self) -> bool {
self.disable_bounds
pub fn bounds(&self) -> BoundsCalcType {
self.bounds
}

pub fn get_id(&self) -> &str {
Expand Down Expand Up @@ -160,7 +161,7 @@ impl PgBuilder {
id2,
merged_inf,
self.pool.clone(),
self.disable_bounds,
self.bounds,
self.max_feature_count,
));
}
Expand Down Expand Up @@ -206,7 +207,7 @@ impl PgBuilder {
id2,
db_inf,
self.pool.clone(),
self.disable_bounds,
self.bounds,
self.max_feature_count,
));
}
Expand Down
27 changes: 24 additions & 3 deletions martin/src/pg/table_source.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use std::collections::HashMap;

use futures::pin_mut;
use log::{debug, info, warn};
use postgis::ewkb;
use postgres_protocol::escape::{escape_identifier, escape_literal};
use serde_json::Value;
use tilejson::Bounds;
use tokio::time::timeout;

use crate::args::{BoundsCalcType, DEFAULT_BOUNDS_TIMEOUT};
use crate::pg::config::PgInfo;
use crate::pg::config_table::TableInfo;
use crate::pg::configurator::SqlTableInfoMapMapMap;
Expand Down Expand Up @@ -96,16 +99,34 @@ pub async fn table_to_query(
id: String,
mut info: TableInfo,
pool: PgPool,
disable_bounds: bool,
bounds_type: BoundsCalcType,
max_feature_count: Option<usize>,
) -> Result<(String, PgSqlInfo, TableInfo)> {
let schema = escape_identifier(&info.schema);
let table = escape_identifier(&info.table);
let geometry_column = escape_identifier(&info.geometry_column);
let srid = info.srid;

if info.bounds.is_none() && !disable_bounds {
info.bounds = calc_bounds(&pool, &schema, &table, &geometry_column, srid).await?;
if info.bounds.is_none() {
match bounds_type {
BoundsCalcType::Skip => {}
BoundsCalcType::Quick | BoundsCalcType::Calc => {
let bounds = calc_bounds(&pool, &schema, &table, &geometry_column, srid);
if bounds_type == BoundsCalcType::Calc {
info.bounds = bounds.await?;
} else {
pin_mut!(bounds);
if let Ok(bounds) = timeout(DEFAULT_BOUNDS_TIMEOUT, &mut bounds).await {
info.bounds = bounds?;
} else {
warn!(
"Timeout computing {} bounds for {id}, aborting query. Use --bounds=calc to wait until complete, or check the table for missing indices.",
info.format_id(),
);
}
}
}
}
}

let properties = if let Some(props) = &info.properties {
Expand Down
6 changes: 6 additions & 0 deletions tests/expected/auto/cmp.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
}
}
],
"bounds": [
-179.27313970132585,
-80.46177157848345,
179.11187181086706,
84.93092095128937
],
"description": "public.points1.geom\npublic.points2.geom",
"name": "table_source,points1,points2"
}
6 changes: 6 additions & 0 deletions tests/expected/auto/points3857_srid.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
}
}
],
"bounds": [
-161.40590777554058,
-81.50727021609012,
172.51549126768532,
84.2440187164111
],
"description": "public.points3857.geom",
"name": "points3857"
}
6 changes: 6 additions & 0 deletions tests/expected/auto/table_source.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
}
}
],
"bounds": [
-2,
-1,
142.84131509869133,
45
],
"name": "table_source",
"foo": {
"bar": "foo"
Expand Down
Loading

0 comments on commit e3151b9

Please sign in to comment.