Millie's Library - Book Search and User Pattern Analysis
Figures
Open full size
Open full size
Open full size
Open full size
Open full sizeOverview
Millie's Library is one of Korea's largest subscription e-book services. It was replacing the search behind the app, and the brief was not only "make search work" — two of the things the client wanted, ranking control and personalised recommended search terms, did not exist in the engine and had to be designed from scratch. I delivered the indexing pipeline, the ranking logic, and the search-log analytics the keyword features are derived from, then stayed through launch and stabilisation.
Indexing that stays current
A catalogue this size cannot be rebuilt from scratch every time a book's metadata changes, and it cannot be left stale either — a title that just went live has to be findable immediately. So indexing runs in two modes: a periodic full index that guarantees consistency, and a dynamic index that applies incremental changes as they arrive. The pipeline handles over 100M indexed points and was designed with headroom past 1B records. The other half of index quality is language. Korean is agglutinative, so substring matching gives you both false positives and misses; the win came from morphological analysis with a much wider dictionary than the previous setup had.
Ranking logic, built from nothing
There was no ranking layer to tune. Results came back in whatever order retrieval produced them, which for a catalogue of this size means the right book is somewhere on page four. What I built combines two families of signal. First, field matches — a query hitting the title should not count the same as one hitting the author or the narrator. Second, engagement, counted over rolling windows rather than all-time: opens in the last 7 and 30 days, shelf adds over the same windows, review count, completion percentage, dwell time. Recent windows are weighted more heavily than lifetime totals, which is what stops a book that was popular three years ago from permanently occupying the top slot. Every one of those signals carries its own weight, and weights interact — raising one to fix a bad result usually breaks a good one elsewhere. The client's product manager and I built a fixed list of around a hundred test queries, including spacing and spelling variants, and re-ran the whole list on every weight change so a fix could be checked against the same baseline instead of the one query that prompted it. That discipline is where the 38% ranking-quality improvement came from; it was not a single clever idea.
Deriving keyword features from the logs
Popular terms, rising terms, related terms and recommended terms are all downstream of one thing: analysis of the search logs over time. Volume per term across rolling windows is what produces both the popularity ranking and the rise/fall markers next to it. None of it is hand-curated, so it tracks what users are actually searching for rather than what someone assumed they would. The same pipeline produces two things that are less visible and more useful. The typo-correction dictionary is extracted automatically instead of being maintained by hand. And terms that are searched repeatedly but return few or no results are flagged — sometimes that means a missing dictionary entry, and sometimes it means readers want a book the catalogue does not carry, which goes to the acquisition team. Search logs turned out to be a demand signal, not just a quality signal.
The feature that had no data
Personalised recommended search terms were not in the original scope. The engine had no such capability, there was no labelled data to train anything on, and the client asked for it regardless. Nobody on the project had an approach. I was taking a forecasting course at the time, and the idea came from there: treat search history as a time series rather than as a bag of terms, and use when someone searches as the grouping variable. Accounts that search at similar times of day turn out to behave similarly, which gives you groups without needing demographic data or an explicit profile, and each group's term demand can then be projected forward from its own history. It shipped with the search relaunch, and the recommended-terms row is still in the app today.
What I took from it
Quality, and what it costs. The client's planner tested at QA depth: a fixed set of criteria re-run every time a variable changed, and a list of around a hundred search terms tried with the spacing varied each way. Thinking through side effects before changing anything became a habit on this project rather than an intention. Changing a ranking weight and watching the results reorder was also the part of the work I enjoyed most at the time.
Auto-completion
Suggestions are whole titles containing the typed term, with the match highlighted wherever it falls rather than only at the start — Korean prefix matching alone would miss a term that appears in the middle of a title, which in practice is most of them. The candidate dictionary is rebuilt by a batch job that exports the title and category fields, orders them by recent opens, and then triggers a reload of the completion module. Ordering by demand means the suggestion list is itself ranked, so the most likely completion is the first one.
What I took from it
This was the project where I learned what quality actually costs. The client tested at QA depth — the same fixed query list, the same criteria, every time a variable changed — and thinking through side effects before touching anything became a habit rather than an intention. It is also the first project where the hardest questions I raised had no answer inside my own company and had to go to the engine's R&D team, which is how I found out that being stuck is often a bug report rather than a knowledge gap. And it was fun. Watching results reorder as I moved a weight is the closest thing to a tight feedback loop I had found in production work at that point, and it is a large part of why retrieval and evaluation are still what I want to work on.