Show HN: We built a fast substring search engine in Go
tamber.com
tamber.com
The first and most common is stemming, it can be useful, but it requires a lot of hand tuning and doesn't get you all that much (at least in my experience).
The second is using phonetics of words themselves to try to figure out what someone meant to type in. The grand-daddy of these is Soundex, which works, but isn't as good as Metaphone. Metaphone and Double Metaphone are algorithms that translate most latin-alphabet languages into possible abbreviated phonemes.
Double Metaphone, in particular, has allowed me to build spelling-agnostic prefix matching as an effectively trivial service for two different companies (the details of which you can read about in the Redis mailing list, and/or section 6.1.2 in my book, Redis in Action). The long and short of it is that if you used double metaphone, for the cost of 2-3x index size, you could get all of the results you are looking for in 2-3 searches instead of length(word) searches.
This is practical advice based on experience over several months of research and development scattered over 9 years. ;)
My impression from reading the post is that they went from an extremely inefficient solution straight to one that seems way overengineered for the company's scale. The app has only 28 ratings on iTunes, which means that it's not greatly popular ($150/month is not much more than my personal AWS bill).
My guess is that the highest ROI of this code (besides the joy of hacking) comes from the HN page views. Usually, optimizing software to this level is a bad idea for a small startup. You're not Twitter, Github, DropBox. Code less and grow your business more.
* Suffix arrays: A new method for on-line string searches, U Manber, G Myers, 1993
* Using Suffix Arrays to Compute Term Frequency and Document Frequency for All Substrings in a Corpus, M Yamamoto, KW Church, 2001
The second paper actually uses an 'inverted index' for matching sequences to documents.
[1]: http://www.daemonology.net/papers/bsdiff.pdf [2]: It performs additional preprocessing to make updates smaller (Courgette).
You are apparently not working in Bioinformatics. Suffix arrays are used a LOT here, but rather the more advanced stuff like the BWT. Although, maybe Genomics and Next Generation Sequencing technology are not very "real worldy" to most... :)
Using a Suffix Array should make it possible to do fuzzy matching easily, but I haven't examined precisely how they combined it with an inverted index.
Why were you reading from harddrive with Mongo? Prefix indexing is the only kind of string indexing that Mongo does, but it does a pretty good job with it. You must have a pretty large dataset if it couldn't keep the index in memory.
> It could be easily hit with a regex injection
It should be pretty easy to regex-escape the search string. Easier than building a substring searching engine.
> It couldn't handle artists with multiple names
That sounds like a data format issue.
What I'm getting at is that, while the prefix-only issue might have been enough to justify this switch, none of your other reasons make a ton of sense to me.
I'd clearly start writing my own indexing engine instead of using Posgresql, or maybe switching to digital ocean/hetzner.
Since an 8 GB dataset is a fucking joke I made fun of their post in a sarcastic way.
Also databases that lock are a fucking joke too, maybe I'll just repeat webscale a few times and that will make it performant.
Should be titled "Startup throws out Mongo DB, gets decent performance"
Ferret isn't running on this entire database, though - it's for auto-complete searches over our artist names, roughly 4MB.
EDIT: Since I can't seem to reply to fleitz' below comment, I'll post a response here:
1. Boyer-Moore takes linear time. Ferret takes logarithmic time. Also, it's intended for searching over a single string, so using it on a dictionary would require some sort of hack like a termination character, taking slightly more memory and time.
2. A trie was one of the original iterations of Ferret. The reason it lost out was memory usage, requiring quadratic memory from every word (because this is a suffix search, not just a prefix search).
Edit: I can't reply to you yet, so here goes:
Calling it O(n.m^2) is, at best, an abuse of big-O notation. Since m is a constant (1), that is large-constant O(n). Which is what I've been saying.
Yes, it increases the size of the data set. That would be the "large-constant" part.
(1) If this is not the case, I would love to hear why.
For even a medium sized dictionary of a few dozen MB, you'll find yourself quickly running out of memory. The 4MB dictionary running on the demo would jump to 328MB. Both a Trie and a binary search tree (both of which I've coded and tested) take significantly more memory, and the binary search tree is somewhat slower (try doing a tree traversal between two points as fast as the same traversal over an array).
EDIT: I'll just reply to your edit here for simplicity and because I really don't want to start another thread as I have to get to sleep. I view m as about as constant as n - adding or subtracting words generally changes them both. Try thinking of m as c/n, where c is the total number of characters in your dictionary. O(n m^2) -> O(c^2 / n), whereas ferret uses O(c). The 1,000,000 most frequent English words might have an m of 6-7, but the 100,000 longest English words might have an m of 12-15, taking more memory in a trie, but less memory in Ferret.
But the point is really irrelevant, tbh. We both know how m and n work, and how much memory the trie and Ferret cost for different dictionaries, which is all that should really matter.
You know that there is a C++ version of Lucene?
http://clucene.sourceforge.net/
There's also Xapian, which is built in C++:
So, if Java's footprint is a concern, there are alternatives.
Apache Lucy is another option that might appeal if you consider Lucene bloated. It's written in C, and while there aren't Go bindings yet, there's great interest in providing them and a couple people starting on them: http://mail-archives.apache.org/mod_mbox/lucy-user/201308.mb...
What about idzebra? It's written in C, it predates Lucene and it was used as the indexing system for Harvest (the origin of Squid).
What is more likely than "low memory footprint" to give an indexing system written in Go an advantage over alternatives is the marketing of Go. It seems the language is being promoted nearly every day on HN's front page. If this is any indication of usage trends among developers, the Go Authors must be pleased.
But it looks like this Go library is not based on Lucene which the Ruby version one was and I don't think that Ruby version is maintained anymore so maybe it's not a problem to have the same name.
We did take the time to examine writing our own full search engine based on Lucene, like the aforementioned search library. The reasons why we didn't are briefly mentioned in the blog post, but simply put, we found Lucene too bloated for our purposes (intended for large-scale data retrievals, among many other things). We just needed a low-cost search over a relatively small dictionary, which could be used in an auto-complete field without stealing resources from other processes, such as our recommendation engine.