Queries in MySQL, Sphinx and many other database or search engines are typically single-threaded. That is when you issue a single query on your brand new r910 with 32 CPU cores and 16 disks, the maximum that is going to be used to process this query at any given point is 1 CPU core and 1 disk. In fact, only one or the other.

Seriously, if query is CPU intensive, it is only going to be using 3% of the available CPU capacity (for the same 32-core machine). If disk IO intensive – 6% of the available IO capacity (for the 16-disk RAID10 or RAID0 for that matter).

Let me put it another way. If your MySQL or Sphinx query takes 10s to run on a machine with a single CPU core and single disk, putting it on a machine with 32 such cores and 16 such disks will not make it any better.

But you knew this already. Question is – can you do something about it?

In case of Sphinx – indeed you can! And with very little effort. In fact, it does not require any changes to your application or database whatsoever, it is only a matter of small changes to the sphinx configuration.

The Plan

First of all, let me explain what we are trying to achieve here.

Sphinx has the ability to do distributed search out of the box – it was designed to scale out that way very early on. If your sphinx index does not fit to one machine, you would index different parts of it from different machines and then you would have an aggregator node that receives the request from application, issues search requests to all data nodes in parallel, merges results from all of the data nodes and returns results back to the application as if it was just one server serving the request in the first place.

Well, guess what – you can actually utilize this feature to your advantage even if your data can easily fit into one machine and all you want is your queries to be many times faster. Even more so, Sphinx now supports this out of the box, so you don’t need to pretend you are querying remote nodes.

Also, you get a bonus: once you configure server for distributed search, you can do indexing in parallel too!

Word of caution: while this technique will improve most types of search queries, there are some that aren’t going to benefit greatly from parallel execution. The reason is that while search is done on data nodes (even if local ones) and in parallel, merging of results is done by the aggregator and therefore it is single-threaded. Merging includes some CPU-intensive operations such as ranking, ordering or even COUNT with GROUP BY and if data nodes return large amounts of data to post-process, aggregator may well become a bottle-neck due to its single-threaded nature.

This is however easy to check – look at your Sphinx query log and look at the number of results matched per query – this should give you a clue.

Execution

Let us assume you have this one index on one server with the following basic configuration (many irrelevant details omitted):

And now we want it to utilize 3 CPU cores and/or disks on a local machine for this index of ours – idx1. Here’s how we would change the configuration:

And you’re done. Of course, you need to reindex all of the indexes, but you can now do it in parallel – just run a separate indexer for every index idx1p0 through idx1p2.

BTW, using div operator is not necessarily the best way to split data, you may want these to be ranges by using a helper table in MySQL to define those ranges, mixing it with sql_query_range or something else, depending on how your data looks like.

Finishing line

I always loved how Sphinx scales out easily with as many machines you need and have been running it this way for many years now, however I think I don’t utilize this feature to make queries even faster on a one-machine show nearly as often as I should. Well, it’s not like it is slow or anything, but queries are never too fast, are they? 🙂

19 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Stefan

Thank for this great post!

What do you think? Does it make sense to distribute my index?
I have an 4 Core CPU (Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz) with 32GB of RAM
but only with an RAID-1 system (containing 2 disks).

My sphinx index contains about 12 millions records with a disk size of about 2,7 GB.

Greetings,
Stefan

Brian

Is the above config example correct? I’m trying to replicate it on my end and I see some issues.

ERROR: index ‘idx1p0’: source ‘src0’ not found.

I don’t see src0 defined anywhere in the above config.

indx1p1 as configured is going to contain the entire dataset, not just 1/3rd of it as expected because it is using src1 which is defined without a % in the where query.

Same error for the 3rd index:
indexing index ‘idx1p2’…
ERROR: index ‘idx1p2’: source ‘src2’ not found.
ERROR: index ‘idx1p2’: no valid sources configured; skipping.

Brian

Thanks, the config works perfectly now.

I’m troubleshooting an issue where we get zero results back after sharding the index into 4 chunks. We’ve been running a single index for a few years now, and we have 16 24 core servers handling the search side of things. For whatever reason, when we shard the index, we no longer get any results back in production. Nothing changes software side, just the sphinx config, index, and a searchd restart.

Robert

How would you merge the indexes after this? I assume you would want to get 1 index. So, I can run the indexer for idx1p0, idx1p1, idx1p2 but how would I then get the idx1 index? If I try to run indexer for idx1, I get “skipping non-plain index ‘idx1’…

Thanks,
Rob

Robert

Thanks Aurimas, I misunderstood the nature of how the indexes would work. Thanks for posting this also, it was a great help to me. I really does speed up the searches.

Osama hasan

Nice Idea,
i tested it on my local machine ( core i3 + 3G ram + sphinx 2.1.1) on 300K rows with an index of 48 M.B and with using stopwords list , min_word_len = 1 , searching only one row and storing 5 rows as attr_string , with 6 languages in charset_table

i developed a script that search for 2000 random queries and return the mean time for each single query and the result was as follow:
1 thread >>maximum (0.019923) and minimum (0.019618) second per query
2 threads >>maximum (0.018417) and minimum (0.017915) second per query

Michael

Hi, coming at this question from an ‘Architect’ perspective so pardon any non-tech misunderstandings; will this be helpful in the case of using sphinx as auto-complete? We have a search form where each input pushes the characters to sphinx index to return the field into a selection container (Am= American/Armenia/Amsterdam). We do this since we don’t want to cache our tables client side but do want a responsive Auto-Suggest.

In load-testing we consistently find we max our user at about 20% of CPU (meaning the response times for the inputs are no longer acceptable after that cpu utilization). This is whether on VPS with 2 cores or dedicated with 64 cores.

Would this solution help in this case? In other words would making sure Sphinx was involving all cores help make sure that the cpu is not overloaded at 20%?

Michael

Hi Aurimas, thanks for the detailed response. More cores does give us a higher # of concurrent sessions before the response times goes haywire. Mainly I’ve noticed it is when the cpu/networks stop scaling in a smooth way and fluctuate the response times get killed. I agree I was looking for 75% or before things got overloaded. In all tests the memory usage was pretty flat and very low (on 32GB + boxes) at less then 10%.

I wonder though since there are about a dozen inputs, perhaps 4-5 ‘core’ inputs, if explicitly separating those into groups might help? For instance push Input A to Core 1, Input B to Core 2, etc. Otherwise as I see it we have multiple users typing into different inputs and each keystroke in each input for each user in theory is overloading the same CPU. Is this even possible? If so I could take the 4 main inputs user search from and push to a core or cores.

I am not in love with the current response time which is, when not being slammed, 350ms to 600ms. I’ve noticed to return auto-suggest in a perfectly timely manner is around 250ms. Since we create urls from the input chracters e.g. /fieldA=ABC and then push that to Sphinx, maybe we can cache those somewhere.

If you think it is possible somehow to push each field input to different cores then we will try that.

Thanks again!

Paul

Hi, struggling with the speed of sphinx queries – do you think you are able to do anything to improve? The site is http://www.bodygoal.co.uk (17500 products). I want the best possible experience for my customers.

Potter

Thanks for your post. After reading, I finally understand what is the “distributed” mean and how to use it.

James

Do you run indexer for each idx1p0, indx1p1, ind1p2…? Or do you run the indexer for idx1 only?