Knowledge Base

title-guard: cleaning 10M B2B contacts on a $5 VPS

2026-07-15title-guardvps

B2B contact databases are full of garbage. Mickey Mouse. John Doe. Chief Butthead. asdf qwerty. Job title: test. Vendors sell data quality as a product. We built a miniature open version — TitleGuard and NameGuard — and measured — end to end, on real benchmarks — what it takes to clean 10 million contacts on a $5 OVH VPS that also runs live websites.

Why this problem

The ZoomInfo / Dun & Bradstreet problem is not missing fields. It is fields that look filled but are useless: fake names, profane joke titles, keyboard mash, placeholder strings. Downstream enrichment, routing, and personalization all treat those strings as real people with real roles. Surface-level filters catch some of it; most of the mess needs something that knows what a job title looks like and what a person name looks like — without a GPU and without a vendor invoice per row.

What we built

Everything runs on a 4-core, 7.6GB RAM box. No GPU. Python, scikit-learn sparse TF-IDF, stdlib csv. About 700 lines of new code.

TitleGuard normalizes a title (double HTML-unescape, abbreviation expansion such as CFO → chief financial officer, a seniority regex ladder), then scores it with character- and word-level TF-IDF nearest-neighbor against 63,934 O*NET title variants covering 1,016 canonical occupations from the US Department of Labor (CC-BY). Separate flags catch profanity, character-bigram gibberish, and placeholder/joke patterns. Output is a confidence score from 0 to 1.

NameGuard is frequency lookup against US Census 2010 surnames (162,253) and SSA baby names 1880–2024 (104,819 first names, 372M births), plus a character-bigram gibberish model and a fake-identity list (Mickey Mouse, John Doe, Seymour Butts, and friends).

Golden eval on titles: 600 real executive titles scraped from SEC EDGAR insider filings — 99.8% score as valid; 12/12 synthetic garbage titles caught. Names: 16/16 legit names pass (including Sean O'Brien, Lakshmi Venkatasubramanian, Guadalupe de la Cruz); 16/16 garbage caught.

The Mickey Mouse lesson

Demo row: Mickey Mouse, Chief Magic Officer at Disney.

Title check alone scores 0.82 — a plausible-looking title against the ONET neighborhood. Name check scores 0.05* with the fake-identity flag. Title alone is not enough. You need both filters.

Measured performance

On the same VPS, one-at-a-time title scoring is 199 ms/title. The vectorized batch path hits 315 titles/s per core — a 63× speedup from batching the sparse matrix multiply. Four processes: 1,030 titles/s. Projected from those measured rates: ten million titles in 2.7 hours on this one box. Names: 42,771/s per core — 10M names in under four minutes.

What it actually cost

The 10M-record job was budgeted at $100–500. Actual marginal cost: about $0 on the owned $5/mo VPS. Renting a 32-vCPU cloud server (~9 core-hours of work) would finish in roughly 20 minutes for under $1. An LLM-API approach with Claude Haiku at batch discount would run roughly $2,000 for the same 10M records. The smarter hybrid: run title-guard on everything, send only the low-confidence tail (~5%) to an LLM — about $110 total.

War stories

Three things broke before the numbers above existed.

  1. `multiprocessing.Pool` deadlocked twice. Fork inherits broken OpenMP thread state after sklearn use; spawn re-imports sklearn before the initializer can cap threads. Fix: four fully independent OS processes launched from bash.

  2. OOM killer took 2 of 4 workers. sklearn kneighbors allocates a 1GB working buffer per process by default — sklearn.set_config(working_memory=256) fixed that. Each worker also built its own index at 2.3GB peak; caching the fitted index with joblib dropped per-worker peak to ~350MB and init from 3.6s to 1.6s.

  3. SSA blocks datacenter IPs. We fetched names.zip via the Wayback Machine.

Honest limits

Pure surface matching fails on function vs rank. Head of Growth Marketing maps toward Soil and Plant Scientists (grow!). Managing Director maps toward Producers and Directors. That is a real class of error, not a tuning footnote. Next phase (C2.5) adds an embedding / function layer on top of the surface match.

What's next

This sits in the Smol LLM Lab series: small, measurable, production-servable ML on tiny hardware. TitleGuard and NameGuard are replicable from free authoritative data — O*NET, SEC EDGAR, Census, SSA — and they already earn their keep as the cheap filter before anything more expensive runs. The hybrid path is the point: classical sparse retrieval on commodity hardware for the bulk, LLM only where confidence fails.