|
| 1 | +title: Making search faster for all projects |
| 2 | +date: February 5, 2026 |
| 3 | +description: We made several improvements to make search faster for all projects on Read the Docs. |
| 4 | +category: Engineering |
| 5 | +tags: performance, search |
| 6 | +authors: Santos Gallegos |
| 7 | +status: published |
| 8 | +image: /images/faster-search.jpg |
| 9 | +image_credit: Photo by <a href="https://unsplash.com/@julianhochgesang?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Julian Hochgesang</a> on <a href="https://unsplash.com/photos/time-lapse-photography-of-vehicles-3-y9vq8uoxk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a> |
| 10 | + |
| 11 | +Search is a fundamental feature for documentation projects, |
| 12 | +helping users to find the information they need quickly. |
| 13 | +That's why Read the Docs includes a powerful [search engine](https://docs.readthedocs.com/platform/stable/server-side-search/index.html) for all hosted projects powered by Elasticsearch. |
| 14 | + |
| 15 | +Since its launch, we have continuously worked to enhance the search experience by adding new features, |
| 16 | +but performance has remained a challenge due to the growing number of projects and users on the platform. |
| 17 | +Over the last few months, we have focused on improving search performance for all projects, |
| 18 | +achieving significant gains in speed and responsiveness. |
| 19 | + |
| 20 | +We reduced the average response time from **210ms to 97ms** on Read the Docs Community, |
| 21 | +and from **570ms to 205ms** on Read the Docs Business (excluding network latency). |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | +_Average response time for search requests on Read the Docs Community from the last 3 months._ |
| 26 | + |
| 27 | + |
| 28 | + |
| 29 | +_Average response time for search requests on Read the Docs Business from the last 3 months._ |
| 30 | + |
| 31 | +## What we did |
| 32 | + |
| 33 | +When looking for ways to improve search performance, |
| 34 | +we identified three main areas of focus: |
| 35 | + |
| 36 | +1. Reducing the volume of data being searched. |
| 37 | +2. Optimizing Elasticsearch configuration and queries. |
| 38 | +3. Optimizing our application logic. |
| 39 | + |
| 40 | +## Reducing the volume of data |
| 41 | + |
| 42 | +The more data we search through, the longer it takes to return results. |
| 43 | +It also impacts our infrastructure costs; Elastic Cloud requires upgrading the instance type (adding CPU and RAM) to increase storage. |
| 44 | +We were approaching 85% of our current plan's storage limit. |
| 45 | + |
| 46 | +We started by removing projects marked as spam from our index, |
| 47 | +followed by inactive projects (those with no builds or searches in the last 90 days). |
| 48 | + |
| 49 | +While this didn't yield a massive performance boost immediately, |
| 50 | +it kept us within our storage limits without an emergency plan upgrade |
| 51 | +and prepared the index for our next step: increasing the number of shards. |
| 52 | + |
| 53 | +## Optimizing Elasticsearch queries |
| 54 | + |
| 55 | +We analyzed our queries for inefficiencies and found several opportunities for improvement: |
| 56 | + |
| 57 | +- **Result limits:** We were returning 50 results by default, |
| 58 | + but most users rarely look past the first few. |
| 59 | + We reduced the default count to 15. |
| 60 | +- **Fuzzy search:** Some search terms containing `~` (like shell prompts or UNIX paths) |
| 61 | + were triggering expensive [fuzzy searches](https://docs.readthedocs.com/platform/stable/server-side-search/syntax.html#special-queries) unintentionally. |
| 62 | + We changed the maximum number of expansions from 50 to 10 and increased the prefix length from 0 to 1. |
| 63 | + See the [Elasticsearch documentation](https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-simple-query-string-query) for more information about these parameters. |
| 64 | + |
| 65 | +## Optimizing Elasticsearch configuration |
| 66 | + |
| 67 | +As we exhausted query-level optimizations, |
| 68 | +we found a couple of alerts from Elastic Cloud's [AutoOps](https://www.elastic.co/platform/autoops) regarding shard size. |
| 69 | + |
| 70 | +We had a single shard for the entire index. |
| 71 | +As the index grew to 630GB, performance degraded. |
| 72 | +Elasticsearch thrives on parallelizing requests across multiple shards; |
| 73 | +the recommended size is around 30GB per shard. |
| 74 | +When Elasticsearch was first integrated into Read the Docs, our index was much smaller, |
| 75 | +so a single shard made sense at the time. |
| 76 | + |
| 77 | +Because the number of shards cannot be changed on an existing index, we performed a full re-index. |
| 78 | +To ensure minimal downtime during the process, we followed these steps: |
| 79 | + |
| 80 | +1. **Cleanup:** Deleted unnecessary data to minimize the transfer. |
| 81 | +2. **Scaling:** Temporarily upgraded instances to handle the storage of both indexes and speed up the migration. |
| 82 | +3. **Re-indexing:** Created a new index with 20 shards (based on the ~570GB post-cleanup size) |
| 83 | + and used the [reindex API](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-reindex) to migrate data. |
| 84 | +4. **Cutover:** Updated our index alias and deleted the old index. |
| 85 | +5. **Catch-up:** Indexed any new or updated documents added during re-indexing. |
| 86 | + |
| 87 | +The process took a couple of hours, and search remained available during the whole time. |
| 88 | +This improved Elasticsearch query averages from **90ms to 60ms** on Community and **80ms to 60ms** on Business. |
| 89 | +It also significantly sped up document indexing and deletions. |
| 90 | + |
| 91 | +**Note:** For Read the Docs Business, our index was smaller (around 100GB), |
| 92 | +but we still needed to increase the number of shards to improve performance, |
| 93 | +so we created a new index with 4 shards. |
| 94 | + |
| 95 | +## Optimizing application logic |
| 96 | + |
| 97 | +We discovered that significant time was spent querying the database to serialize results (fetching project details, resolving URLs), |
| 98 | +and on Read the Docs Business performing permission checks. |
| 99 | + |
| 100 | +This got worse as we enabled searching for subprojects by default, |
| 101 | +as searching on projects with many subprojects triggered multiple database queries per search. |
| 102 | + |
| 103 | +We resolved several N+1 issues using `select_related` and `prefetch_related`, |
| 104 | +implemented caching where possible, re-used previously fetched data/instances, and did operations in bulk. |
| 105 | + |
| 106 | +This saved approximately **12ms** on Community and **105ms** on Business. |
| 107 | + |
| 108 | +## Optimizing the Elasticsearch client |
| 109 | + |
| 110 | +Years ago, we disabled connection pooling in the Python client due to stability issues, |
| 111 | +this resulted in creating a new connection for each request. |
| 112 | +We revisited this, and enabled connection pooling without issues. |
| 113 | + |
| 114 | +We also used the recommended way to connect to Elastic Cloud using the `cloud_id` parameter, |
| 115 | +this enables [HTTP compression and other optimizations](https://www.elastic.co/guide/en/elasticsearch/client/python-api/8.19/connecting.html#connect-ec). |
| 116 | + |
| 117 | +Elasticsearch query times dropped from **60ms to 25ms** on Community and **60ms to 40ms** on Business. |
| 118 | + |
| 119 | +**Note:** The difference in improvement between Community and Business is likely due to the difference in the instance types used in each platform. |
| 120 | +On Community, we use more powerful instances due to the higher storage needs. |
| 121 | + |
| 122 | +## Conclusions |
| 123 | + |
| 124 | +After implementing these changes, we saw a significant improvement in search performance across all projects hosted on Read the Docs. |
| 125 | +Search as you type is now much more responsive, especially on Community. We still have work to do on Business, |
| 126 | +but we are happy with the progress we have made so far. |
| 127 | + |
| 128 | +You can see the improvements in the graphs below. |
| 129 | +There are three main drops in the graphs that correspond to the main changes we made: |
| 130 | + |
| 131 | +- The first drop corresponds to the optimizations made to our Elasticsearch queries. |
| 132 | +- The second drop corresponds to the increase in the number of shards. |
| 133 | +- The third drop corresponds to the change in our Elasticsearch client to use connection pooling. |
| 134 | + |
| 135 | +Of course, there are other optimizations that contributed to the overall improvement that were included in the same time frame, |
| 136 | +but these were the most significant ones regarding Elasticsearch performance. |
| 137 | + |
| 138 | + |
| 139 | + |
| 140 | +_Average response time for search requests on Read the Docs Community from the last 3 months._ |
| 141 | + |
| 142 | + |
| 143 | + |
| 144 | +_Average Elasticsearch query time on Read the Docs Community from the last 3 months._ |
| 145 | + |
| 146 | + |
| 147 | + |
| 148 | +_Average response time for search requests on Read the Docs Business from the last 3 months._ |
| 149 | + |
| 150 | + |
| 151 | + |
| 152 | +_Average Elasticsearch query time on Read the Docs Business from the last 3 months._ |
| 153 | + |
| 154 | +Some reasons why Read the Docs Business is still slower than Read the Docs Community: |
| 155 | + |
| 156 | +- **More complex permission checks.** |
| 157 | + Since we host private projects, we need to ensure that users only see results from versions they have access to. |
| 158 | + This requires checking each version's permissions before including it in the search results. |
| 159 | +- **No caching of search results.** |
| 160 | + Due to the nature of private projects, we can't cache search results, |
| 161 | + as they may change based on user permissions. |
| 162 | + |
| 163 | +## Recommendations |
| 164 | + |
| 165 | +Here are some recommendations for other teams looking to improve their Elasticsearch search performance: |
| 166 | + |
| 167 | +- Monitor shard size and number of documents per shard. |
| 168 | + As your index grows, it's important to ensure that shards don't become too large. |
| 169 | +- If you are using Elastic Cloud, check [AutoOps](https://www.elastic.co/platform/autoops) alerts regularly. |
| 170 | + You can setup email/slack notifications for new alerts. |
| 171 | +- Delete unnecessary data from your index. |
| 172 | + This will help reduce storage costs and further reduce your number of shards needed. |
| 173 | +- Make sure your Elasticsearch client is using connection pooling and other optimizations. |
| 174 | +- Implement restrictions on the number of results returned by default, and the use of expensive search features like fuzzy search. |
| 175 | +- Check for optimizations in your application logic, if it interacts with the results from Elasticsearch. |
| 176 | + |
| 177 | +## Next steps |
| 178 | + |
| 179 | +While we are happy with the improvements we have made so far, there is still room for further optimizations: |
| 180 | + |
| 181 | +- Revisit the number of shards. |
| 182 | + We decided on the number of shards based on the size of the index before re-indexing, |
| 183 | + but the resulting index was smaller than we expected (there was maybe some fragmentation on the old index). |
| 184 | + Reducing the number of shards could improve performance, but it's not clear by how much. |
| 185 | + As this requires re-indexing again, we won't do this unless we need to re-index for other reasons. |
| 186 | +- Further optimize database queries. |
| 187 | + This is an ongoing effort, and we will continue to look for ways to reduce the number of queries and improve their efficiency, |
| 188 | + especially on Read the Docs Business, where we run these queries everywhere a resource is accessed. |
| 189 | + |
| 190 | +## Acknowledgements |
| 191 | + |
| 192 | +[Elastic](https://www.elastic.co/) sponsors Read the Docs Community with a free [Elastic Cloud](https://www.elastic.co/cloud) plan. |
| 193 | +We are grateful for their support in helping us provide search functionality to all our users, |
| 194 | +their support is key to keeping search fast and accessible for the whole community. |
| 195 | + |
| 196 | +## Changes |
| 197 | + |
| 198 | +If you are interested in the specific changes we made, |
| 199 | +here is the list of relevant pull requests. |
| 200 | +Note that some changes were made in private repositories, |
| 201 | +so they are not included here. |
| 202 | + |
| 203 | +- <https://github.com/readthedocs/readthedocs.org/pull/12695> |
| 204 | +- <https://github.com/readthedocs/readthedocs.org/pull/12666> |
| 205 | +- <https://github.com/readthedocs/readthedocs.org/pull/12660> |
| 206 | +- <https://github.com/readthedocs/readthedocs.org/pull/12635> |
| 207 | +- <https://github.com/readthedocs/readthedocs.org/pull/12588> |
| 208 | +- <https://github.com/readthedocs/readthedocs.org/pull/12569> |
| 209 | +- <https://github.com/readthedocs/readthedocs.org/pull/12560> |
| 210 | +- <https://github.com/readthedocs/readthedocs.org/pull/12549> |
0 commit comments