From: Sarah Hoffmann Date: Wed, 20 Oct 2021 20:05:15 +0000 (+0200) Subject: add new replication mode catch-up X-Git-Tag: v4.0.0~14^2~1 X-Git-Url: https://git.openstreetmap.org./nominatim.git/commitdiff_plain/c1fa70639ba2f861cd6a7718e5b02f0ade763abc add new replication mode catch-up This mode gets updates until the server reports no new diffs anymore. Also adds additional indexing, when the main indexing step left a couple of objects to process. This happens only when the next update is expected to be more than 40min away. --- diff --git a/nominatim/clicmd/replication.py b/nominatim/clicmd/replication.py index a22cef47..44eec5f1 100644 --- a/nominatim/clicmd/replication.py +++ b/nominatim/clicmd/replication.py @@ -42,14 +42,17 @@ class UpdateReplication: help='Initialise the update process') group.add_argument('--no-update-functions', dest='update_functions', action='store_false', - help=("Do not update the trigger function to " - "support differential updates.")) + help="Do not update the trigger function to " + "support differential updates (EXPERT)") group = parser.add_argument_group('Arguments for updates') group.add_argument('--check-for-updates', action='store_true', help='Check if new updates are available and exit') group.add_argument('--once', action='store_true', - help=("Download and apply updates only once. When " - "not set, updates are continuously applied")) + help="Download and apply updates only once. When " + "not set, updates are continuously applied") + group.add_argument('--catch-up', action='store_true', + help="Download and apply updates until no new " + "data is available on the server") group.add_argument('--no-index', action='store_false', dest='do_index', help=("Do not index the new data. Only usable " "together with --once")) @@ -92,28 +95,40 @@ class UpdateReplication: round_time(end - start_import), round_time(end - batchdate)) + + @staticmethod + def _compute_update_interval(args): + if args.catch_up: + return 0 + + update_interval = args.config.get_int('REPLICATION_UPDATE_INTERVAL') + # Sanity check to not overwhelm the Geofabrik servers. + if 'download.geofabrik.de' in args.config.REPLICATION_URL\ + and update_interval < 86400: + LOG.fatal("Update interval too low for download.geofabrik.de.\n" + "Please check install documentation " + "(https://nominatim.org/release-docs/latest/admin/Import-and-Update#" + "setting-up-the-update-process).") + raise UsageError("Invalid replication update interval setting.") + + return update_interval + + @staticmethod def _update(args): from ..tools import replication from ..indexer.indexer import Indexer from ..tokenizer import factory as tokenizer_factory + update_interval = UpdateReplication._compute_update_interval(args) + params = args.osm2pgsql_options(default_cache=2000, default_threads=1) params.update(base_url=args.config.REPLICATION_URL, - update_interval=args.config.get_int('REPLICATION_UPDATE_INTERVAL'), + update_interval=update_interval, import_file=args.project_dir / 'osmosischange.osc', max_diff_size=args.config.get_int('REPLICATION_MAX_DIFF'), indexed_only=not args.once) - # Sanity check to not overwhelm the Geofabrik servers. - if 'download.geofabrik.de' in params['base_url']\ - and params['update_interval'] < 86400: - LOG.fatal("Update interval too low for download.geofabrik.de.\n" - "Please check install documentation " - "(https://nominatim.org/release-docs/latest/admin/Import-and-Update#" - "setting-up-the-update-process).") - raise UsageError("Invalid replication update interval setting.") - if not args.once: if not args.do_index: LOG.fatal("Indexing cannot be disabled when running updates continuously.") @@ -135,8 +150,7 @@ class UpdateReplication: index_start = dt.datetime.now(dt.timezone.utc) indexer = Indexer(args.config.get_libpq_dsn(), tokenizer, args.threads or 1) - indexer.index_boundaries(0, 30) - indexer.index_by_rank(0, 30) + indexer.index_full(analyse=False) with connect(args.config.get_libpq_dsn()) as conn: status.set_indexed(conn, True) @@ -145,10 +159,15 @@ class UpdateReplication: else: index_start = None + if state is replication.UpdateState.NO_CHANGES and \ + args.catch_up or update_interval > 40*60: + while indexer.has_pending(): + indexer.index_full(analyse=False) + if LOG.isEnabledFor(logging.WARNING): UpdateReplication._report_update(batchdate, start, index_start) - if args.once: + if args.once or (args.catch_up and state is replication.UpdateState.NO_CHANGES): break if state is replication.UpdateState.NO_CHANGES: diff --git a/nominatim/indexer/indexer.py b/nominatim/indexer/indexer.py index d0cfb391..50bd232e 100644 --- a/nominatim/indexer/indexer.py +++ b/nominatim/indexer/indexer.py @@ -91,6 +91,17 @@ class Indexer: self.num_threads = num_threads + def has_pending(self): + """ Check if any data still needs indexing. + This function must only be used after the import has finished. + Otherwise it will be very expensive. + """ + with connect(self.dsn) as conn: + with conn.cursor() as cur: + cur.execute("SELECT 'a' FROM placex WHERE indexed_status > 0 LIMIT 1") + return cur.rowcount > 0 + + def index_full(self, analyse=True): """ Index the complete database. This will first index boundaries followed by all other objects. When `analyse` is True, then the diff --git a/test/python/test_cli_replication.py b/test/python/test_cli_replication.py index dcaeaf25..2dd35c0e 100644 --- a/test/python/test_cli_replication.py +++ b/test/python/test_cli_replication.py @@ -53,8 +53,7 @@ def init_status(temp_db_conn, status_table): @pytest.fixture def index_mock(monkeypatch, tokenizer_mock, init_status): mock = MockParamCapture() - monkeypatch.setattr(nominatim.indexer.indexer.Indexer, 'index_boundaries', mock) - monkeypatch.setattr(nominatim.indexer.indexer.Indexer, 'index_by_rank', mock) + monkeypatch.setattr(nominatim.indexer.indexer.Indexer, 'index_full', mock) return mock @@ -122,7 +121,7 @@ class TestCliReplication: with pytest.raises(IndexError): self.call_nominatim() - assert index_mock.called == 4 + assert index_mock.called == 2 def test_replication_update_continuous_no_change(self, monkeypatch, index_mock): @@ -137,6 +136,6 @@ class TestCliReplication: with pytest.raises(IndexError): self.call_nominatim() - assert index_mock.called == 2 + assert index_mock.called == 1 assert sleep_mock.called == 1 assert sleep_mock.last_args[0] == 60