The index of an OpenAlex snapshot¶
A downloaded OpenAlex snapshot can be indexed once per release
(cartolex collect snapshot-index SNAPSHOT, Collecting people and texts), so that a
collection reads only the parts of it that may hold what it asks for. The index
lives beside the data, in the snapshot’s folder; it is not part of a project.
What indexing changes¶
Each part OpenAlex ships (data/jsonl/<entity>/updated_date=…/part_….gz) is one
gzip stream. Indexing cuts every part of the works and the authors into
gzip members of about a megabyte of whole lines each (compressed at level 6,
the size of the original): the file is still a gzip of the same JSON lines, in
the same order, and any tool reads it as before. Its size, no longer the one in
OpenAlex’s manifests, is recorded in the index. The institutions are not cut:
they are small and read whole.
The files¶
<snapshot>/cartolex-index/
index.json the release, the parts, the keys (below)
works/blocks.npy each member's offset and length (structured: offset u8, length u4)
works/first.npy the number of each part's first member (u8, one more than parts)
works/<key>/NN.keys.npy bucket NN of a key (key modulo 64): the keys, sorted (u8)
works/<key>/NN.refs.npy beside each key, a member that holds it (u4: part << 20 | member)
works/<key>/NN.fence.npy every 4096th key of the bucket (u8), kept in memory to find a key
authors/… the same, for the authors
building.json, journal.jsonl while a build runs or after it stopped (gone once complete)
spill/ the postings found so far, while building
The keys of the works are id (the record’s own id), author (every author id
in its line), institution (every institution id in its line, lineages
included) and doi (every DOI in its line, as the first 8 bytes of its BLAKE2b
hash in lower case); the authors have id. Numbers are the digits of an id
(A5012345678 → 5012345678). Parts are numbered in the order of their
updated_date=…/part_….gz names, the members of a part in file order.
index.json:
Field |
Meaning |
|---|---|
|
|
|
the snapshot’s release date (from its manifest) |
|
when the build ended (UTC) |
|
the settings of the build |
|
each part: |
|
the members of the entity, their compressed bytes |
|
|
|
|
Reading¶
A query by ids, authors, institutions or DOIs looks its keys up in their buckets (a bucket asked many keys is read whole; asked a few, each key is found through the bucket’s fence, in a slice of 4096 keys, so a few reads where they lie; an index built without fences makes them at its first lookup), gathers the members that hold one, and reads those members of those parts only (members close together in one read); each line is then tested exactly as a reading of the whole part tests it, so the records found are the same. A search by name, by ROR id or of everything reads whole parts. An index of another release, an incomplete one, or one whose parts changed size since (a part downloaded again) is not used: the queries read whole parts.
Building, stopping, resuming¶
A build cuts the parts in worker processes. Each new copy is written beside its
part (….gz.cutting), synced, then put in its place. Every 16 parts, everything
written is synced and the journal marks the point: a build stopped at any moment
goes on from the last such point (parts cut after it are cut again; cutting a
part already cut gives the same members). When every part is cut, each bucket of
postings is sorted, index.json is written and the journal is removed.