GSoC 2026: Modernize search storage format for the MusicBrainz database

Hello Everyone!

I’m Junaid (fettuccinae), an undergraduate Computer Science student at MGIT in India. This summer, I returned to MetaBrainz for my second GSoC project, where I worked on modernizing the MusicBrainz search under the mentorship of @kartikohri13 and @bitmap.

Project Overview:

MusicBrainz uses Apache Solr for search queries.
The previous implementation serialized all the response data into a single _store field.
The response writers reads _store, unmarshal the XML into a MusicBrainz XML Metadata Format (MMD) object and then serialize it again as XML or JSON for the response.
Few problems with this design are:
1. The indexer must construct a complete XML representation for every document, even though much of the same information is already available in normal Solr fields.
2. Most of the response data is stored in an opaque _store XML blob.

This project focused on moving the fields from one _store XML blob into their own flat fields (and JSON strings for nested fields).

The main goals of this project were:
1. Upgrade the Solr schema version from 1.5 to 1.7
2. Add fields (in configsets and indexer) to store all the data to be returned
3. Create response writers to return data from fields

The proposal for this project can be found here.

Result:

This project replaced the _store blob with stored fields and JSON string fields across all 16 search entities.

I reindexed both the old and new configurations using the sample MusicBrainz database, then force-merged the indexes before measuring them.
I wrote a python script to measure their sizes using Solr’s status API and compared them.

The total core size went from 3.8 GiB to 2.8 GiB, saving around 1 GiB or 26%.

CollectionOld coreNew coreDifference%
annotation15.8 MiB14.8 MiB5.8%
area7.9 MiB4.5 MiB42.9%
artist131.3 MiB81.2 MiB38.2%
cdstub94.0 MiB56.0 MiB40.5%
editor5.6 MiB1.9 MiB65.6%
event2.3 MiB1.4 MiB39.7%
instrument542.8 KiB405.2 KiB25.4%
label10.7 MiB6.3 MiB40.6%
place6.2 MiB3.7 MiB40.1%
recording3.0 GiB2.2 GiB26.1%
release37.2 MiB25.9 MiB30.5%
release-group42.1 MiB31.7 MiB24.7%
series1.2 MiB624.5 KiB49.6%
tag3.6 MiB1.6 MiB56.0%
url65.7 MiB44.4 MiB32.3%
work363.0 MiB307.1 MiB15.4%
Total3.8 GiB2.8 GiB26.1%

With BEST_COMPRESSION codec enabled, it reduces to 2 GiB with a size difference of 1.8 GiB or 47.9%. (The trade-off here is the search speed performance)

CollectionOldNewDifference%
annotation15.8 MiB12.1 MiB23.4%
area7.9 MiB4.0 MiB49.7%
artist131.3 MiB66.2 MiB49.6%
cdstub94.0 MiB49.8 MiB47.1%
editor5.6 MiB1.7 MiB68.9%
event2.3 MiB1.2 MiB47.5%
instrument542.8 KiB370.2 KiB31.8%
label10.7 MiB5.5 MiB48.5%
place6.2 MiB3.2 MiB48.7%
recording3.0 GiB1.5 GiB49.5%
release37.2 MiB19.6 MiB47.2%
release-group42.1 MiB20.2 MiB52.1%
series1.2 MiB570.6 KiB53.9%
tag3.6 MiB1.4 MiB61.2%
url65.7 MiB38.1 MiB42.0%
work363.0 MiB236.1 MiB34.9%
Total3.8 GiB2.0 GiB47.9%

Implementation details:

Upgrading the schema:

The Solr schema change from 1.5 to 1.7 enables docValues by default for primitive field types.
Most primitive fields are already stored directly with stored="true". Since docValues are mainly used for faceting, sorting, and function queries, I added docValues="false" to primitive field types to avoid storing the same value twice.
ref_count is used in boost functions in some cores , which needs docValues.
I created a new int_dv field type with docValues="true" and used it for ref_count and the other count fields used by boost functions.
Finally, I bumped the schema version from 1.5 to 1.7 in each core and verified that indexing and search continued to work.

Schema upgrade PR

Configset:

I changed fields that can be stored directly to stored="true" and added the remaining fields with indexed="false" and stored="true".
I then removed _store field from the schemas and request parameters.


I replaced ngram with edge-ngram, since we don’t require non-edge permutations of a query during search.

Configset PRs

Indexer:

In the SearchEntity class, I added a preserve_og flag. When converting a result dictionary into a Solr document, this flag preserves the original field order instead of converting the values to a set.
I also added an objconverter method, which allows a specific field’s structure to be converted before it is sent to Solr.
The preserve_og flag is enabled for simple parallel fields, such as tags. These fields do not need to be stored as JSON, instead, they use two parallel multi-valued fields. Preserving their order is necessary so that each tag_count remains associated with its corresponding tag_name.
The objconverter method is used to convert nested fields into JSON strings.


In each Search<Entity>, I moved fields from extrapaths to their respective fields and added the required objconverter methods for nested fields.
The existing wscompat converter logic for nested objects was mirrored to create these objconverter methods.
The remaining logic for creating MMD objects is now handled by the response writers.

Indexer PRs

Response Writer:

I added MBFlatXMLWriter, a field based response writer that reads the stored fields and, by mirroring the logic in wscompat's converters, create a MMD object and write it to the response.
I added entity-specific builders to construct MMD object from its stored fields. MBFlatXMLWriter uses these builders as helper classes.

I made MBJSONWriter which is currently used by MusicBrainz website search, to extend MBFlatXMLWriter instead of MBXMLWriter and verified that search works.

I also updated the tests to validate the new flat field logic instead of the old _store logic.

Response Writer PR

Future

The next steps for this project are to:
1. Benchmark it in production and measure disk, RAM, and CPU usage to see whether the optimizations hold up in a real environment or introduce any regressions.
2. Use Solr’s default response writers instead of custom response writers, and build the MMD objects in musicbrainz-server itself.

Conclusion

This project was more complex than my previous one. I spent a good amount of time understanding and deep-diving into Apache Lucene and Solr internals.

The implementation was a lot of fun because I was constantly experimenting with different approaches and reporting my findings. Each time I triggered a reindex, I stared at the terminal, hoping that indexing would not throw a wall of errors and that the core size would at least remain the same as the baseline.

Overall, the project gave me a much better understanding of search engines, some of the distributed-system techniques used by Solr, and the complexity of MusicBrainz itself.

I’m thankful to my mentors, @kartikohri13 and @bitmap, for their support and guidance. It has been a pleasure working with you all. Thanks for an incredible summer!

Leave a Reply

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