Turning am unsearchable file into a searchable database, safely, meaning without corrupting or silently altering a single original value

From 5 GB of XML to Instant Search: The 8 Steps of Safe Data Migration | INGOAMPT
INGOAMPT · ARTICLES
Data Engineering · Databases · Step by Step for Beginners

From 5 GB of XML to Instant Search

The eight steps of safely turning a giant public dataset into a searchable SQL database — explained for complete beginners, with one worked example the whole way through.

8 steps · 14 figures · a simple example for every part · ~40 min read

Imagine you download one file that contains almost every computer-science paper ever published: titles, authors, years, venues, DOIs. That file really exists. It is the DBLP computer-science bibliography, distributed as a single XML file that is roughly 1 GB compressed and about 5 GB when unpacked, holding on the order of 8.5 million publication records (plus millions of author and link entities — which is why people often say “over 12 million records”).

You can open it in a text editor and see the data. But you cannot ask it a question. There is no search box. Type “find every paper by this author from 2019” and the file just sits there, silent.

This article is the recipe for fixing that — turning an enormous, unsearchable file into a small, fast, searchable database, safely, meaning without corrupting or silently altering a single original value. We will use one running example all the way through: let us imagine we want to convert the DBLP XML dump into SQLite safely. Every step, though, is a reusable recipe you can apply to your own data: a government open-data dump, a Wikipedia export, a museum catalog, your company’s legacy records.

The one principle to carry through the whole article

You are not going to “clean” the data by editing it in place. You are going to preserve every original value exactly as it came, and store any tidied-up or search-friendly versions separately, clearly labelled as derived. That single discipline is what separates a trustworthy dataset from a mystery file.

FIG. 1 THE WHOLE JOURNEY ON ONE PAGE RAW FILE 5 GB XML unsearchable 1 verify 2 profile 3 model 4 parse 5 test 6 load DATABASE correct copy still slow 7 prove 8 index SEARCH millisec ranked THREE THINGS FIGHT YOU ALONG THE WAY: TIME hours-long jobs get interrupted MEMORY 5 GB will not fit in a small laptop’s RAM TRUST “it finished” is not the same as “it is correct” Steps 4 beats MEMORY · steps 5-6 beat TIME · steps 1, 2 and 7 beat TRUST · step 8 makes it useful. Nothing here is difficult on its own. The skill is doing them in the right order.
Figure 1 — The map. Eight steps take you from a silent 5 GB file to a search box that answers in milliseconds. Keep this picture in mind; every section below is one box on this line.

STEP 1Get the source and prove it is intact

Industry name: source verification · integrity checking · checksum validation

What it is

Before you process a single byte, you download the file and prove it arrived complete and unmodified. The tool is a checksum (also called a hash): a short fingerprint such as an MD5 or SHA-256 string that changes completely if even one bit of the file changes. You also record provenance: where the file came from, when you downloaded it, and which exact version it is.

Why it matters

Downloads get truncated. Disks flip bits. Files get swapped. If your source is silently broken, everything you build on top is built on sand — and you will not discover it for weeks. A checksum gives you near-mathematical certainty that your copy matches the publisher’s copy. MD5 is fine for catching accidental corruption; SHA-256 is the safer choice when tampering matters.

The DBLP example

DBLP publishes three files side by side: dblp.xml.gz (the data), dblp.xml.gz.md5 (its checksum) and dblp.dtd (the grammar file the parser needs). The safe routine:

  1. Download all three from the official directory.
  2. Recompute the MD5 of your .gz and compare it with the published one.
  3. Test the archive itself with gzip -t before unpacking.
  4. Write down the provenance. DBLP publishes monthly snapshots with their own DOIs, and recommends citing the snapshot rather than the ever-changing daily file — so your work stays reproducible. Note also the licence: DBLP’s metadata is released under CC0 (public domain), so reuse is free.
FIG. 2 THE SEAL AND THE PACKING SLIP dblp.xml.gz dblp.xml.gz.md5 dblp.dtd always download all three hash it published: 7ee3323762f1fed8… yours: 7ee3323762f1fed8… MATCH proceed PROVENANCE CARD (write this down and keep it forever) source URL · snapshot date · DOI · file size · checksum · licence without it, nobody can ever reproduce your database – including future you
Figure 2 — Check the seal before you unpack. The checksum is the tamper-evident seal; the provenance card is the packing slip. Both cost seconds now and save weeks later.
Simple example You order a parcel. Before signing, you check the security seal is unbroken (checksum) and glance at the delivery note: what it is, who sent it, when (provenance). You do not tear it open, scatter everything on the floor, and only then wonder whether the box was the right one.

STEP 2Look before you leap

Industry name: data profiling · data auditing

What it is

Data profiling means examining the raw data to learn its real shape before you design anything. How many records? Which record types exist? Which fields appear, and how often? Any duplicates or missing keys? And — crucially — you open a handful of real records and read them with your own eyes.

Why it matters

Migration guides call a thorough data audit “one of the most critical yet often underestimated best practices… before moving a single byte.” Skip it and you are migrating data you do not understand; you will discover its surprises only after the load, when fixing them is expensive. Profiling is how you replace assumptions with numbers.

The DBLP example

A profiling pass over DBLP reveals facts that directly shape the design: the collection is dominated by conference papers (inproceedings, roughly 3.9 million, about 46%) and journal articles (about 3.4 million, roughly 40%), with a large informal/preprint category (around 921,000) and smaller counts of books, theses and data records. It also reveals traps: www records look like publications but are actually author home pages; a single paper carries many <author> elements in a meaningful order; and DOIs hide inside <ee> link fields rather than a tidy doi column.

FIG. 3 WHAT A PROFILING REPORT LOOKS LIKE RECORDS BY TYPE (share of the collection) inproceedings ~3.9M (46%) article ~3.4M (40%) informal / preprints ~921k books, theses, data, incollection FIELD COVERAGE (how often each field is present) title: ~100% year: ~98% DOI present: only some – plan for it TRAPS FOUND BY READING – www records are author pages – author ORDER carries meaning – DOI hides inside link fields
Figure 3 — Numbers plus eyeballs. Counts tell you the shape; reading real records tells you the surprises. You need both, and you need them before designing the database.
Simple example Before renovating a house you did not build, you walk through every room with a notepad: how many rooms, which have plumbing, where the load-bearing walls are, what is already broken. You do not start knocking down walls and then ask what they were holding up.

STEP 3Design the destination

Industry name: schema design · data modelling

What it is

Schema design is deciding the tables, columns, keys and relationships in your target database, and writing down the mapping from the source’s shape to that structure. XML is a tree; SQL is tables; somebody has to translate, deliberately.

Why it matters

Do it well and queries are fast and honest. Do it carelessly and you silently lose information — most famously ordering. Three professional rules apply: preserve ordered relationships, keep original values separate from derived ones, and store provenance inside the data so every row can be traced to the exact snapshot it came from.

The DBLP example

  • A publications table: one row per record, keyed by the DBLP key, with type, title, year, venue, doi.
  • An authors table plus a publication_authors join table — with an explicit author_position column, because in science first and last author mean different things. Tables have no built-in row order, so if order matters you must store it as data.
  • Provenance columns: snapshot DOI, source checksum, import date.
  • Original vs derived: keep the exact original title; if you later build a lowercased, punctuation-stripped version for searching, put it in a separate column such as title_normalized and never overwrite the original.
FIG. 4 ONE XML RECORD BECOMES SEVERAL TIDY ROWS <inproceedings key=”conf/x/A17″> <author>Ada Lovelace</author> <author>Alan Turing</author> <title>On Machines.</title> <year>2017</year> <ee>https://doi.org/10…/x</ee> </inproceedings> map publications key | type | title | year | venue | doi conf/x/A17 | inproceedings | On Machines. | 2017 | … publication_authors (the ORDER lives here) conf/x/A17 | Ada Lovelace | position 1 conf/x/A17 | Alan Turing | position 2 provenance (attached to every import) snapshot DOI | source checksum | imported_at NEVER do this: authors = “Ada Lovelace, Alan Turing” one text blob = order preserved by luck, and you can never count an author’s papers
Figure 4 — The translation. One tree-shaped record fans out into a main row, one row per author with an explicit position, and a provenance record. The join table is where author order is stored rather than hoped for.
Simple example It is like labelling shelves before unpacking the moving boxes: “books here, left-to-right in series order; a card in each drawer noting which box it came from.” The label and the card cost nothing while unpacking — and are impossible to reconstruct afterwards.

STEP 4Build the reader

Industry name: parsing · extraction (the E of ETL)

What it is

Now you write the code that reads the XML and produces rows. For huge files the technique is streaming parsing (SAX, or iterparse in Python): process one record at a time, then throw it away — instead of loading the whole 5 GB tree into memory.

Why it matters

A DOM parser loads the entire document into RAM, typically needing several times the file size. On a normal laptop that is an instant crash. Streaming keeps memory bounded and flat: one record at a time, forever. You also want each write wrapped in a transaction so a crash can never leave half a record behind.

The DBLP example

Use iterparse with one crucial detail: after handling each record element, call elem.clear() to free it — the classic beginner mistake is letting parsed elements pile up until memory dies. DBLP adds two specific gotchas: the dblp.dtd is a “private” resource you must download and keep next to the XML (it defines the character entities such as accented letters), and because the file contains an enormous number of entities, parsers may need their entity-expansion security limit raised. Each parsed record becomes a small immutable object (key, title, year, ordered authors) handed to the loader.

FIG. 5 TWO WAYS TO READ A 5 GB FILE DOM: pour the whole tanker into the bathtub memory: grows with the file – CRASH STREAMING: one cup at a time from a tap memory: flat and tiny – always safe read record → make a small object → write it → clear() → next record THE CLASSIC BUG: forgetting clear() the parser keeps every finished record attached to the tree – your “streaming” quietly becomes DOM again
Figure 5 — Streaming is a tap, not a flood. The technique only works if you actively release each record after use; otherwise memory grows exactly as if you had loaded the whole file.
Simple example Reading a 1,000-page novel: DOM photocopies all 1,000 pages onto your desk first (desk overflows). Streaming reads page by page and recycles each one. The book’s size stops mattering.

STEP 5Test small first

Industry name: staged validation · sample loads · dry run

What it is

Before the multi-hour full load, run the entire pipeline on small samples — say 1,000 records, then 100,000 — and validate the output each time.

Why it matters

Validation is not something you save for the end; it starts before the migration does. Catching a mapping bug on 1,000 records costs seconds. Catching the same bug after a full load costs the entire run — and your afternoon. Small runs also give you the two numbers you need to plan the big one: records per second and bytes per record.

The DBLP example

Load the first 1,000 records into a throwaway database and check: does the row count match, minus any record types you deliberately skip? Do a few known papers have their authors in the right order? Did the DOI land in the right column when <ee> contained one? Then scale to 100,000, watch memory and speed, and multiply out: if 100,000 records take one minute and 220 MB, then 8.5 million will take roughly an hour and a few gigabytes. Only then commit to the real run.

FIG. 6 CLIMB THE LADDER – NEVER JUMP TO THE TOP 1,000 records seconds – does it work at all? check counts + author order 100,000 records a minute – does it SCALE? measure records/second measure bytes/record now you can predict the big run THE FULL LOAD millions of records hours, unattended only start this when the two rehearsals were clean no surprises left to find
Figure 6 — Three rungs. Each rehearsal answers a different question: does it work, does it scale, and only then do it for real. The middle rung is also where your time and size estimates come from.
Simple example A chef tastes the sauce from a teaspoon before serving the whole pot. Cheap to fix now; expensive and embarrassing to fix at the table.

STEP 6Run the full load

Industry name: loading · ingestion · bulk load (the L of ETL)

What it is

Parsing every record and inserting it into the database, for real — the hours-long unattended run.

Why it matters

At millions of records, how you insert dominates everything. Three ideas make the difference between a job that finishes and one that ruins your day:

  • Batched transactions. A transaction is a group of writes that all succeed or all un-happen. Committing one record at a time is agonisingly slow (the database must force each one to disk); committing everything in one giant transaction means a crash at 90% loses everything. Committing every 1,000–10,000 records captures nearly all the speed while capping what a crash can cost.
  • Checkpoints. After each committed batch, durably record a bookmark: “complete through record 6,000,000.” On restart, resume there instead of at zero.
  • Do not build search indexes yet. Indexes must be updated on every single insert. Load first, index at the end (that is Step 8).
FIG. 7 HOW BIG SHOULD EACH TRUCKLOAD BE? A – one record per commit [1] [1] [1] [1] [1] [1] [1] [1] … safest, but the disk is forced to sync thousands of times – far too slow B – everything in one giant commit fast, but a crash at 90% rolls back EVERYTHING – hours lost, nothing kept C – batches of ~1,000 (the professional choice) a crash costs at most ONE batch plus a checkpoint after each batch: “complete through record N” – resume, never restart
Figure 7 — Batching is the compromise that wins. It keeps nearly the full speed of one big transaction while limiting a crash to a single batch — and the checkpoint turns a catastrophic restart into a minor resume.
Simple example Moving house: you do not carry one item at a time and lock the door after each trip (A), nor pile everything into one overloaded truck that could tip over (B). You load sensible truckloads and tick a checklist after each one (C).

STEP 7Prove it is correct

Industry name: reconciliation · data validation · data quality checks

This is where amateurs stop and professionals begin. The importer printing “Done!” tells you the program finished. It does not tell you the data is correct. Step 7 is how you turn belief into evidence — and it is genuinely the easiest step of all, because you are only counting and comparing.

7.1 What reconciliation means

Data reconciliation is the formal process of verifying a migration by comparing the source against the target. It is normally the final step of any migration test. Six checks, from cheapest to deepest:

The reconciliation battery
CheckQuestion it answersHow
1. Total countDid every record arrive?Records read from the source vs. rows written to the database
2. Per-type countsDid one kind of record go missing?Compare article / inproceedings / book / thesis counts separately
3. Key uniquenessAny duplicates or collisions?Group by key, expect zero groups larger than one
4. Referential integrityAny orphan links?PRAGMA foreign_key_check — every author link must point at a real record
5. Physical integrityIs the file itself sound?PRAGMA integrity_check (or the faster quick_check)
6. SamplingAre the contents right, not just the count?Pull random records, compare field by field with the raw source

7.2 Why two independent counts are strong evidence

Checks 1 and 2 rest on a 600-year-old idea: double-entry bookkeeping. Accountants record every transaction twice, from two directions, and the totals must agree; a mismatch is a guaranteed sign of an error. Here, one count comes from scanning the source file, the other from querying the database — two different tools, two different code paths, two different moments in time. For both to be wrong in exactly the same way is essentially impossible. That is why agreement is proof rather than hope.

FIG. 8 THE BALANCE SHEET OF A MIGRATION SOURCE (counted from the XML) total records ………… 12,796,119 inproceedings ………… 3,921,218 article ……………… 4,387,689 book, thesis, other …… …… counted BEFORE loading, by a separate tool TARGET (counted from the database) total rows …………… 12,796,119 inproceedings ………… 3,921,218 article ……………… 4,387,689 book, thesis, other …… …… counted AFTER loading, by SQL = PLUS: 0 duplicate keys · 0 orphan links (foreign_key_check) · integrity_check = ok · samples match Only when ALL of these pass is the migration allowed to be called “done”.
Figure 8 — Two independent counts, one verdict. The numbers shown are from a real DBLP-scale load. If any line disagreed by even one, the correct response is to stop and investigate — not to shrug.

7.3 The manifest: your database’s birth certificate

When reconciliation passes, write a manifest and store it with the database. It records:

  • source filename, snapshot version/DOI and its checksum;
  • the code version that built it (for example a git commit hash);
  • the settings used (batch size, profile, options);
  • start and finish time, runtime, throughput;
  • final record counts and database size.

This is what makes your result reproducible: somebody else — or future you — can rebuild the identical database and get identical numbers. It also satisfies the “Reusable” pillar of the widely-adopted FAIR data principles (Findable, Accessible, Interoperable, Reusable), which asks for clear provenance, quality and licensing information.

Definition of done

“The importer finished” is not the definition of done. Done means: counts reconcile (total and per type), keys are unique, foreign-key and file-integrity checks pass, spot-checks match the source, and the manifest is written. Anything less is a database you hope is right.

Simple example After moving house you do not say “the truck is empty, done.” You count the boxes against your inventory (count reconciliation), check nothing rattles (integrity check), open a few boxes to confirm the contents (sampling), and file the moving paperwork (manifest). Then you are done.

STEP 8Make it searchable

Industry name: indexing · full-text search · information retrieval

You now have a correct, verified database. It is still not fast to search. This step is where a pile of data becomes a search engine — and it is the part most tutorials never reach.

8.1 Why a loaded database is still slow

Run SELECT * FROM publications WHERE title LIKE '%neural%' and the database must perform a full table scan: read every one of the millions of rows, examine each title, and test for the substring. There is no shortcut, because a search pattern beginning with % cannot use an index. It is slow, it cannot rank results, and it cannot match word variants.

8.2 B-tree indexes: jump instead of crawl

An index is the back-of-the-book index for your table: a pre-sorted structure that lets the engine jump straight to matching rows. SQL indexes are B-trees — balanced trees that discard a large chunk of candidates at every hop, so finding one value among a million rows takes on the order of twenty comparisons rather than a million.

They are perfect for exact and range lookups: WHERE year = 2019, WHERE doi = '10.1145/...', WHERE year BETWEEN 2010 AND 2015. The costs are real but manageable: writes get slower (every insert must update every index — which is exactly why Step 6 said load first, index later) and each index takes disk space. So you index only the columns you actually search or join on.

FIG. 9 SCAN vs SEEK – WHY AN INDEX IS 1000x FASTER WITHOUT INDEX: read every row 8.5 million reads for one answer WITH A B-TREE INDEX: about 20 hops each hop throws away half the candidates 1,000,000 rows → about 20 comparisons THE PRICE OF AN INDEX – slower writes (build AFTER loading) – extra disk space – only helps the indexed column
Figure 9 — The back-of-book index. Without one you read all 900 pages; with one you look up the term and jump to the page. The database does exactly the same thing, in about twenty hops.

8.3 Try the identifier first — the fastest path of all

Before any text search, ask: do I have an identifier? A DOI, arXiv ID, ISBN or the source’s own key uniquely identifies a record. Looking one up against a B-tree index is instant and unambiguous — no guessing, no ranking, no false matches. Production matching systems always try identifiers first and fall back to text search only when there is no identifier to use.

Simple example Finding a book: if you have the ISBN barcode, you scan it — done, one exact hit. Only when you have no barcode do you start describing the cover to the librarian.

8.4 Full-text search with SQLite FTS5

For real text search (“papers about attention transformers”), SQLite ships a dedicated engine: FTS5, a virtual-table module that has been part of SQLite since version 3.9.0 (2015). Instead of scanning, it builds an inverted index — a map from each word to the list of rows containing it — and ranks results with BM25 out of the box.

FIG. 10 WHAT AN INVERTED INDEX ACTUALLY IS YOUR ROWS row 1: “Attention Is All You Need” row 2: “Neural Machine Translation” row 3: “Attention in Neural Networks” invert THE INDEX (word → rows) attention → row 1, row 3 neural → row 2, row 3 translation → row 2 SEARCH “attention neural” look up “attention” → {1, 3} look up “neural” → {2, 3} both words appear in → row 3 (no scanning of any row at all) This is the same trick a book index uses – and the same trick web search engines use.
Figure 10 — Turning the data inside out. Instead of rows containing words, you store words containing rows. Finding documents becomes a lookup rather than a search — which is why it stays fast at millions of rows.

Creating the search table is one statement:

CREATE VIRTUAL TABLE pub_fts USING fts5(title, venue);

Do not store your text twice. By default FTS5 keeps its own copy of the text, which for millions of rows doubles storage. The fix is an external content table: FTS5 stores only the index and reads the actual values from your existing table.

CREATE VIRTUAL TABLE pub_fts USING fts5(
    title, venue,
    content='publications',
    content_rowid='id'
);

Searching uses the MATCH operator, and you can mix it freely with ordinary SQL conditions:

SELECT title FROM pub_fts
WHERE pub_fts MATCH 'neural attention'
ORDER BY rank
LIMIT 10;

The query language supports phrases ("deep learning"), boolean AND/OR/NOT, prefix search (transform* matches transformer, transformation), column filters (title:neural) and proximity search.

FTS5 tokenizers — how text is chopped into searchable words
TokenizerWhat it doesUse it when
unicode61 (default)Sensible Unicode word splitting, case-insensitiveAlmost always — the safe default
asciiTreats only ASCII characters as specialPlain English-only data
porterAdds stemming: running / runs / run fold togetherYou want word variants to match
trigramIndexes 3-character sequences, enabling substring and typo-tolerant matchingYou need to match fragments inside words

8.5 Populating and maintaining the index

Build the index after the bulk load. With an external-content table you fill it with one special command, then compact it:

INSERT INTO pub_fts(pub_fts) VALUES('rebuild');
INSERT INTO pub_fts(pub_fts) VALUES('optimize');

rebuild deletes the whole index and regenerates it from the content table (it is also the repair tool if the two ever drift apart). optimize merges the index into a single compact structure — the smallest on disk and the fastest to query. Both take a while on millions of rows; both are one-time costs.

8.6 BM25 ranking, explained simply

Finding matches is only half the job; ordering them is the other half. BM25 is the classic ranking function used by full-text search engines — the same family used by Elasticsearch and Lucene — and FTS5 has it built in. It scores each result using three intuitions:

FIG. 11 THE THREE IDEAS INSIDE BM25 1. TERM FREQUENCY a document mentioning your word more is more about it but with diminishing returns: the 10th mention adds less than the 1st 2. RARE WORDS COUNT MORE “the” is everywhere and tells you nothing “quantum” is rare and highly informative (this is the IDF part) 3. LENGTH NORMALISATION long documents contain more words simply by being long so they are discounted, and cannot dominate unfairly QUIRK TO KNOW: SQLite’s BM25 scores are NEGATIVE, and smaller means better. That is deliberate – so a plain “ORDER BY rank” (ascending) puts the best match on top.
Figure 11 — Why the right paper comes first. Term frequency, rare-word weighting and length normalisation together explain almost every ranking decision you will see — and BM25 remains the strong baseline that fancier methods must beat.
Simple example Search “neural attention”. A short paper titled “Neural Attention Networks” outranks a 300-page book that mentions “neural” once: both rare-ish words appear prominently in a short text (frequency + rarity), while the long book is discounted for its length.

8.7 Normalised text for search — kept separate from the originals

To make search robust you index a normalised copy of the text: lowercased, punctuation and markup stripped, disambiguation suffixes removed (bibliographies often append numbers to distinguish authors with identical names — useful for identity, noise for search). Following the Step 3 rule, these normalised strings live in separate columns; the authoritative original is never overwritten. You search the normalised copy and display the original.

8.8 What comes after BM25

The next layers, one line each
TechniqueWhat it adds
Fuzzy / trigram matchingTolerates typos and partial words (“atention” still finds “attention”)
Embeddings / semantic searchMatches meaning, so “car” can find “automobile” with no shared words
Hybrid retrieval + rank fusion (RRF)Runs keyword and semantic search together and merges the two ranked lists
RerankingPasses the top 50–100 candidates to a slower, smarter model for a final precise order
Calibrated confidenceTurns raw scores into an honest “92% sure” the user can trust
Evaluation metricsTop-1 / Top-5 accuracy, MRR and Recall@k — how you prove one method beats another
FIG. 12 THE SEARCH LAYER, BUILT IN ORDER THE TABLE (correct, verified, but a full scan is slow) B-TREE INDEXES – exact and range lookups: identifiers, year, keys FTS5 INVERTED INDEX + BM25 – ranked keyword search over millions of rows ADVANCED – fuzzy, embeddings, hybrid fusion, reranking, confidence built in step 6 minutes the big win optional, later
Figure 12 — Build upward, in this order. Each layer only makes sense once the one below it exists. Most projects get enormous value from the first three and never need the fourth.

What is all this actually called?

Terminology — and three things this is NOT

Beginners get lost because the field has many overlapping words, often used loosely. Here is the precise map.

The umbrella term is data engineering: building the systems that move and prepare data for use. The specific pattern in this article is an ETL pipelineExtract (parse the XML), Transform (map it to tables, derive normalised copies), Load (insert into the database). Its cousin ELT loads raw data first and transforms it inside a big warehouse afterwards, which is common in cloud data platforms. The whole automated flow is a data pipeline; the one-time mass move is a data migration or bulk load.

Every step in this article and its professional name
Our stepWhat it is called in industry
1. Get and verify the sourcesource verification, integrity checking, checksum validation
2. Look before you leapdata profiling, data auditing
3. Design the destinationschema design, data modelling
4. Build the readerparsing, extraction (streaming / SAX / iterparse)
5. Test small firststaged validation, sample loads, dry run
6. Run the full loadloading, ingestion, bulk load
7. Prove it is correctreconciliation, data validation, data quality checks
8a. Speed up lookupsindexing (B-tree)
8b. Search the textfull-text search, information retrieval

Three distinctions worth memorising

FIG. 13 WHERE EACH WORD BELONGS ON THE JOURNEY RAW SOURCE dump or scrape ETL / INGESTION everything in this article DATABASE storage search / retrieval DATA MINING machine learning NOT data mining: mining discovers unknown patterns in data that is ALREADY stored NOT data cleaning: cleaning CHANGES values; we preserve originals and derive copies
Figure 13 — The terminology map. Everything in this article sits in the orange box. Mining and machine learning stand on top of the database that the orange box builds.
What this is, and what it is not
TermWhat it meansIs that what we did?
Data engineering / ETLBuilding the pipeline that moves and prepares dataYES — this is the correct name
Data miningDiscovering unknown patterns and relationships in data that is already storedNO — that comes after, on top of the database
Data cleaning / cleansingCorrecting errors: removing duplicates, fixing values, filling gaps — it changes the dataNO — we deliberately preserve every original value
Data wrangling / mungingThe broad reshaping of raw data into a usable form (cleaning is a part of it)Partly — our transform step is a mild form of it
Data curationOrganising, describing, preserving and adding value to data over its lifetimeYES — our provenance and manifest discipline is exactly this
Why we do not “clean” a public dataset in place

If you correct a value inside your copy of a published dataset, your database no longer matches the source, and nobody — including you — can tell later which values were original and which you altered. Provenance dies, reproducibility dies. The professional alternative is what this article does throughout: preserve the original, add a derived column, and document the derivation. Cleaning is legitimate work — it just belongs in a clearly-labelled derived layer, never in place.


The reusable checklist for your own data

Lift this for any dataset: open government data, a Wikipedia export, a product catalogue, legacy records

Before you load

  • Acquire and verify. Download the data and its checksum; recompute and compare; test the archive; record source URL, version/DOI, date, size, checksum — and check the licence.
  • Profile before designing. Count records and types; measure field coverage; look for duplicates and missing keys; read real samples with your own eyes.
  • Model the target. Design tables, keys, relationships. Store ordering as data. Keep original and derived values separate. Add provenance columns.

While you load

  • Stream, never slurp. Parse incrementally and release each record after use.
  • Rehearse on samples. 1,000 then 100,000 records; validate counts, mapping, ordering; measure speed and size to predict the full run.
  • Load in batches with checkpoints so an interruption resumes instead of restarting.
  • Do not build search indexes yet.

After you load

  • Reconcile. Source vs target counts, total and per type; key uniqueness; foreign-key check; integrity check; sample spot-checks.
  • Write the manifest: source + checksum, code version, settings, runtime, counts, size.
  • Then build search. B-tree indexes on what you query and join; identifier lookup first; FTS5 with an external content table; rebuild then optimize; order by rank.

Always

  • Right-size the effort to the stakes. A throwaway CSV needs almost none of this. A dataset that a publication, a product or a decision depends on needs most of it. Knowing which situation you are in is the real skill.

What comes next

Once a searchable database exists, the natural continuations are short to describe and long to perfect: evaluate search quality against a small benchmark of known question-answer pairs (Top-1, Top-5, MRR); add better matchers (fuzzy, semantic, hybrid, reranking); put an interface on top — a search box, an API; and decide how to stay fresh when the source publishes a new release: a full rebuild from the new snapshot (simplest and safest while it is cheap) or incremental updates that apply only the changes (necessary once rebuilding becomes too slow — and remember that increments must handle deletions, not only additions).

Closing thought

“Just load the file into a database” hides an entire discipline. What actually happens is a small logistics campaign against three adversaries — time, memory and trust — fought with checksums, streaming parsers, batched transactions, checkpoints, reconciliation and indexes. None of the eight steps is difficult on its own. What separates an experienced engineer from a frustrated beginner is doing them in the right order, proving each one before moving on, and knowing which of them a given job actually needs.

Do that, and a silent 5 GB file becomes something genuinely valuable: a database that answers in milliseconds, tells you where every value came from, and can be rebuilt by anyone, exactly, from the source.

Further reading

SQLite documentation — the FTS5 extension (virtual tables, bm25(), rank, tokenizers, external-content tables, rebuild and optimize), query planning and indexing, and the PRAGMA reference for integrity_check and foreign_key_check (sqlite.org) · DBLP’s own FAQ and blog on parsing dblp.xml, the DTD, monthly DOI snapshots and the CC0 licence (dblp.org, blog.dblp.org) · Okapi BM25 and information-retrieval evaluation metrics — Introduction to Information Retrieval by Manning, Raghavan and Schütze · the FAIR Guiding Principles for scientific data management (Wilkinson et al., Scientific Data, 2016) · data-migration best-practice guides on profiling and reconciliation as standard pre- and post-migration steps · Cormack, Clarke and Büttcher, “Reciprocal Rank Fusion” (SIGIR 2009) for hybrid-search fusion.

A note on the numbers: dataset sizes and record counts grow every month, so treat all figures here as a snapshot rather than a constant — which is precisely why Step 1 insists you record the exact version you used. Figures in this article are original illustrations.

INGOAMPT · ingoampt.com — articles that make engineering understandable for everyone.

Leave a reply

Your email address will not be published. Required fields are marked *