Skip to main content

Batch Address Cleanup — One Million Records

Problem: One million customer addresses. Coordinates, normalized form, confidence. Once.
Before any API call, run the preparation stage.One million rows typically contains far fewer distinct addresses. Normalizing then deduplicating routinely reduces the billable set by a large fraction, and it costs one SELECT DISTINCT.Report that number before you report a timeline. It changes the conversation.

The pipeline

Schema

Stage 1 — preparation

Use libpostal for international normalization rather than a regex you will regret. It is self-hosted, removes normalization from your API bill entirely, and does not geocode.

Stage 2 — chunk and queue

Stage 3 — poll, and the three codes that are not errors

Code that treats any non-200 as failure will log a perfectly successful job as broken, every night, until someone reads the spec.

Stage 4 — partial success and the confidence threshold

Storing a coordinate without its confidence converts a probabilistic estimate into a fact. Every downstream consumer treats it as truth.query_score: 0.95 with field_score.houseNumber: 0.4 matched the street confidently and the house number badly. That is a failed delivery. Persist field_score.

Stage 5 — monitoring

Nobody is watching a nightly job. Instrument it as if it will fail at 3am, because it will.
Alert on the confidence distribution shifting, not just on errors.A map release, a source-system change, or a normalization regression shows up as a distribution shift long before it shows up as a failure.
Webhooks exist in beta, with placeholders like ${JOB_ID} and ${JOB_STATUS}. If your pipeline’s correctness depends on a webhook arriving, build the polling fallback anyway.

Stage 6 — export, and the steady state

The end state: every address has coordinates, a normalized form, a confidence score, and a timestamp.New addresses arrive at a trickle and are geocoded in real time. The batch job runs once, at migration, and then rarely. On the second run, WHERE geocoded_at IS NULL returns almost nothing.Architect the pipeline for the backfill. It will spend its life doing very little.
Do not use a TTL. A thirty-day expiry invalidates a stable rooftop match for a building that has stood since 1904, and does nothing about the subdivision that opened yesterday. Invalidate on map release, low confidence, explicit correction, or a failed delivery.
A permanent geocode cache is a permanent record of where people live. Coordinates that identify a residence are personal data. Right-to-erasure applies to the cache; it is not exempt because it is “just a cache.”Consider separating the address → coordinate mapping (a fact about the world) from the person → address mapping (personal data). Delete the second; keep the first.In a multi-tenant system, a shared geocode cache is a data leak — hit patterns are a side channel. Key by tenant.

Common mistakes

Not deduplicating. Paying for repetition. Not normalizing before deduplicating. Catches only exact matches. Treating Batch API as a bulk endpoint. It is a job lifecycle. Losing the job ID on restart. Full rebill. Retrying 204. Zero errors. Aggressively retrying 404. The job has not finished. Hammering after 429. Assuming all-or-nothing. Read /errors. Handle partial success. Never deleting jobs. They accumulate. Loading a million results into memory. Stream. Persisting low-confidence matches as truth. A time-based TTL. Not writing results into a durable cache. You will do this again next quarter, and pay again. Onboarding jobs starving nightly enrichment. Concurrency is per-contract. Prioritize.

High-Volume Geocoding

Queues, idempotency, and what to monitor.

Caching Geocoding Results

Normalization, invalidation, and the privacy question.

Batch Geocoding

The single-job lifecycle in isolation.

Address Validation

Why geocoding and deliverability are different products.

HERE documentation


Need production HERE API keys or implementation support? Placematic is an official HERE Technologies reseller and implementation partner. Talk to us.