Our Primary Key Is a Molecule. A textbook antibody drawn as two Fab arms and a stem, its heavy chain domains in green and its light chain domains in amber, resolving by an arrow into a single long integer, captioned the id is the molecule
Article

Our primary key is a molecule

Deriving a primary key from protein sequence, and what it cost when the key described the wrong molecule

Key takeaways

  • Identity is derived, not assigned. A molecule's id is a hash of its chain sequences, so re-ingesting the same molecule lands on the row that already exists.
  • Nobody has to share an identifier. A structure prediction, a simulation, a computed score and a wet lab assay all start from the same chains, so four teams who never speak put their results on one row without registering anything.
  • Only facts the molecule determines belong in the key. Names, projects, submitters, even the homodimer flag, all sit beside it, where a wrong value cannot split or merge a molecule.
  • Canonicalize arm pairs, not chains. Sorting the heavy chains and the light chains independently gives two chemically different bispecifics the same id.
  • A derived key is only as specific as its source. Keying on the engineered constant region alone collapsed roughly sixty identifiers onto about a hundred and forty distinct chains. Widening the key to the whole chain took collisions to zero.
  • Content addressing is not free. Ids are signed and negative about half the time, they round silently when parsed as JavaScript numbers, the formula has to be implemented identically in two languages, and shared rows cannot be swept by an undo.

Nobody should have to share an id

Four different people will touch the same molecule, and most of them will never meet.

A bioinformatician predicts its structure. A structural biologist runs a molecular dynamics simulation of it. Someone else computes binding scores with their own scripts. Months later a contract laboratory returns an assay result that cost real money and three weeks. Four activities, four toolchains, four file formats, four vocabularies that grew up independently.

The coordination tax

The usual way to connect them is to make everyone agree on a name. Mint an identifier, publish it, require it to be quoted on everything. In practice the bioinformatician cannot record a prediction until somebody has registered the molecule, the simulation gets filed under whatever string was pasted into a job name, and the lab writes down whatever the purchase order called it. The identifier becomes paperwork that has to travel between people with no reason to talk to each other, and it takes one person skipping it for the connection to be lost. Lost silently, which is the part that matters.

What actually arrives

It fails on the way in as well. A contract lab finishes a binding assay and sends back a spreadsheet: a column of protein sequence, a column of mutation tokens like L234A/L235A, a column of numbers that took three weeks and a lot of money to produce, and a name in the first column that somebody typed in a hurry. No identifier. That is not sloppiness, because the lab was paid to measure something, not to register it. Next quarter the same molecule comes back from a different lab under a different name, because a different person filled in that column.

Give it an id on arrival and the database's opinion of what counts as one molecule becomes a function of the order things arrived in and of whether somebody remembered. One molecule, three rows, and no join that can put them back together.

The same molecule arriving in three sheets under three different names with three different assay values and no identifier, where only the heavy chain column is identical, and the two outcomes: three assigned ids and three rows, or one derived id and one row

The one thing they all already hold

But all four producers are already holding the same thing. Every one of them is working from the chain sequences. The bioinformatician folded them, the simulation moved them, the scoring script read them, the lab expressed them. The sequences are the only artifact that travels through all four activities unchanged, because the sequences are the molecule.

So let the sequences be the identifier. Then nobody has to share anything. Each producer computes the id from the sequences they were handed and never asks anyone for a number, and four people who have never exchanged a message put their results on one row, because all four computed the same function over the same molecule without knowing they were doing it.

On the left, four producers all routed through an identifier registry, with one of them never registering and its result orphaned. On the right, the same four producers each computing the same id from the chain sequences and landing on one row

That is the promise. The rest of this article is what it took to get it, including the part where we derived the key from the wrong thing.

The molecule is already unique

An antibody, for the purposes of a database, is up to two arms. Each arm is one heavy chain paired with one light chain. An ordinary monoclonal has two identical arms. A bispecific has two different ones, which is what lets it bind two targets at once.

So the key is those four slots, written out:

antibody:{heavy1}:{heavy2}:{light1}:{light2}

Take the SHA-256 of that string, read the first eight bytes as a signed 64-bit integer, and that is the molecule's id. A function of the content and of nothing else. Not the name, not the project, not the date, not who submitted it.

The payoff is idempotency, and it is larger than it first looks. Re-ingest the same sheet and every row hashes to the id it already had, so the storage engine collapses the duplicate instead of writing a second molecule. No lookup round trip before the write, no sequence to allocate, no upsert race between two pipelines running at once.

The molecule, in other words, was already unique before the database had an opinion. All the schema does is notice.

A prediction and an experiment share one row

So the promise from the opening holds. The four producers never coordinate, and they do not even attach to a molecule the same way.

A table of the four producers, what each declares about what it made, and how each attaches, from the assay that carries a registered identifier down to the folding job that declares nothing, plus the rules that inputs are sorted but never deduplicated and that an ambiguous match is a recorded outcome

Four routes onto one row

The assay result arrives carrying the registered identifier, because that is the string that travelled out on the order form and came back on the results, so it resolves by lookup. The simulation declares what it simulated: its payload says which sequences played which role, so it attaches directly.

The structure prediction declares nothing at all. A folding job is handed a list of sequences and asked to fold them. Nothing in it says which chain is a heavy, which is a light, which is a receptor and which is an antigen, because the model does not need to know and whoever queued it did not say. So a structure gets an identity of its own, derived the same way: a hash over its input sequences, normalized and sorted.

Where resolution gets subtle

Two details in that sort carry weight. Sorting makes the identity insensitive to the order the chains were listed in, which is right, because a complex is a set rather than a sequence. But duplicates are deliberately not removed, because one copy of a chain is a monomer and two copies is a homodimer, and those fold differently.

Resolving a structure back to a molecule is then a cascade, and it has to be allowed to fail. Fold a sub-region instead of a whole chain and it will match many molecules rather than one, because antibody constant regions are conserved by construction and a short stretch of one is shared by hundreds of others. So ambiguous is a real recorded outcome rather than an error. Substring matching is fine behind a search box; it is not a linkage mechanism.

What this buys is one row. A structure predicted before the molecule was ever built, a simulation run on a cluster, a score from somebody's own script, and an assay result from a lab a year later all land on the same molecule, with none of the four filing anything or talking to each other. Which is the only arrangement in which you can ask the question everybody actually wants answered: was the prediction any good?

Content addressing is not free

Deriving identity from content has costs, and they are not the ones people expect. Not one of them is a problem with the hash.

Four costs shown side by side: a real negative id, an id whose last digits change silently when parsed as a JavaScript number, the same formula implemented twice with opposite byte order, and one shared chain row referenced by three molecules that undo cannot delete

A negative primary key is not a bug

This one is cosmetic but persistent. The integer is signed, so about half of all ids are negative. A negative primary key is not a bug here, and every tool, dashboard and junior engineer that assumes ids count upward from one is wrong about this system forever.

The rounding that raises no error

This one is worse, because its failure mode is silence. A 64-bit id does not fit in a JavaScript number, which is a double and runs out of integer precision at about nine quadrillion. Anything that parses one as a number rounds it. The value still looks like an id, still nineteen digits long, and the lookup that follows simply matches nothing without raising an error anywhere. The only defence is to treat these as strings at every boundary where they leave the database, forever, in every new endpoint.

One formula, two implementations

This one is structural. The id has to be computable in the application language and again in the query language, because the warehouse cannot call your code. One formula, two implementations, permanently in step. Ours differ by a byte order reversal, because one reads the digest big-endian and the other little-endian, and the day those two drift is the day measurements start pointing at molecules that do not exist.

Shared rows cannot be swept

This one surprises people, because from most angles it looks like a feature. Chains are content addressed, so they are shared: one polypeptide row is referenced by every molecule that contains it. That is what makes the storage compact and the annotations consistent, and it is also why an undo cannot sweep them, since deleting one submission's chains would strip regions off a chain that three other molecules still use. Undo removes what a given run owns and deliberately leaves the shared layer alone. Sharing and deletion pull against each other, and you have to pick.

Two collisions, neither in the hash

The one we caught

A key built from four slots has to answer an awkward question: what if the submitter listed the arms the other way round? The same molecule described in a different order must produce the same id, so the slots have to be canonicalized before hashing.

The obvious way to canonicalize is to sort. Sort the heavy chains, sort the light chains, concatenate the four. It looks canonical, and it reads as canonical in review. Consider a bispecific built from heavy chains A and B and light chains P and Q. Pair A with P and B with Q and you get one molecule; pair A with Q and B with P and you get a different one. Those two bind different things and behave differently in an animal. Sorting the heavies and the lights independently gives both the identical key, because both contain the same four chains. Two different drugs, one id, and a hash function working exactly as specified.

The fix is one word. Sort the arm pairs, not the chains. The pairing survives the sort, the two molecules keep their own ids, and the key is still insensitive to the order the submitter typed. Two smaller rules hide in the same few lines: a repeated arm collapses, so an ordinary monoclonal written out as two identical arms keys the same as one written out once, and the empty slot is padded after sorting rather than before, because an empty pair sorts ahead of any real one.

The one we shipped

This is an Fc engineering catalogue. The Fc is the constant region at the base of the antibody, the part that talks to the immune system rather than to the target, and engineering it is how you change a molecule's half life or its effector function without touching what it binds. So the catalogue is shaped accordingly: a handful of parent antibodies, each appearing in hundreds of constant-region variants. Across roughly eleven hundred chains there are about ten distinct variable regions, and one of them accounts for over five hundred chains.

A measurement sheet from that world names the Fc. Not the whole molecule, just the engineered region, because that is what the assay was ordered to characterise. The key was built from what the file contained, so the key was built from the Fc.

Take two constructs with different antigen-binding arms, the same light chain, and one shared engineered Fc. Keyed on the Fc, they land on one id. And sharing an Fc across parent antibodies is not an edge case a careful reviewer would have caught, it is the entire point of Fc engineering. The failure was not rare, it was the workload.

Across the real catalogue that came to roughly sixty identifiers covering about a hundred and forty distinct chains, between two and five chains apiece, every one of them a molecule wearing another molecule's identity. Assay values attached to the wrong construct. Whichever chain was written last won the row, so the stored sequence for an id depended on ingestion order, and every annotation anchored to that sequence by offset was silently wrong too.

Widening the key to the whole heavy chain takes the collisions to zero. We checked it the way you should check any re-keying, by recomputing ids for live rows and asking how often the new formula reproduces what is already stored. Hashing the full chain reproduced the stored id about ninety nine percent of the time. Hashing the Fc alone reproduced it about seventeen percent, and that seventeen percent was exactly the constructs that are only ever an Fc, where the two strings are the same string.

What it actually taught us

Which is the lesson, and it is not the one people expect. SHA-256 did not let us down. The collision was in the sheet. A derived key can only ever be as specific as the source is willing to be, and a source is only ever as specific as the question somebody paid it to answer.

So derive the key from the most complete thing you can honestly assert, then write down which thing you chose. That second half is the part teams skip. A record describing only a constant region is not a low quality record, it is a complete and true assertion about a real molecule, and it must never quietly merge into a longer chain that happens to contain it, because the longer chain is a different claim made by a different person on a different day.

Only the molecule gets a vote

The rule that falls out of all of this is short. Only facts the molecule itself determines belong in the key. Everything a person asserted about the molecule lives beside the key, never inside it.

On one side the key, holding the four chain slots and nothing else. On the other, the display name, project, iteration, submitter and laboratory, which are real but get no vote. Below, the homodimer field, deliberately left outside because the slots already answer it

The clearest worked example is the field recording whether a molecule is a homodimer or a heterodimer. It is deliberately not part of the id, because the four chain slots already encode it: if the two arms are the same, it is a homodimer, and the key says so without being asked. Leaving it out means a wrong value there is a cosmetic error in a display column. Putting it in would have meant a wrong value could split one molecule into two. The same logic excludes the display name, the project, the iteration, the submitter and the lab. All real, all worth storing, none of them entitled to a vote on what the molecule is.

None of this removes judgement from the system. It concentrates all of it into a single decision, made once, about what you are willing to call the thing. We got that decision wrong the first time, not because we misunderstood hashing, but because we let the shape of an invoice decide the shape of an identity.

Get it right and the database keeps its own books. Every writer agrees without coordinating, re-ingestion is free, and a molecule that arrives under four names is still one row. Get it wrong and the database will keep those books with exactly the same diligence, for the wrong molecule, and it will not tell you.