<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sergey Nikolaev</title>
    <description>The latest articles on DEV Community by Sergey Nikolaev (@sanikolaev).</description>
    <link>https://dev.to/sanikolaev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F363352%2F6f7a2da7-fa00-47f5-aaca-a007b1d43350.jpeg</url>
      <title>DEV Community: Sergey Nikolaev</title>
      <link>https://dev.to/sanikolaev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sanikolaev"/>
    <language>en</language>
    <item>
      <title>Full-Text Search Still Works. It Just Doesn’t Get You to an Answer</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Mon, 24 Aug 2026 10:34:39 +0000</pubDate>
      <link>https://dev.to/sanikolaev/full-text-search-still-works-it-just-doesnt-get-you-to-an-answer-1dhd</link>
      <guid>https://dev.to/sanikolaev/full-text-search-still-works-it-just-doesnt-get-you-to-an-answer-1dhd</guid>
      <description>&lt;p&gt;Originally posted by Klim Todrik on &lt;a href="https://manticoresearch.com/blog/conversational-search/" rel="noopener noreferrer"&gt;https://manticoresearch.com/blog/conversational-search/&lt;/a&gt; on Aug 20, 2026&lt;/p&gt;

&lt;p&gt;Imagine a typical online shoe store.&lt;/p&gt;

&lt;p&gt;A shopper opens search and types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need black waterproof running shoes for daily runs on wet pavement. What would you recommend?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few years ago, almost no one expected this from a search box. The query would have been shortened to something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;black waterproof running shoes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the shopper would open several product cards, compare descriptions, materials, intended use, and price, and make the decision alone.&lt;/p&gt;

&lt;p&gt;Today, people increasingly expect search itself to do part of that work. Not because full-text search has become worse. It still performs very well with exact names, SKUs, product codes, brands, and keywords. What has changed is what people expect to be able to ask a search system.&lt;/p&gt;

&lt;p&gt;For example, Google reported in May 2026 that AI Mode had surpassed one billion monthly users. The company notes that people are asking longer, more complex questions that previously did not fit into conventional search. (&lt;a href="https://blog.google/products-and-platforms/products/search/search-io-2026/" rel="noopener noreferrer"&gt;blog.google&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The same thing happens in an online store.&lt;/p&gt;

&lt;p&gt;A query such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need black waterproof running shoes for daily runs on wet pavement.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;only looks like one sentence. For a search system, it contains several tasks.&lt;/p&gt;

&lt;p&gt;It needs to extract constraints such as color and waterproofing; understand that this is about running rather than walking; account for price, size, and availability; find suitable products; and, if the user asks “which are better,” explain the differences.&lt;/p&gt;

&lt;p&gt;No single algorithm solves all of that.&lt;/p&gt;

&lt;p&gt;Full-text search, vector search, filters, hybrid search, and a language model each solve different parts of the problem. It is far more effective to combine them than to choose between them.&lt;/p&gt;

&lt;p&gt;That is exactly why Manticore has Conversational Search.&lt;/p&gt;




&lt;h2&gt;
  
  
  Word search, semantic search, and conversation are different tasks
&lt;/h2&gt;

&lt;p&gt;Start with a simple query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nike Pegasus 41 black
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the system barely needs to interpret the user’s intent. Full-text search handles it directly.&lt;/p&gt;

&lt;p&gt;Or something even simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SKU 123456
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Semantic methods are not needed here.&lt;/p&gt;

&lt;p&gt;Now consider another example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;light shoes for long summer walks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A product card may not contain the words “summer” or “long walks,” but it may include details such as “breathable material” or “lightweight construction.”&lt;/p&gt;

&lt;p&gt;This is where vector search becomes useful.&lt;/p&gt;

&lt;p&gt;Real queries often fall between these extremes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;black Gore-Tex shoes for everyday running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some parameters — &lt;code&gt;black&lt;/code&gt; and &lt;code&gt;Gore-Tex&lt;/code&gt; — need to be preserved. &lt;code&gt;Everyday running&lt;/code&gt; describes the user’s intent rather than an exact attribute.&lt;/p&gt;

&lt;p&gt;For such cases, Manticore uses hybrid search, combining full-text and vector search through result ranking.&lt;/p&gt;

&lt;p&gt;But even hybrid search returns only a list of results.&lt;/p&gt;

&lt;p&gt;At that point, search considers its job done. The user usually does not.&lt;/p&gt;

&lt;p&gt;It does not answer questions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which of these models are better suited to rain?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it certainly does not handle a follow-up such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which of those cost less than $120?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a conversation. It needs another layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we built
&lt;/h2&gt;

&lt;p&gt;To test this in practice, we used &lt;a href="https://arxiv.org/abs/2602.16938" rel="noopener noreferrer"&gt;ConvApparel&lt;/a&gt;, a dataset of conversations about choosing apparel. After cleanup, it contained 82,524 products: footwear, pants, tops, and outerwear. Each product has a description, category, images, and attributes. We built Manticore Apparel Shop on this data.&lt;/p&gt;

&lt;p&gt;For example, you can type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need black waterproof running shoes for jogging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system first finds suitable products, then a language model generates an answer using them as context, while the interface shows the products themselves.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try the demo:&lt;/strong&gt; &lt;a href="https://chat.manticoresearch.com/" rel="noopener noreferrer"&gt;Manticore Apparel Shop&lt;/a&gt; generates a random product and a query that should retrieve it, then demonstrates that the same query does retrieve that product through Manticore.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvj2udfja5lxjw43kyxbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvj2udfja5lxjw43kyxbq.png" alt="Conversational Search shows an answer and its source products in Manticore Apparel Shop" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is important to keep the connection between the answer and the data. If the system claims that a model is suitable for rain, the user should be able to open the product and verify the source of that claim.&lt;/p&gt;

&lt;p&gt;In this approach, the language model does not replace search. It interprets its results.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Two main commands are used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;CHAT&lt;/span&gt; &lt;span class="n"&gt;MODEL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;CHAT&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, you create a Conversational Search model and set the rules it follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;CHAT&lt;/span&gt; &lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="n"&gt;assistant&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'openrouter:google/gemma-4-26b-a4b-it'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;retrieval_limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_document_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;custom_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'You are a context-only shopping assistant.

Answer using only the provided context.
Do not use outside knowledge or unsupported assumptions.

Recommend only products supported by the retrieved context.
For every recommended product, briefly explain why it matches the request.

End every recommendation with the corresponding
context source ID in the format [ref:&amp;lt;id&amp;gt;].

If none of the retrieved products support the request,
say that you do not have enough information.'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;retrieval_limit&lt;/code&gt; determines how many documents enter the context. &lt;code&gt;max_document_length&lt;/code&gt; limits the amount of text from each document.&lt;/p&gt;

&lt;p&gt;If there is too little context, the model will not see the right products. If there is too much, latency and query cost increase. Like a person, a language model does not become smarter just because it has been given everything to read.&lt;/p&gt;




&lt;p&gt;You can then run a query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;CHAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'I need black waterproof running shoes for jogging'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'convapparel_products'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'assistant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'demo-session-001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'embedding_vector'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then continue the conversation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CALL&lt;/span&gt; &lt;span class="n"&gt;CHAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'Which of these are better for daily use?'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'convapparel_products'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'assistant'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'demo-session-001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'embedding_vector'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system uses conversation history, so the user does not need to repeat the context.&lt;/p&gt;




&lt;h2&gt;
  
  
  Through the HTTP API
&lt;/h2&gt;

&lt;p&gt;Conversational Search is also available through the JSON API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"chat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I need black waterproof running shoes for jogging"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"convapparel_products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"model_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"conversation_uuid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"demo-session-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"vector_field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"embedding_vector"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The request is sent to &lt;code&gt;/search&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the system returns
&lt;/h2&gt;

&lt;p&gt;The response contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;conversation_uuid&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user_query&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;search_query&lt;/code&gt; — the search query generated by the system&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sources&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;search_query&lt;/code&gt; is particularly important.&lt;/p&gt;

&lt;p&gt;If the user writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Which of these would work better in rain?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On its own, this query makes no sense without context. The system therefore forms a complete search query using the conversation history.&lt;/p&gt;

&lt;p&gt;This also simplifies debugging: you can trace the entire chain from query to answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  How retrieval works
&lt;/h2&gt;

&lt;p&gt;Conversational Search uses vector search over an embedding field. The flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user question
→ conversation history
→ search query
→ vector search
→ retrieved documents
→ language model
→ answer and sources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Where search ends and conversation begins
&lt;/h2&gt;

&lt;p&gt;Full-text search works well for exact queries. Vector search works with semantic ones. Hybrid search works with their combination. Conversational Search is needed when the result must be explained, compared, or refined.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quality
&lt;/h2&gt;

&lt;p&gt;We tested this on our &lt;a href="https://github.com/manticoresoftware/conversational-search-quality-becnhmark" rel="noopener noreferrer"&gt;conversational search quality benchmark&lt;/a&gt;, using 200 deterministic shopping queries from ConvApparel. The benchmark evaluates the product IDs returned as sources, rather than the wording of the generated answer.&lt;/p&gt;

&lt;p&gt;In the current run, Manticore scored &lt;strong&gt;0.3650 Hit@3&lt;/strong&gt;, &lt;strong&gt;0.4250 Hit@5&lt;/strong&gt;, &lt;strong&gt;0.5250 Hit@10&lt;/strong&gt;, and &lt;strong&gt;0.2790 MRR&lt;/strong&gt; — the best result on each of those metrics among the engines tested. The full repository includes the dataset-building rules, engine configuration, smoke tasks, and raw results.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Search in modern systems is not one algorithm but several layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full-text search&lt;/li&gt;
&lt;li&gt;vector search&lt;/li&gt;
&lt;li&gt;hybrid search&lt;/li&gt;
&lt;li&gt;Conversational Search&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each solves its own task. A language model does not replace search; it works on top of it.&lt;/p&gt;

&lt;p&gt;Without good search, you do not get a smart assistant; you get a very talkative consultant that barely knows its own catalog.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want to test the approach in practice?&lt;/strong&gt; Try &lt;a href="https://chat.manticoresearch.com/" rel="noopener noreferrer"&gt;Manticore Apparel Shop&lt;/a&gt;: choose a random product, ask a question based on it, and confirm that the same product is found and suggested in the answer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you want to run it locally or explore the code, check the &lt;a href="https://github.com/manticoresoftware/demo-conversational-search" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>database</category>
      <category>search</category>
    </item>
    <item>
      <title>UUID in Manticore: A Practical Guide</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Thu, 06 Aug 2026 05:20:04 +0000</pubDate>
      <link>https://dev.to/sanikolaev/uuid-in-manticore-a-practical-guide-2foc</link>
      <guid>https://dev.to/sanikolaev/uuid-in-manticore-a-practical-guide-2foc</guid>
      <description>&lt;p&gt;In the &lt;a href="https://manticoresearch.com/blog/uuid-document-ids/" rel="noopener noreferrer"&gt;overview article&lt;/a&gt; we explained why it makes sense to use the same UUID in search and in the primary database, when one already exists. Here we will go straight to practice: create a table, run the core operations through SQL and the JSON API, and then load several documents through &lt;code&gt;/bulk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All examples are for Manticore Search 28.5.0 or later. The &lt;code&gt;&amp;lt;generated UUID&amp;gt;&lt;/code&gt; value in the responses means the UUID that Manticore creates when processing the request. You do not need to copy this string into the next request: substitute the actual &lt;code&gt;id&lt;/code&gt; from your own response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table for all examples
&lt;/h2&gt;

&lt;p&gt;To make UUID the document ID, first declare the field as &lt;code&gt;id uuid&lt;/code&gt;. Aside from the &lt;code&gt;id&lt;/code&gt; type, the RT table schema does not change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DESC products_uuid&lt;/code&gt; will show that the &lt;code&gt;id&lt;/code&gt; field has the &lt;code&gt;uuid&lt;/code&gt; type. Both SQL and the HTTP API use this value as the document ID, so there is no need to copy the UUID into a string attribute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with UUID through SQL
&lt;/h2&gt;

&lt;p&gt;First, insert a product with a prebuilt UUID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'Mechanical keyboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'KB-001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;149&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In SQL, the UUID must be enclosed in single quotes. You can find the document with a regular &lt;code&gt;WHERE id = '...'&lt;/code&gt; condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440000'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Manticore can also generate the UUID. To do that, simply do not pass any &lt;code&gt;id&lt;/code&gt; value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'USB microphone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'MIC-001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;LAST_INSERT_ID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;LAST_INSERT_ID()&lt;/code&gt; will return the UUID created by this request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------+
| last_insert_id()                     |
+--------------------------------------+
| &amp;lt;generated UUID&amp;gt;                     |
+--------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;INSERT&lt;/code&gt; for multiple documents and the &lt;code&gt;@@session.last_insert_id&lt;/code&gt; variable are covered in detail in the documentation section on &lt;a href="https://manual.manticoresearch.com/dev/Data_creation_and_modification/Adding_documents_to_a_table/Adding_documents_to_a_real-time_table" rel="noopener noreferrer"&gt;adding documents&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's add another document with a known ID to check &lt;code&gt;IN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'USB-C dock'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'DOCK-001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;119&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440001'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can change attributes with a regular &lt;code&gt;UPDATE&lt;/code&gt;. The &lt;code&gt;id&lt;/code&gt; itself stays the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;139&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440000'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An &lt;code&gt;INSERT&lt;/code&gt; with an already existing UUID will not overwrite the document. Manticore will return a duplicate error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'Duplicate keyboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'KB-DUP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create a new version of the document with the same UUID, use &lt;code&gt;REPLACE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'Mechanical keyboard, revised'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'KB-001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;129&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To change only the price, &lt;code&gt;UPDATE&lt;/code&gt; is enough. Full-text fields and columnar attributes require &lt;code&gt;REPLACE&lt;/code&gt;: it marks the old version of the document with the same ID as deleted and writes the new one. If that ID does not exist yet, Manticore simply adds the document. More details are in the documentation: &lt;a href="https://manual.manticoresearch.com/dev/Data_creation_and_modification/Updating_documents/UPDATE" rel="noopener noreferrer"&gt;UPDATE&lt;/a&gt; and &lt;a href="https://manual.manticoresearch.com/dev/Data_creation_and_modification/Updating_documents/REPLACE" rel="noopener noreferrer"&gt;REPLACE&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Delete the document by the same UUID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440001'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's check the current state of the document whose UUID we set on insert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440000'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SQL client sends and receives a UUID as a string. Pass it as a string parameter, and read the &lt;code&gt;id&lt;/code&gt; from &lt;code&gt;SELECT&lt;/code&gt; as a string. There is no need to convert it to a number or &lt;code&gt;BINARY(16)&lt;/code&gt;. Code written for a numeric document ID will need to be adjusted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same operations through the JSON API
&lt;/h2&gt;

&lt;p&gt;When writing through the JSON API, the &lt;code&gt;id&lt;/code&gt; field is passed alongside &lt;code&gt;table&lt;/code&gt;, not inside &lt;code&gt;doc&lt;/code&gt;. In the first example, we use an uppercase UUID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://localhost:9308/insert &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "table": "products_uuid",
    "id": "AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA",
    "doc": {
      "title": "Wireless keyboard",
      "sku": "JSON-KB-001",
      "price": 159
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Manticore accepts UUID in uppercase, stores it in lowercase, and returns it lowercased in the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"products_uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For automatic generation, remove the &lt;code&gt;id&lt;/code&gt; field entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://localhost:9308/insert &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "table": "products_uuid",
    "doc": {
      "title": "Portable speaker",
      "sku": "JSON-SPK-001",
      "price": 79
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response contains the ID that should be saved for subsequent operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"products_uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;generated UUID&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;/search&lt;/code&gt;, you can use UUID in the &lt;code&gt;equals&lt;/code&gt; filter. If you request &lt;code&gt;id&lt;/code&gt; in &lt;code&gt;_source&lt;/code&gt;, the result will contain the same UUID in both &lt;code&gt;_id&lt;/code&gt; and &lt;code&gt;_source.id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://localhost:9308/search &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "table": "products_uuid",
    "query": {
      "equals": {
        "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
      }
    },
    "_source": ["id", "title", "sku", "price"]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timed_out"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_relation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Wireless keyboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JSON-KB-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;159&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;_id&lt;/code&gt; is part of the search result metadata, while &lt;code&gt;_source.id&lt;/code&gt; is the document field. For a UUID table, they contain the same string.&lt;/p&gt;

&lt;p&gt;Now let's call the remaining endpoints for modifying data one by one. &lt;code&gt;UPDATE&lt;/code&gt; changes only the price:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://localhost:9308/update &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "table": "products_uuid",
    "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
    "doc": {"price": 149}
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To replace the document completely, call &lt;code&gt;/replace&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://localhost:9308/replace &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "table": "products_uuid",
    "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
    "doc": {
      "title": "Wireless keyboard, revised",
      "sku": "JSON-KB-001",
      "price": 139
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now delete the replaced document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://localhost:9308/delete &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "table": "products_uuid",
    "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After &lt;code&gt;/delete&lt;/code&gt;, there is no document with this UUID left in the table. A &lt;code&gt;/search&lt;/code&gt; by the same ID will return &lt;code&gt;total: 0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://localhost:9308/search &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "table": "products_uuid",
    "query": {
      "equals": {
        "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
      }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timed_out"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_relation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Examples of requests for deleting by ID and by condition are collected in the documentation section on &lt;a href="https://manual.manticoresearch.com/dev/Data_creation_and_modification/Deleting_documents" rel="noopener noreferrer"&gt;deleting documents&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to generate UUID
&lt;/h2&gt;

&lt;p&gt;If the UUID is already issued by the primary database, just pass it to Manticore as &lt;code&gt;id&lt;/code&gt;. If the request is repeated, the UUID will stay the same. &lt;code&gt;INSERT&lt;/code&gt; will report a duplicate, and &lt;code&gt;REPLACE&lt;/code&gt; will write a new version of the document under the same ID. The same rule applies to &lt;code&gt;insert&lt;/code&gt; and &lt;code&gt;replace&lt;/code&gt; in &lt;code&gt;/bulk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Manticore can also generate the UUID itself: just do not pass &lt;code&gt;id&lt;/code&gt;. But resending such a request will create another document, so for automatic retries it is better to set the UUID explicitly.&lt;/p&gt;

&lt;p&gt;For an explicit ID, any UUID version from v1 to v8 is suitable. For automatic generation, Manticore uses its own UUIDv8 structure, but it does not convert UUIDs received from the client into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch loading through &lt;code&gt;/bulk&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/bulk&lt;/code&gt; accepts NDJSON: each line contains a separate operation. The UUID is passed in the &lt;code&gt;id&lt;/code&gt; field, just like in the other JSON API requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /bulk
Content-Type: application/x-ndjson

{"insert":{"table":"products_uuid","id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb","doc":{"title":"USB hub","sku":"BULK-HUB-001","price":49}}}
{"insert":{"table":"products_uuid","id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbc","doc":{"title":"Laptop stand","sku":"BULK-STAND-001","price":39}}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the last data line, a trailing newline is required. With &lt;code&gt;curl&lt;/code&gt;, it is convenient to pass the body through &lt;code&gt;--data-binary&lt;/code&gt; so it does not strip newlines.&lt;/p&gt;

&lt;p&gt;Both operations belong to one table, so Manticore executes them in a single transaction. The shortened response shows how many documents were added and how the entire batch finished:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bulk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"current_line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skipped_lines"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On an error, &lt;code&gt;current_line&lt;/code&gt; shows the line where processing stopped, and &lt;code&gt;skipped_lines&lt;/code&gt; shows the number of skipped lines. If an empty line or a table switch split the request into multiple transactions, Manticore will not roll back the ones that already completed.&lt;/p&gt;

&lt;p&gt;When reloading a document, it is important to choose the right operation. If the UUID already exists, &lt;code&gt;insert&lt;/code&gt; will fail with a duplicate error. &lt;code&gt;replace&lt;/code&gt; will write the document again, and if no such ID exists, it will add a new document.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;id&lt;/code&gt; field in &lt;code&gt;/bulk&lt;/code&gt; can also be omitted, and Manticore will generate a UUID. But the &lt;code&gt;/bulk&lt;/code&gt; response contains only the overall transaction result, without separate results for each inserted row. If you need to keep the ID of each document, it is more convenient to generate the UUID before the batch request or add documents one by one through &lt;code&gt;/insert&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation and common errors
&lt;/h2&gt;

&lt;p&gt;Manticore validates the UUID before adding the document. SQL, the JSON API, and &lt;code&gt;/bulk&lt;/code&gt; return errors differently, so do not bind your code to the exact wording of the message.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you sent&lt;/th&gt;
&lt;th&gt;What Manticore will do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;550e8400-e29b-41d4-a716-446655440000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add the document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add the document, store the UUID, and return it in lowercase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A string without hyphens or with an invalid character&lt;/td&gt;
&lt;td&gt;Return an error because the UUID format is invalid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;00000000-0000-0000-0000-000000000000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Return an error: zero UUID cannot be used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A number, including &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Return an error: &lt;code&gt;id&lt;/code&gt; must be a string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A UUID with a version outside the 1-8 range or an RFC-invalid &lt;code&gt;variant&lt;/code&gt; value&lt;/td&gt;
&lt;td&gt;Return a validation error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;INSERT&lt;/code&gt; or &lt;code&gt;insert&lt;/code&gt; in &lt;code&gt;/bulk&lt;/code&gt; with an already existing ID&lt;/td&gt;
&lt;td&gt;Return a duplicate error&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The canonical form consists of 36 characters split into &lt;code&gt;8-4-4-4-12&lt;/code&gt; groups. UUID versions from 1 to 8 are valid; in the RFC &lt;code&gt;variant&lt;/code&gt; position, one of these characters must appear: &lt;code&gt;8&lt;/code&gt;, &lt;code&gt;9&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, or &lt;code&gt;b&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Manticore validates only the ID format. Correct generation is the application's responsibility: for UUIDv4, randomness matters, and for UUIDv7, the time component and compliance with the rules matter. Manticore does not convert passed v4 and v7 values into v8.&lt;/li&gt;
&lt;li&gt;In a table with UUID IDs, &lt;code&gt;id = 0&lt;/code&gt; does not trigger automatic generation unlike numeric IDs: the request will fail. But if you do not pass &lt;code&gt;id&lt;/code&gt;, Manticore will generate its own UUIDv8 structure and return it in the response. Keep in mind that it can be used as a document ID, but not as, for example, an access token.&lt;/li&gt;
&lt;li&gt;You can validate the UUID with a standard library on input, but keep in mind that Manticore still performs its own validation.&lt;/li&gt;
&lt;li&gt;In the &lt;code&gt;/bulk&lt;/code&gt; response, check &lt;code&gt;errors&lt;/code&gt;, &lt;code&gt;current_line&lt;/code&gt;, and &lt;code&gt;skipped_lines&lt;/code&gt;: they show where processing stopped and which part of the batch could not be committed. In SQL batch requests, the entire request is rolled back if an error occurs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Columnar RT and replication
&lt;/h2&gt;

&lt;p&gt;UUID can also be used as the document ID in RT tables with columnar storage. Only the engine declaration changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products_uuid_columnar&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'columnar'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For regular and columnar RT tables, &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;REPLACE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;, exact-match searches, and &lt;code&gt;IN&lt;/code&gt; conditions are written the same way. The client code does not depend on the table engine. But &lt;code&gt;UPDATE&lt;/code&gt; does not change columnar attributes, so, for example, the price in such a table can only be updated together with the whole document through &lt;code&gt;REPLACE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Replication also supports UUID. Suppose the &lt;code&gt;catalog&lt;/code&gt; cluster has already been created, the nodes have joined, and the local &lt;code&gt;products_uuid&lt;/code&gt; table exists. Let's add it to the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;CLUSTER&lt;/span&gt; &lt;span class="k"&gt;catalog&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In SQL, a colon goes between the cluster name and the table name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;products_uuid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655441000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'Replicated keyboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'REPL-KB-001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;169&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;products_uuid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655441000'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the JSON API, the table name is passed unchanged, while the cluster goes in a separate field. For example, the following operation will update a document previously added through SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; http://localhost:9308/update &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "cluster": "catalog",
    "table": "products_uuid",
    "id": "550e8400-e29b-41d4-a716-446655441000",
    "doc": {"price": 159}
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After replication, the document keeps the same UUID on all nodes. You can run exact-match searches, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;REPLACE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; by it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to consider before rollout
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;uuid&lt;/code&gt; type can be assigned only to the &lt;code&gt;id&lt;/code&gt; field. It is supported in RT tables, including those with columnar storage and replication, but not in plain, percolate/PQ, or shard tables.&lt;/li&gt;
&lt;li&gt;An existing table cannot be switched from a numeric ID to a UUID or back with &lt;code&gt;ALTER TABLE&lt;/code&gt;. This decision has to be made when creating the new schema.&lt;/li&gt;
&lt;li&gt;A UUID can be used in &lt;code&gt;=&lt;/code&gt; and &lt;code&gt;IN&lt;/code&gt; conditions. The &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;gt;=&lt;/code&gt; ranges, as well as arithmetic on &lt;code&gt;id&lt;/code&gt;, are not supported.&lt;/li&gt;
&lt;li&gt;UUIDv7 contains time, but filtering &lt;code&gt;id&lt;/code&gt; by range is still not allowed. For date-based selection, add an attribute such as &lt;code&gt;created_at timestamp&lt;/code&gt; and filter by it.&lt;/li&gt;
&lt;li&gt;The document ID cannot be changed with &lt;code&gt;UPDATE&lt;/code&gt;. To give an object a different UUID, you will need to create a document with a new ID and delete the old one separately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The behavior of SQL sessions and the full list of limitations are described in the documentation: &lt;a href="https://manual.manticoresearch.com/dev/Creating_a_table/Data_types#UUID-document-IDs" rel="noopener noreferrer"&gt;UUID document IDs&lt;/a&gt;. Support for UUID as a document ID appeared in &lt;a href="https://manticoresearch.com/blog/manticore-search-28-6-6/" rel="noopener noreferrer"&gt;Manticore Search 28.6.6&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>search</category>
      <category>sql</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>UUID in Manticore: a single ID for the primary database and Manticore</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Wed, 05 Aug 2026 04:21:12 +0000</pubDate>
      <link>https://dev.to/sanikolaev/uuid-in-manticore-a-single-id-for-the-primary-database-and-manticore-39bl</link>
      <guid>https://dev.to/sanikolaev/uuid-in-manticore-a-single-id-for-the-primary-database-and-manticore-39bl</guid>
      <description>&lt;p&gt;Suppose your product already has the ID &lt;code&gt;550e8400-e29b-41d4-a716-446655440000&lt;/code&gt; in the primary database. It shows up in events, logs, and API responses. But when you load that same product into Manticore, the application still has to assign it another, numeric ID.&lt;/p&gt;

&lt;p&gt;Before Manticore Search 28.5.0, the document ID was an unsigned 64-bit number. A UUID could be stored in a separate string attribute, but that did not make it the document ID. &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;REPLACE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; still required a numeric &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As a result, you had to keep a mapping between the UUID from the primary database and the numeric ID in the Manticore table. Now you can do without it: an RT table in Manticore can use a UUID as the document ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a second ID is a problem
&lt;/h2&gt;

&lt;p&gt;A mapping table between IDs is not exactly complicated, but only as long as the application is loading data and searching. The problems start when documents change.&lt;/p&gt;

&lt;p&gt;A worker receives an event with a UUID, finds the matching numeric ID, and only then sends the update to Manticore. Deletion follows the same path. If the mapping record is missing or stale, the wrong document may be updated, or the changes may never reach Manticore at all.&lt;/p&gt;

&lt;p&gt;Another option is to turn the UUID into a 64-bit hash. Then the application itself has to account for possible collisions. You can also introduce a separate sequence counter, but then it has to be coordinated across all processes that create documents.&lt;/p&gt;

&lt;p&gt;There is another subtle point with numeric IDs, and it appears at the API level. Inside Manticore it is a &lt;code&gt;uint64&lt;/code&gt;, while SQL shows it as a signed &lt;code&gt;BIGINT&lt;/code&gt;. Because of that, SQL can return values greater than &lt;code&gt;2^63-1&lt;/code&gt; as negative numbers, and the client has to convert them carefully. A UUID is passed and returned as a string, so there is no need to worry about signed range or overflow.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;id uuid&lt;/code&gt; solves the problem at the root: the object identifier no longer needs conversion. The same UUID is used in the primary DB, the queue, Manticore, logs, and the external API.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a UUID is
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9562.html" rel="noopener noreferrer"&gt;UUID&lt;/a&gt; is essentially a 128-bit identifier that does not require a central registry to create. In text form, it usually consists of 36 characters: 32 hexadecimal digits and four hyphens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;550e8400-e29b-41d4-a716-446655440000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A UUID contains a version and a &lt;code&gt;variant&lt;/code&gt;. The version defines how the remaining bits are formed. UUIDv4 is based on random or pseudorandom data. UUIDv7 includes a time component and preserves the chronological order of identifiers. In UUIDv8, the placement of the remaining bits is defined by a specific implementation.&lt;/p&gt;

&lt;p&gt;The lack of a central registry makes it possible to assign an ID before writing to a shared database. For example, two independent services can create objects in parallel, and a mobile client with no LTE signal can prepare data without a server connection. After synchronization, the object keeps the same identifier.&lt;/p&gt;

&lt;p&gt;However, a UUID should not be treated as an absolute guarantee of uniqueness. In practice, that depends on the correctness of the chosen generator. Also keep in mind that a UUID is not a secret and does not replace a password, token, or permission check.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in Manticore 28.5.0
&lt;/h2&gt;

&lt;p&gt;For an RT table, you can now explicitly set the document ID type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'550e8400-e29b-41d4-a716-446655440000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'Mechanical keyboard'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'KB-001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;149&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After insertion, the same UUID can be used in equality and &lt;code&gt;IN&lt;/code&gt; filters, as well as in &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;REPLACE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;. SQL returns the ID as a string. You can also pass an explicit UUID or ask Manticore to generate an ID through the JSON API; we will cover detailed requests and responses in the next article.&lt;/p&gt;

&lt;p&gt;If you run &lt;code&gt;INSERT&lt;/code&gt; again with a UUID that already exists, Manticore rejects it, just as it used to with a numeric ID. Use &lt;code&gt;REPLACE&lt;/code&gt; to overwrite a document by ID.&lt;/p&gt;

&lt;p&gt;Note that the &lt;code&gt;uuid&lt;/code&gt; type applies only to the document ID. You cannot declare a regular user attribute with the &lt;code&gt;uuid&lt;/code&gt; type.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who creates the UUID
&lt;/h2&gt;

&lt;p&gt;There are two options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;How the document is written&lt;/th&gt;
&lt;th&gt;Who creates the ID&lt;/th&gt;
&lt;th&gt;What Manticore does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The &lt;code&gt;id&lt;/code&gt; field is provided&lt;/td&gt;
&lt;td&gt;The primary DB, a client library, or another system component&lt;/td&gt;
&lt;td&gt;Validates the format, version, and &lt;code&gt;variant&lt;/code&gt;, then stores the UUID in lowercase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The &lt;code&gt;id&lt;/code&gt; field is missing&lt;/td&gt;
&lt;td&gt;Manticore&lt;/td&gt;
&lt;td&gt;Creates a UUIDv8 and encodes its internal numeric auto-ID into it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your application already creates the UUID, you do not need to adapt it to Manticore. The format is standard: five groups of &lt;code&gt;8-4-4-4-12&lt;/code&gt;. The version can be any from v1 to v8, and the &lt;code&gt;variant&lt;/code&gt; position must contain &lt;code&gt;8&lt;/code&gt;, &lt;code&gt;9&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, or &lt;code&gt;b&lt;/code&gt;. Case does not matter: you can send the UUID in uppercase, but Manticore will store it in lowercase.&lt;/p&gt;

&lt;p&gt;That is where validation ends: Manticore checks the UUID format, but not the quality of the generator. The client library is responsible for the randomness of UUIDv4 and the correct time component in UUIDv7.&lt;/p&gt;

&lt;p&gt;Server-side generation works differently. If the &lt;code&gt;id&lt;/code&gt; field is not provided, Manticore creates a UUIDv8 with its own structure and encodes the built-in numeric auto-ID into it. This is not a random UUIDv4 and it is not a secret value.&lt;/p&gt;

&lt;p&gt;A UUIDv8 created by the application will be validated and stored by Manticore as is; the server will not reshape it according to its own scheme.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Technical note. The full UUID remains the external identifier: Manticore does not replace it with a 64-bit hash and does not require the application to store a mapping table. How the UUID is laid out inside the engine is an implementation detail and does not affect the external contract.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where it works
&lt;/h2&gt;

&lt;p&gt;You can use a UUID as the document ID in regular RT tables, RT tables with &lt;code&gt;engine='columnar'&lt;/code&gt;, and tables in a replication cluster. With such IDs, exact match search, &lt;code&gt;IN&lt;/code&gt; filters, and the usual document operations are available: &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;REPLACE&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Keep these limitations in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;uuid&lt;/code&gt; type is not suitable for regular attributes: Manticore will reject a declaration such as &lt;code&gt;guid uuid&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;in plain, percolate/PQ, and shard tables, UUIDs cannot be used as the document ID;&lt;/li&gt;
&lt;li&gt;an existing table cannot be switched from a numeric ID to a UUID, or back, with &lt;code&gt;ALTER TABLE&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;range conditions &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;lt;=&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;gt;=&lt;/code&gt;, as well as arithmetic operations on IDs of this type, are not supported;&lt;/li&gt;
&lt;li&gt;the document ID cannot be changed through &lt;code&gt;UPDATE&lt;/code&gt; - this applies to both UUIDs and numeric IDs;&lt;/li&gt;
&lt;li&gt;for automatic generation, the &lt;code&gt;id&lt;/code&gt; field must be omitted: the value &lt;code&gt;0&lt;/code&gt; does not work here as a special marker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is easy to forget when porting legacy code. For a numeric RT table, &lt;code&gt;0&lt;/code&gt; can mean “create the ID automatically.” In a UUID table, you simply need to leave out the &lt;code&gt;id&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;UUIDv7 also does not make &lt;code&gt;id&lt;/code&gt; suitable for time-based filters. You can use it as an external identifier, but Manticore does not yet support queries like &lt;code&gt;id &amp;gt; ...&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;If the UUID already exists in the primary DB, you can now pass it to Manticore together with the document. If the document appears in Manticore first, do not specify &lt;code&gt;id&lt;/code&gt; and take the generated UUID from the response.&lt;/p&gt;

&lt;p&gt;From there, use the same UUID in &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;REPLACE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;. There is no longer any need to give the document a separate numeric ID and keep a mapping between the two identifiers.&lt;/p&gt;

&lt;p&gt;The full contract and current limitations are documented here: &lt;a href="https://manual.manticoresearch.com/dev/Creating_a_table/Data_types#UUID-document-IDs" rel="noopener noreferrer"&gt;UUID document IDs&lt;/a&gt;. Support for UUID as a document ID appeared in &lt;a href="https://manticoresearch.com/blog/manticore-search-28-5-0/" rel="noopener noreferrer"&gt;Manticore Search 28.5.0&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the next article, &lt;a href="https://manticoresearch.com/blog/uuid-document-ids-cookbook/" rel="noopener noreferrer"&gt;a practical guide to using UUIDs as document IDs&lt;/a&gt;, we will step through how to set and generate IDs with SQL and JSON, search, update, replace, and delete documents, and handle errors.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>database</category>
    </item>
    <item>
      <title>Manticore Search 28.6.6: UUID document IDs, ordered GROUP_CONCAT(), and 16 fixes</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Fri, 31 Jul 2026 09:33:57 +0000</pubDate>
      <link>https://dev.to/sanikolaev/manticore-search-2866-uuid-document-ids-ordered-groupconcat-and-16-fixes-12ch</link>
      <guid>https://dev.to/sanikolaev/manticore-search-2866-uuid-document-ids-ordered-groupconcat-and-16-fixes-12ch</guid>
      <description>&lt;p&gt;Manticore Search 28.6.6 has been released. The headline additions are UUID document IDs for real-time tables and ordered, limited &lt;code&gt;GROUP_CONCAT()&lt;/code&gt; for grouped queries. The release also includes 16 fixes for backups, replication, query processing, SQL compatibility, and secondary indexes.&lt;/p&gt;

&lt;p&gt;This post covers everything shipped from &lt;strong&gt;28.4.5 through 28.6.6&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Upgrade notes
&lt;/h2&gt;

&lt;p&gt;There are no new mandatory data migrations in this release. UUID IDs are an opt-in table definition: existing numeric-ID tables keep working as they are. If you want UUID identifiers, create a real-time table with &lt;code&gt;id uuid&lt;/code&gt;; &lt;code&gt;ALTER TABLE&lt;/code&gt; cannot convert an existing table between numeric and UUID IDs.&lt;/p&gt;

&lt;p&gt;Two fixes are particularly useful for production installations. Successful backups now always unfreeze real-time tables when they finish (previously in rare cases they didn't), rather than leaving writes blocked. And authenticated replication can again add an existing populated RT table with &lt;code&gt;ALTER CLUSTER ... ADD&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  UUID document IDs for real-time tables
&lt;/h2&gt;

&lt;p&gt;Applications often already have UUID identifiers from the system of record. Until now, using them with Manticore Search meant maintaining a separate numeric ID mapping. Real-time tables can now use &lt;a href="https://manual.manticoresearch.com/Creating_a_table/Data_types#UUID-document-IDs" rel="noopener noreferrer"&gt;UUID document IDs&lt;/a&gt; directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products_uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Manticore accepts an explicit UUID string, or generates one when &lt;code&gt;id&lt;/code&gt; is omitted from an insert or replace. UUID equality and &lt;code&gt;IN&lt;/code&gt; filters work in queries, and UUID IDs can be used with &lt;code&gt;REPLACE&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is currently a real-time-table capability, including columnar and replicated RT tables. Plain, percolate, and sharded tables continue to use their existing ID models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ordered and limited GROUP_CONCAT()
&lt;/h2&gt;

&lt;p&gt;Grouped results often need a compact preview of the most relevant values in each group. &lt;code&gt;GROUP_CONCAT()&lt;/code&gt; can now sort values and retain only the requested number of them in explicit SQL &lt;code&gt;GROUP BY&lt;/code&gt; queries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt; &lt;span class="n"&gt;SEPARATOR&lt;/span&gt; &lt;span class="s1"&gt;', '&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new &lt;code&gt;ORDER BY&lt;/code&gt;, &lt;code&gt;SEPARATOR&lt;/code&gt;, and &lt;code&gt;LIMIT&lt;/code&gt; options let the query return the top values in the requested order instead of concatenating an arbitrary-sized set and sorting it again in application code. See the &lt;a href="https://manual.manticoresearch.com/Searching/Grouping#GROUP_CONCAT%28field%29" rel="noopener noreferrer"&gt;GROUP_CONCAT() reference&lt;/a&gt; for the full syntax.&lt;/p&gt;




&lt;h2&gt;
  
  
  Safer table, backup, and cluster maintenance
&lt;/h2&gt;

&lt;p&gt;A few changes remove failures that tend to appear during routine operations rather than ordinary searches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/manticoresoftware/manticoresearch-backup" rel="noopener noreferrer"&gt;Manticore Backup&lt;/a&gt; 1.10.2 fixes a path where a successful backup could leave an RT table frozen and future writes blocked.&lt;/li&gt;
&lt;li&gt;Authenticated replication state transfer now works when &lt;code&gt;ALTER CLUSTER ... ADD&lt;/code&gt; is used to add a populated RT table.&lt;/li&gt;
&lt;li&gt;RT lifecycle operations now remove obsolete external files after &lt;code&gt;ALTER&lt;/code&gt;, &lt;code&gt;TRUNCATE&lt;/code&gt;, chunk removal, and optimization. The same release preserves Jieba settings during unrelated &lt;code&gt;ALTER TABLE&lt;/code&gt; operations and rejects unsupported effective setting changes explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://manual.manticoresearch.com/Securing_and_compacting_a_table/Compacting_a_table#OPTIMIZE-TABLE" rel="noopener noreferrer"&gt;OPTIMIZE TABLE&lt;/a&gt; accepts qualified &lt;code&gt;system.&amp;lt;table&amp;gt;&lt;/code&gt; names, so physical sharded tables can be optimized.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Query processing and client compatibility
&lt;/h2&gt;

&lt;p&gt;The fixes are broad, but several stand out for busy search services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://manual.manticoresearch.com/Searching/Highlighting#CALL-SNIPPETS" rel="noopener noreferrer"&gt;CALL SNIPPETS&lt;/a&gt; no longer spends seconds simplifying some complex boolean queries before highlighting them when &lt;code&gt;boolean_simplify&lt;/code&gt; is enabled.&lt;/li&gt;
&lt;li&gt;Long exact and phrase-like queries no longer risk a query-stack underestimate that could corrupt memory or crash &lt;code&gt;searchd&lt;/code&gt;; repeated keyword statistics are also deduplicated for &lt;code&gt;local_df&lt;/code&gt; work on multi-chunk RT and distributed tables.&lt;/li&gt;
&lt;li&gt;The updated columnar library fixes a StreamVByte decoder over-read that could crash secondary-index queries, including &lt;code&gt;GROUP BY&lt;/code&gt; on JSON fields. &lt;code&gt;indextool --check&lt;/code&gt; now reports corrupted secondary-index files instead of crashing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;USER()&lt;/code&gt; remains usable after &lt;code&gt;USER&lt;/code&gt; became an authentication keyword, restoring MySQL client &lt;code&gt;status&lt;/code&gt; command compatibility. &lt;code&gt;SHOW INDEX ... STATUS&lt;/code&gt; is also protected from a concurrent percentile-calculation race.&lt;/li&gt;
&lt;li&gt;Document-ID &lt;code&gt;IN(...)&lt;/code&gt; filters correctly accept valid unsigned 64-bit IDs above &lt;code&gt;Long.MAX_VALUE&lt;/code&gt;, and a &lt;code&gt;LEFT JOIN&lt;/code&gt; with multiple &lt;code&gt;FACET&lt;/code&gt; clauses no longer crashes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The release also fixes a SphinxQL SSL connection-handling race that could consume workers at 100% CPU, and fixes PHP API response parsing and the Ruby API fixture so valid response chunks ending in &lt;code&gt;"0"&lt;/code&gt; are preserved.&lt;/p&gt;

&lt;p&gt;For the complete list, see the &lt;a href="https://manual.manticoresearch.com/Changelog#Version-28.6.6" rel="noopener noreferrer"&gt;Version 28.6.6 changelog&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Manticore Search 28.6.6
&lt;/h2&gt;

&lt;p&gt;Install or upgrade Manticore Search with the &lt;a href="https://manticoresearch.com/install/" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;. If you are upgrading an authenticated or replicated deployment, review the existing &lt;a href="https://manticoresearch.com/blog/manticore-auth-migration-hardening-checklist/" rel="noopener noreferrer"&gt;authentication rollout checklist&lt;/a&gt; before changing production traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Need help or want to connect?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Join our &lt;a href="https://slack.manticoresearch.com" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit the &lt;a href="https://forum.manticoresearch.com" rel="noopener noreferrer"&gt;Forum&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Report issues or suggest features on &lt;a href="https://github.com/manticoresoftware/manticoresearch/issues" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email us at &lt;code&gt;contact@manticoresearch.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>search</category>
      <category>sql</category>
    </item>
    <item>
      <title>Faceted search under active filters</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Thu, 30 Jul 2026 04:54:21 +0000</pubDate>
      <link>https://dev.to/sanikolaev/faceted-search-under-active-filters-3bn7</link>
      <guid>https://dev.to/sanikolaev/faceted-search-under-active-filters-3bn7</guid>
      <description>&lt;p&gt;Facets in an online store seem simple until the first filter is selected.&lt;/p&gt;

&lt;p&gt;In a catalog, they are part of the navigation. A selected color should not disappear from the list. Other colors should remain available: the user may want to switch to one or broaden the selection. Options with no matching products are more useful when shown as unavailable. Within a single facet, &lt;code&gt;OR&lt;/code&gt; usually applies: red or blue. Across different facets, it is &lt;code&gt;AND&lt;/code&gt;: brand, color, and size at the same time.&lt;/p&gt;

&lt;p&gt;The difficult part begins after the first selection. Imagine a product catalog where the user has selected a brand, color, and size. The main result set must remain narrow and show only products matching all three conditions. But the filter panel follows different rules. It needs to preserve the selected color while also showing the colors the user can switch to under the same brand and size.&lt;/p&gt;

&lt;p&gt;If every facet inherits all filters from the main query, it quickly collapses to the values already selected. If the filters for every facet have to be rebuilt manually, you end up with separate query branches for color, size, brand, availability, seller, and every other attribute.&lt;/p&gt;

&lt;p&gt;Manticore Search 25.12.0 introduced &lt;code&gt;facet_filter_mode&lt;/code&gt;, which moves this behavior into the facet API. A query can now describe how the store's filter panel should behave, without the application manually building nearly identical queries for every facet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What e-commerce facets need
&lt;/h2&gt;

&lt;p&gt;For a filter panel, answering "how many products have this value?" is not enough. It needs statuses that the interface can use directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;selected values remain visible and can be cleared quickly;&lt;/li&gt;
&lt;li&gt;other values in the same facet remain available because they broaden the filter through &lt;code&gt;IN (...)&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;values in other facets indicate whether selecting them would return at least one product;&lt;/li&gt;
&lt;li&gt;counts remain predictable: the interface knows whether a number refers to the current result set or a broader selection;&lt;/li&gt;
&lt;li&gt;search within long brand lists, categories, price ranges, SEO rules, and query analytics still matter; they are a separate part of search design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;facet_filter_mode&lt;/code&gt; handles the most common part of this problem: recalculating facets after the user has already selected several filters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;Facets now have three filter inheritance modes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;strict&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Applies all filters from the main query to the facet. This is the previous behavior and remains the default.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;auto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Applies every filter except filters on the facet itself and adds &lt;code&gt;status&lt;/code&gt;: selected values get &lt;code&gt;selected&lt;/code&gt;, while other values get &lt;code&gt;available&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Calculates buckets over a broad base result set and adds &lt;code&gt;status&lt;/code&gt; to distinguish selected, available, and unavailable values.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can also control this manually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ALL FILTERS&lt;/code&gt; - apply all filters to the facet;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FILTERS color_id, size_id&lt;/code&gt; - apply only the listed filters;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EXCLUDE FILTERS color_id&lt;/code&gt; - apply every filter except those listed;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ZEROES&lt;/code&gt; - starting with Manticore Search 27.3.0, preserve buckets from the broad &lt;code&gt;max&lt;/code&gt; scope in SQL &lt;code&gt;max&lt;/code&gt; mode even when their &lt;code&gt;count(*)&lt;/code&gt; in the current facet is &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same options are available in the JSON API through &lt;code&gt;facet_filter_mode&lt;/code&gt;, &lt;code&gt;mode&lt;/code&gt;, &lt;code&gt;filters&lt;/code&gt;, &lt;code&gt;exclude_filters&lt;/code&gt;, and &lt;code&gt;zeroes: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In short, &lt;code&gt;strict&lt;/code&gt; answers "what is in the current result set?", &lt;code&gt;auto&lt;/code&gt; answers "what happens if this particular filter changes?", and &lt;code&gt;max&lt;/code&gt; shows a broad list of buckets with a &lt;code&gt;status&lt;/code&gt; field: &lt;code&gt;selected&lt;/code&gt;, &lt;code&gt;available&lt;/code&gt;, or &lt;code&gt;unavailable&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimal example
&lt;/h2&gt;

&lt;p&gt;Consider a small catalog with a brand, color, size, and SKU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;brand_id&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;color_id&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;color_name&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;size_name&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;brand_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;color_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;size_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;size_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'p1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'small'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'sku1'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'p2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'large'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'sku2'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'p3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'blue'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'small'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'sku3'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'p4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'green'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'xlarge'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'sku4'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'p5'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'small'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'sku5'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'p6'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'black'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'large'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'sku6'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'p7'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'white'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'small'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'sku7'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user selected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;brand_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under these conditions, the result set contains one product: &lt;code&gt;p1&lt;/code&gt;. Let's see what happens to the facets.&lt;/p&gt;

&lt;h2&gt;
  
  
  strict: the previous behavior
&lt;/h2&gt;

&lt;p&gt;Without additional options, Manticore Search uses &lt;code&gt;strict&lt;/code&gt;: every facet receives all filters from the main query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;brand_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------+----------+
| color_id | count(*) |
+----------+----------+
|        1 |        1 |
+----------+----------+
+---------+----------+
| size_id | count(*) |
+---------+----------+
|      10 |        1 |
+---------+----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an accurate SQL response, but it is often too narrow for an online store. The user sees only the already selected &lt;code&gt;color_id=1&lt;/code&gt; and &lt;code&gt;size_id=10&lt;/code&gt;. It looks as if there are no other options, even though the data contains a product with the same brand and size but a different color (&lt;code&gt;color_id=2&lt;/code&gt;), and another with the same brand and color but a different size (&lt;code&gt;size_id=20&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  auto: a facet ignores its own filter
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;auto&lt;/code&gt; mode keeps every filter except the filter on the facet currently being calculated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;brand_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;OPTION&lt;/span&gt; &lt;span class="n"&gt;facet_filter_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'auto'&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------+----------+-----------+
| color_id | count(*) | status    |
+----------+----------+-----------+
|        1 |        1 | selected  |
|        2 |        1 | available |
+----------+----------+-----------+
+---------+----------+-----------+
| size_id | count(*) | status    |
+---------+----------+-----------+
|      10 |        1 | selected  |
|      20 |        1 | available |
+---------+----------+-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FACET color_id&lt;/code&gt; applied &lt;code&gt;brand_id=7 AND size_id=10&lt;/code&gt;, but not &lt;code&gt;color_id=1&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FACET size_id&lt;/code&gt; applied &lt;code&gt;brand_id=7 AND color_id=1&lt;/code&gt;, but not &lt;code&gt;size_id=10&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives the interface alternative values without a separate query for every facet. Selected buckets are marked &lt;code&gt;selected&lt;/code&gt;, while other values in the same facet are marked &lt;code&gt;available&lt;/code&gt;: they can be added to the current filter as a broader selection through &lt;code&gt;IN (...)&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  max: a broad bucket list with statuses
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;auto&lt;/code&gt; shows only values from the current filter scope for a particular facet. &lt;code&gt;max&lt;/code&gt; goes further: it calculates buckets over the base result set and marks their status separately for the interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;brand_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;OPTION&lt;/span&gt; &lt;span class="n"&gt;facet_filter_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'max'&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------+----------+-----------+
| color_id | count(*) | status    |
+----------+----------+-----------+
|        1 |        3 | selected  |
|        2 |        1 | available |
|        3 |        1 | available |
|        4 |        1 | available |
|        5 |        1 | available |
+----------+----------+-----------+
+---------+----------+-----------+
| size_id | count(*) | status    |
+---------+----------+-----------+
|      10 |        4 | selected  |
|      20 |        2 | available |
|      30 |        1 | available |
+---------+----------+-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;status&lt;/code&gt; comes directly from Manticore Search:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;selected&lt;/code&gt; - the value is already present in the filter on this facet;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;available&lt;/code&gt; - the value can be selected; for another value in the same facet, this broadens the filter through &lt;code&gt;IN (...)&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unavailable&lt;/code&gt; - the value exists in the facet's broad result set, but selecting it would return no documents under the current filters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this example, &lt;code&gt;color_id=1&lt;/code&gt; occurs in three products across the catalog, so its count in &lt;code&gt;max&lt;/code&gt; is &lt;code&gt;3&lt;/code&gt;. It is marked &lt;code&gt;selected&lt;/code&gt; because it already participates in the filter. The other colors are marked &lt;code&gt;available&lt;/code&gt;: if the user selects one, the color filter becomes broader, for example &lt;code&gt;color_id IN (1,2)&lt;/code&gt;. Unavailable values appear when a bucket in the broad result set cannot return documents under the current filters; the &lt;code&gt;sku&lt;/code&gt; example below demonstrates this case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manually setting the filter scope
&lt;/h2&gt;

&lt;p&gt;The global &lt;code&gt;facet_filter_mode&lt;/code&gt; covers most common cases, but sometimes different facets need different behavior. For example, color can remain strictly constrained by all filters, size can use &lt;code&gt;max&lt;/code&gt;, SKU can be calculated using only color and size, and brand can be calculated without the color filter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;brand_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;OPTION&lt;/span&gt; &lt;span class="n"&gt;facet_filter_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'max'&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;FILTERS&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="n"&gt;FILTERS&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;brand_id&lt;/span&gt; &lt;span class="n"&gt;EXCLUDE&lt;/span&gt; &lt;span class="n"&gt;FILTERS&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;brand_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------+----------+-----------+
| color_id | count(*) | status    |
+----------+----------+-----------+
|        1 |        1 | selected  |
+----------+----------+-----------+
+---------+----------+-----------+
| size_id | count(*) | status    |
+---------+----------+-----------+
|      10 |        4 | selected  |
|      20 |        2 | available |
|      30 |        1 | available |
+---------+----------+-----------+
+------+----------+-------------+
| sku  | count(*) | status      |
+------+----------+-------------+
| sku1 |        1 | available   |
| sku5 |        1 | unavailable |
+------+----------+-------------+
+----------+----------+----------+
| brand_id | count(*) | status   |
+----------+----------+----------+
|        7 |        2 | selected |
+----------+----------+----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to read this query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FACET color_id ALL FILTERS&lt;/code&gt; applies every filter and returns only the selected color;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FACET size_id&lt;/code&gt; inherits the query-level &lt;code&gt;max&lt;/code&gt; mode;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FACET sku FILTERS color_id, size_id&lt;/code&gt; applies only the color and size filters;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FACET brand_id EXCLUDE FILTERS color_id&lt;/code&gt; applies every filter except color.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mode is useful when a filter panel contains both regular and technical facets, and some values need to be calculated according to special rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  ZEROES: zero-count buckets in max
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ZEROES&lt;/code&gt; is useful when counts need to remain strict but the list of values needs to stay broad. It works with &lt;code&gt;max&lt;/code&gt;, either through &lt;code&gt;OPTION facet_filter_mode='max'&lt;/code&gt; or through &lt;code&gt;MODE max&lt;/code&gt; on an individual facet.&lt;/p&gt;

&lt;p&gt;Without &lt;code&gt;ZEROES&lt;/code&gt;, a facet with &lt;code&gt;ALL FILTERS&lt;/code&gt; shows only the bucket that passes every filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;brand_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;OPTION&lt;/span&gt; &lt;span class="n"&gt;facet_filter_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'max'&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;FILTERS&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------+----------+----------+
| size_id | count(*) | status   |
+---------+----------+----------+
|      10 |        1 | selected |
+---------+----------+----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;ZEROES&lt;/code&gt;, Manticore Search keeps the same visible counts but returns the remaining buckets from the broad &lt;code&gt;max&lt;/code&gt; scope with a count of zero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;brand_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;color_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;OPTION&lt;/span&gt; &lt;span class="n"&gt;facet_filter_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'max'&lt;/span&gt;
&lt;span class="n"&gt;FACET&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;FILTERS&lt;/span&gt; &lt;span class="n"&gt;ZEROES&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;size_id&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------+----------+-----------+
| size_id | count(*) | status    |
+---------+----------+-----------+
|      10 |        1 | selected  |
|      20 |        0 | available |
|      30 |        0 | available |
+---------+----------+-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interface can then show &lt;code&gt;large&lt;/code&gt; and &lt;code&gt;xlarge&lt;/code&gt; alongside the selected &lt;code&gt;small&lt;/code&gt;: the number refers to the current strict result set, while &lt;code&gt;status&lt;/code&gt; shows that these values can still be selected to broaden the filter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same query through the JSON API
&lt;/h2&gt;

&lt;p&gt;SQL is shorter here and shows the mechanics more clearly, but the JSON API supports the same approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/search&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"must"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"equals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"brand_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"equals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"color_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"equals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"size_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"facet_filter_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aggs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"colors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"terms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"color_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"color_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asc"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sizes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"terms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"size_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"size_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asc"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"took"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timed_out"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_relation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aggregations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"colors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"buckets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"doc_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"selected"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"doc_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"available"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"doc_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"available"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"doc_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"available"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"doc_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"available"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sizes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"buckets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"doc_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"selected"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"doc_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"available"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"doc_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"available"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In JSON aggregations, &lt;code&gt;mode&lt;/code&gt;, &lt;code&gt;filters&lt;/code&gt;, &lt;code&gt;exclude_filters&lt;/code&gt;, and &lt;code&gt;zeroes&lt;/code&gt; can also be set for each individual aggregation.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this differs from Meilisearch, Elasticsearch, and OpenSearch
&lt;/h2&gt;

&lt;p&gt;We tested the same scenario in Manticore Search, Meilisearch, Elasticsearch, and OpenSearch: the &lt;code&gt;brand_id=7&lt;/code&gt;, &lt;code&gt;color_id=1&lt;/code&gt;, and &lt;code&gt;size_id=10&lt;/code&gt; filters are active; the result set remains strict, while the facets need to show options that appear when their own filter is excluded.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Engine&lt;/th&gt;
&lt;th&gt;Excludes its own filter in one query&lt;/th&gt;
&lt;th&gt;Native bucket status&lt;/th&gt;
&lt;th&gt;What remains for the application&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Manticore Search&lt;/td&gt;
&lt;td&gt;Yes, through &lt;code&gt;auto&lt;/code&gt;/&lt;code&gt;max&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes, in &lt;code&gt;auto&lt;/code&gt;/&lt;code&gt;max&lt;/code&gt;; &lt;code&gt;unavailable&lt;/code&gt; only in &lt;code&gt;max&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Rendering the interface, SEO, analytics, and custom rules.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meilisearch&lt;/td&gt;
&lt;td&gt;Not in this form&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Run additional queries and assemble facets in the application.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenSearch&lt;/td&gt;
&lt;td&gt;Yes, but manually through &lt;code&gt;global&lt;/code&gt; + &lt;code&gt;filter&lt;/code&gt; aggregations&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Duplicate filter branches and calculate &lt;code&gt;status&lt;/code&gt; in the application.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elasticsearch&lt;/td&gt;
&lt;td&gt;Yes, but manually through &lt;code&gt;global&lt;/code&gt; + &lt;code&gt;filter&lt;/code&gt; aggregations&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;The same: maintain separate aggregation branches and client-side logic.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Basic facets are easy to use in Meilisearch, but a query such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"brand_id = 7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"color_id = 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"size_id = 10"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"facets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"color_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"size_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;returns counts for the already filtered result set. In our dataset, that means only &lt;code&gt;color_id=1&lt;/code&gt; and &lt;code&gt;size_id=10&lt;/code&gt;. The alternative &lt;code&gt;color_id=2&lt;/code&gt; and &lt;code&gt;size_id=20&lt;/code&gt; values do not appear in this response. If the interface needs them, the application must make additional queries and merge the results.&lt;/p&gt;

&lt;p&gt;In Elasticsearch and OpenSearch, similar behavior can be built in one query, but each facet needs its own explicit aggregation branch. For &lt;code&gt;color&lt;/code&gt;, you keep &lt;code&gt;brand_id&lt;/code&gt; and &lt;code&gt;size_id&lt;/code&gt;; for &lt;code&gt;size&lt;/code&gt;, you keep &lt;code&gt;brand_id&lt;/code&gt; and &lt;code&gt;color_id&lt;/code&gt;; and so on. This works, but the query grows quickly, and bucket statuses still have to be calculated in the application.&lt;/p&gt;

&lt;p&gt;The main difference is where this logic lives. In Manticore Search, it is configured directly in the facet API; in other systems, it is usually assembled from several similar filter trees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance and limitations
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;strict&lt;/code&gt; remains the default and preserves the previous behavior. If you only need facets within the current result set, nothing needs to change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;auto&lt;/code&gt; is usually the better fit for e-commerce filters: it shows alternative values within each facet, marks selected values as &lt;code&gt;selected&lt;/code&gt;, and marks other values as &lt;code&gt;available&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;max&lt;/code&gt; when the interface needs value lists broader than the current result set and needs to show unavailable options. This mode costs more: Manticore Search calculates buckets over a broad scope and then determines their &lt;code&gt;status&lt;/code&gt; separately. This is worth considering with large datasets and many facets.&lt;/p&gt;

&lt;p&gt;There are also some limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local filter rewriting for a facet supports only attribute filters combined with &lt;code&gt;AND&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;complex &lt;code&gt;AND&lt;/code&gt;/&lt;code&gt;OR&lt;/code&gt; trees are not rewritten automatically for individual facets;&lt;/li&gt;
&lt;li&gt;selected values are currently matched reliably only for explicit value filters such as &lt;code&gt;=&lt;/code&gt; and &lt;code&gt;IN&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;statuses are not yet calculated automatically for ranges such as prices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For prices, discounts, and ratings, it is better to define separate ranges explicitly. For example, keep a numeric field for sorting and sliders, and add &lt;code&gt;price_band&lt;/code&gt; or &lt;code&gt;discount_band&lt;/code&gt; for the facet. This lets the interface show clear statuses for predefined ranges.&lt;/p&gt;

&lt;p&gt;SEO for faceted URLs, A/B tests, merchandising rules, and query analytics remain the responsibility of the application or platform around search.&lt;/p&gt;

&lt;p&gt;In practice, start with &lt;code&gt;auto&lt;/code&gt; when you need alternative values, and move to &lt;code&gt;max&lt;/code&gt; when the interface needs a broad bucket list with &lt;code&gt;selected&lt;/code&gt;, &lt;code&gt;available&lt;/code&gt;, and &lt;code&gt;unavailable&lt;/code&gt; statuses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;For an introduction to faceted search in Manticore Search, start with the earlier &lt;a href="https://manticoresearch.com/blog/faceted-search/" rel="noopener noreferrer"&gt;Faceted search&lt;/a&gt; article. It covers basic &lt;code&gt;FACET&lt;/code&gt; queries, sorting, limits, and facets over expressions.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://manual.manticoresearch.com/Searching/Faceted_search" rel="noopener noreferrer"&gt;&lt;code&gt;FACET&lt;/code&gt; documentation&lt;/a&gt; for details.&lt;/p&gt;

&lt;p&gt;For an interactive introduction to facets, take the &lt;a href="https://play.manticoresearch.com/faceting/" rel="noopener noreferrer"&gt;Manticore Faceting&lt;/a&gt; course.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Install Manticore Search with one command</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Wed, 29 Jul 2026 03:24:47 +0000</pubDate>
      <link>https://dev.to/sanikolaev/install-manticore-search-with-one-command-4cj5</link>
      <guid>https://dev.to/sanikolaev/install-manticore-search-with-one-command-4cj5</guid>
      <description>&lt;p&gt;There is now a one-line installer for Manticore Search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://manticoresearch.com | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is still a normal package-manager install. The script checks whether the machine uses APT, YUM/DNF, or Homebrew, configures the Manticore repository when needed, installs the &lt;code&gt;manticore&lt;/code&gt; package, and starts the service unless you tell it not to.&lt;/p&gt;

&lt;p&gt;Use it when you want to bring up Manticore Search quickly and do not need to walk through the package-manager steps by hand. If you prefer to control the repository setup, package install, service start, or deployment method yourself, the manual installation pages are still the right place to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  After it runs
&lt;/h2&gt;

&lt;p&gt;A successful run leaves Manticore installed through the system package manager. Upgrades, service management, uninstall, and host inspection use the usual system tools.&lt;/p&gt;

&lt;p&gt;By default, the installer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installs from the stable Manticore package repository&lt;/li&gt;
&lt;li&gt;installs the &lt;code&gt;manticore&lt;/code&gt; package&lt;/li&gt;
&lt;li&gt;starts the &lt;code&gt;manticore&lt;/code&gt; service&lt;/li&gt;
&lt;li&gt;keeps the default listeners on &lt;code&gt;9306&lt;/code&gt;, &lt;code&gt;9308&lt;/code&gt;, and &lt;code&gt;9312&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;writes an install log for troubleshooting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Check the service
&lt;/h2&gt;

&lt;p&gt;Once the service is started, a quick SQL-over-HTTP check is enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s2"&gt;"http://127.0.0.1:9308/sql?mode=raw"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"SELECT VERSION()"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use the MySQL client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-P9306&lt;/span&gt; &lt;span class="nt"&gt;-h0&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT VERSION()"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that returns a version, the package is installed and the service is answering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why add another install path?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://manticoresearch.com/install/" rel="noopener noreferrer"&gt;manual instructions&lt;/a&gt; are not going away. They are still better when you need Docker, Kubernetes, Windows, a pinned package-manager flow, or a deployment policy that does not allow remote shell scripts.&lt;/p&gt;

&lt;p&gt;The one-line installer covers the shorter path. Instead of asking the reader to pick between APT, YUM/DNF, and Homebrew first, it detects the package manager and runs the same standard setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debian or Ubuntu: add the APT repo, update metadata, install the package, start the service.&lt;/li&gt;
&lt;li&gt;RHEL-compatible distribution: install the repository RPM, install the package, start the service.&lt;/li&gt;
&lt;li&gt;macOS: use Homebrew, then start the service through &lt;code&gt;brew services&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Other environments: stop and point to the manual options.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small difference, but useful when you just want a running local or test instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the script does
&lt;/h2&gt;

&lt;p&gt;The installer is a shell script. If you want to read it first, the source is &lt;a href="https://github.com/manticoresoftware/manticoresearch/blob/main/installer/bootstrap-standalone.sh" rel="noopener noreferrer"&gt;&lt;code&gt;installer/bootstrap-standalone.sh&lt;/code&gt;&lt;/a&gt; in the Manticore Search GitHub repository.&lt;/p&gt;

&lt;p&gt;On a system it supports, it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;detects APT, YUM/DNF, or Homebrew&lt;/li&gt;
&lt;li&gt;checks that the CPU architecture is x86-64 or ARM64&lt;/li&gt;
&lt;li&gt;installs the Manticore repository package when needed&lt;/li&gt;
&lt;li&gt;installs the &lt;code&gt;manticore&lt;/code&gt; package&lt;/li&gt;
&lt;li&gt;starts the &lt;code&gt;manticore&lt;/code&gt; service by default&lt;/li&gt;
&lt;li&gt;checks the default listener ports before startup when &lt;code&gt;ss&lt;/code&gt; or &lt;code&gt;netstat&lt;/code&gt; is available&lt;/li&gt;
&lt;li&gt;writes an install log to &lt;code&gt;/var/log/manticore_install.log&lt;/code&gt; when possible, otherwise to &lt;code&gt;/tmp/manticore_installer.log&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default listener ports are &lt;code&gt;9306&lt;/code&gt;, &lt;code&gt;9308&lt;/code&gt;, and &lt;code&gt;9312&lt;/code&gt;. If one of them is already occupied, Manticore is not already running, and the system has &lt;code&gt;ss&lt;/code&gt; or &lt;code&gt;netstat&lt;/code&gt;, the installer fails instead of editing the configuration for you. If neither utility is available, the pre-check cannot run; service startup may still fail if a port is already taken.&lt;/p&gt;

&lt;p&gt;Port changes are left to you because changing them can affect clients, service files, and monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it applies
&lt;/h2&gt;

&lt;p&gt;The installer currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debian, Ubuntu, Linux Mint, and Debian-like systems using APT&lt;/li&gt;
&lt;li&gt;RHEL, CentOS, Fedora, Rocky Linux, AlmaLinux, Amazon Linux, and RHEL/Fedora-like systems using YUM or DNF&lt;/li&gt;
&lt;li&gt;macOS when Homebrew is installed&lt;/li&gt;
&lt;li&gt;x86-64 and ARM64 CPUs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The command shown above uses &lt;code&gt;curl&lt;/code&gt;. You can also fetch the script with &lt;code&gt;wget&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget &lt;span class="nt"&gt;-O-&lt;/span&gt; https://manticoresearch.com | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bash must be installed. The downloaded script starts under &lt;code&gt;/bin/sh&lt;/code&gt;, checks for Bash, and then runs the main installer payload with Bash. You also need the usual permissions to install packages and start services, either as root or through &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the operating system, package manager, or CPU architecture is not supported, the installer stops and prints a message pointing to the manual installation guide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default behavior
&lt;/h2&gt;

&lt;p&gt;With no extra options, the installer uses the stable release channel, installs the package, starts the service, and keeps the standard listener ports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stable packages are used unless you pass &lt;code&gt;dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the service starts unless you pass &lt;code&gt;no-start&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;listener ports stay at &lt;code&gt;9306&lt;/code&gt;, &lt;code&gt;9308&lt;/code&gt;, and &lt;code&gt;9312&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;existing data is not removed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other checks apply only in specific cases. Before an upgrade, the installer backs up configuration. It copies data directories only if you pass &lt;code&gt;backup-data&lt;/code&gt;. During &lt;code&gt;uninstall&lt;/code&gt; and &lt;code&gt;purge&lt;/code&gt;, it keeps data in place. If it finds an existing &lt;code&gt;searchd&lt;/code&gt; binary outside the package-managed install, it prints a warning.&lt;/p&gt;

&lt;p&gt;The destructive option is still separate: &lt;code&gt;purge-all&lt;/code&gt; removes packages, repository state, configuration, and data. Without non-interactive mode, it asks you to type &lt;code&gt;DELETE&lt;/code&gt; before removing &lt;code&gt;/etc/manticoresearch&lt;/code&gt; and &lt;code&gt;/var/lib/manticore&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common commands
&lt;/h2&gt;

&lt;p&gt;Most installer behavior is controlled by passing options after &lt;code&gt;sh -s&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install the latest stable package and start the service&lt;/span&gt;
curl https://manticoresearch.com | sh

&lt;span class="c"&gt;# Install but do not start the service&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; no-start

&lt;span class="c"&gt;# Upgrade an existing package-managed installation&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; upgrade

&lt;span class="c"&gt;# Upgrade and include the data directory in the backup&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; upgrade backup-data

&lt;span class="c"&gt;# Choose where upgrade backups are written&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; upgrade backup-data backup-dir /path/to/backups

&lt;span class="c"&gt;# List available stable versions&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; list-versions

&lt;span class="c"&gt;# List versions from the development repository&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; dev list-versions

&lt;span class="c"&gt;# Install or switch to a specific version without starting the service&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; version 25.0.0 no-start

&lt;span class="c"&gt;# Show installer help&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nb"&gt;help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specific version selection is for Linux package-manager installs. Homebrew installs and upgrades follow the Homebrew formula and do not support &lt;code&gt;version&lt;/code&gt; through this installer.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;dev&lt;/code&gt; option switches to the development repository. The default is the release channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI and non-interactive use
&lt;/h2&gt;

&lt;p&gt;For automation, use &lt;code&gt;silent&lt;/code&gt; or &lt;code&gt;yes&lt;/code&gt; only when you want the installer to run without prompts and accept the script's defaults:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; silent no-start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;silent&lt;/code&gt; and &lt;code&gt;yes&lt;/code&gt; are aliases for the same non-interactive mode. They do not mean "no output": the installer still writes progress and errors, but without interactive prompts and color. They also do not request an upgrade by themselves. If an existing installation should be upgraded, pass &lt;code&gt;upgrade&lt;/code&gt; explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; upgrade silent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be careful with cleanup commands in non-interactive mode. In the current installer, &lt;code&gt;silent&lt;/code&gt; also skips the "DELETE" prompt for &lt;code&gt;purge-all&lt;/code&gt;, so it can delete configuration and data without asking again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup options
&lt;/h2&gt;

&lt;p&gt;Uninstalling is split into levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove packages, keep configuration, data, and repository state&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; uninstall

&lt;span class="c"&gt;# Remove packages and the repository bootstrap package&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; purge

&lt;span class="c"&gt;# Remove packages, repository state, configuration, and data&lt;/span&gt;
curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; purge-all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;purge-all&lt;/code&gt; is the destructive option. Do not combine it with &lt;code&gt;silent&lt;/code&gt; or &lt;code&gt;yes&lt;/code&gt; unless you intentionally want to remove &lt;code&gt;/etc/manticoresearch&lt;/code&gt; and &lt;code&gt;/var/lib/manticore&lt;/code&gt; without a confirmation prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; purge-all silent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Removing software and deleting data are not the same operation, so the installer keeps those paths separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  If your policy forbids pipe-to-shell
&lt;/h2&gt;

&lt;p&gt;Some environments do not allow &lt;code&gt;curl | sh&lt;/code&gt;. That is fine. Use the &lt;a href="https://manticoresearch.com/install/" rel="noopener noreferrer"&gt;manual package-manager instructions&lt;/a&gt;, or review the script source first and run it only after inspection.&lt;/p&gt;

&lt;p&gt;For manual package-manager commands, Docker, Kubernetes, Windows, and separate packages, see the &lt;a href="https://manticoresearch.com/install/" rel="noopener noreferrer"&gt;Manticore Search installation guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>database</category>
      <category>devops</category>
      <category>search</category>
    </item>
    <item>
      <title>Manticore Search authentication rollout checklist for production</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Tue, 28 Jul 2026 06:15:49 +0000</pubDate>
      <link>https://dev.to/sanikolaev/manticore-search-authentication-rollout-checklist-for-production-5427</link>
      <guid>https://dev.to/sanikolaev/manticore-search-authentication-rollout-checklist-for-production-5427</guid>
      <description>&lt;p&gt;In production, "turn it on and done" almost never works. On a standalone node, the technical sequence is short: enable &lt;code&gt;auth&lt;/code&gt;, restart Manticore, create an admin user, and update clients. A topology with distributed tables or replication clusters needs extra preparation because nodes must authenticate to each other as well.&lt;/p&gt;

&lt;p&gt;Handle the rollout like a small release. Inventory clients and nodes, prepare the auth data, rehearse the procedure for your topology, and then cut over. A rehearsal exposes failures before the maintenance window.&lt;/p&gt;

&lt;p&gt;This checklist is for users who plan to enable authentication and want to roll it out as safely as possible in an existing system. Remember that authentication is disabled until you configure &lt;code&gt;auth&lt;/code&gt;; after cutover, clients that still omit credentials will fail.&lt;/p&gt;

&lt;p&gt;Choose the procedure by topology:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Topology&lt;/th&gt;
&lt;th&gt;Migration requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;standalone node&lt;/td&gt;
&lt;td&gt;bootstrap the first administrator after enabling &lt;code&gt;auth&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;distributed tables and remote agents&lt;/td&gt;
&lt;td&gt;distribute one canonical auth store before authenticated remote queries begin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;replication cluster&lt;/td&gt;
&lt;td&gt;prepare the persisted cluster user and auth store before startup, then recover the cluster in a controlled order&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Phase 1: inventory before changing anything
&lt;/h2&gt;

&lt;p&gt;Start by listing every client that connects to Manticore Search, including each application and independently deployed application component. Do this before editing the config.&lt;/p&gt;

&lt;p&gt;Common things to check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;application search frontends&lt;/li&gt;
&lt;li&gt;ingest workers&lt;/li&gt;
&lt;li&gt;cron jobs&lt;/li&gt;
&lt;li&gt;dashboards and BI tools&lt;/li&gt;
&lt;li&gt;support or admin tools&lt;/li&gt;
&lt;li&gt;schema change/update scripts&lt;/li&gt;
&lt;li&gt;backup and maintenance scripts&lt;/li&gt;
&lt;li&gt;local scripts run manually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each client, record something like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Client&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Tables or clusters&lt;/th&gt;
&lt;th&gt;Needed actions&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;product search app&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;products&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;will use Bearer token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;catalog ingest worker&lt;/td&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;products&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;uses password auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;schema migration job&lt;/td&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;products&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;run only during deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;access administrator&lt;/td&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;admin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;manages users and permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then confirm the deployment basics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this a standalone node, a distributed-table topology, a replication cluster, or a combination of them?&lt;/li&gt;
&lt;li&gt;Are you running RT mode or plain mode?&lt;/li&gt;
&lt;li&gt;In plain mode, where should the auth file live?&lt;/li&gt;
&lt;li&gt;Is &lt;code&gt;pid_file&lt;/code&gt; configured in the config? Bootstrap needs it.&lt;/li&gt;
&lt;li&gt;Are all participating nodes running a version that supports the same authentication protocol?&lt;/li&gt;
&lt;li&gt;Will SQL clients use SSL, and will HTTP clients use HTTPS when credentials cross a network?&lt;/li&gt;
&lt;li&gt;Where will credentials, including temporary Bearer tokens, be stored safely?&lt;/li&gt;
&lt;li&gt;Who is allowed to see the first admin password?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If replication is present, also record every cluster name and its persisted &lt;code&gt;user&lt;/code&gt; in each node's &lt;code&gt;&amp;lt;data_dir&amp;gt;/manticore.json&lt;/code&gt;. Back up the complete data directory, configuration, and any existing auth store before the rehearsal and again before production cutover.&lt;/p&gt;

&lt;p&gt;Do not skip the credential-handling questions. &lt;code&gt;CREATE USER&lt;/code&gt; returns a raw Bearer token; &lt;code&gt;TOKEN&lt;/code&gt; generates a new token for the specified user. &lt;code&gt;SHOW TOKEN&lt;/code&gt; later shows the stored token hash, not the raw token. If the raw token is lost, rotate it with &lt;code&gt;TOKEN&lt;/code&gt; and update the token in your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: set up least-privilege users
&lt;/h2&gt;

&lt;p&gt;Create users based on specific workloads, rather than granting permissions based on what someone might need later.&lt;/p&gt;

&lt;p&gt;Use a small matrix like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;User&lt;/th&gt;
&lt;th&gt;Permissions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;search frontend&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app_read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GRANT read ON 'products' TO 'app_read'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ingest worker&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app_ingest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GRANT write ON 'products' TO 'app_ingest'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;schema migration job&lt;/td&gt;
&lt;td&gt;&lt;code&gt;schema_job&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GRANT schema ON 'products' TO 'schema_job'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auth operator&lt;/td&gt;
&lt;td&gt;&lt;code&gt;security_admin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GRANT admin ON * TO 'security_admin'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;replication operator&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cluster_repl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GRANT replication ON 'posts' TO 'cluster_repl'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep in mind that &lt;code&gt;admin&lt;/code&gt; is a narrow permission. It only allows managing authentication and authorization state; it does not imply &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;schema&lt;/code&gt;, or &lt;code&gt;replication&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That matters because a person who manages credentials does not automatically need to read business data. A service that searches products does not need to write documents. A migration job does not need to manage users.&lt;/p&gt;

&lt;p&gt;Plan negative permission tests as well. For every user you create, choose at least one thing it should be able to do and one thing it should not be able to do.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app_read&lt;/code&gt; can search &lt;code&gt;products&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app_read&lt;/code&gt; cannot insert into &lt;code&gt;products&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app_ingest&lt;/code&gt; can write to &lt;code&gt;products&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app_ingest&lt;/code&gt; cannot manage auth.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schema_job&lt;/code&gt; can change schema for &lt;code&gt;products&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schema_job&lt;/code&gt; cannot read tables unless you grant &lt;code&gt;read&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Phase 3: test in staging
&lt;/h2&gt;

&lt;p&gt;Use a staging environment to rehearse the sequence of steps you plan to follow in production.&lt;/p&gt;

&lt;p&gt;In RT mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;searchd&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;data_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/lib/manticore&lt;/span&gt;
    &lt;span class="py"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
    &lt;span class="py"&gt;auth_log_level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;
    &lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To turn auth off explicitly in RT mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;searchd&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;data_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/lib/manticore&lt;/span&gt;
    &lt;span class="py"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;
    &lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In plain mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;searchd&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/lib/manticore/auth.json&lt;/span&gt;
    &lt;span class="py"&gt;auth_log_level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;
    &lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the auth file private. Before the first bootstrap, Manticore may create an empty auth file. After bootstrap, that file stores auth data and credential hashes.&lt;/p&gt;

&lt;p&gt;This bootstrap sequence works on a standalone node or on an isolated temporary daemon that prepares auth data for several nodes. Never start an existing replication data directory with an empty auth store. Its persisted cluster user will be missing, and Manticore may skip the cluster descriptor.&lt;/p&gt;

&lt;p&gt;Start &lt;code&gt;searchd&lt;/code&gt;, then bootstrap the first administrator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;searchd &lt;span class="nt"&gt;--config&lt;/span&gt; /etc/manticoresearch/manticore.conf &lt;span class="nt"&gt;--auth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For scripted setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'admin\nStrongPass#2026\nStrongPass#2026\n'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  searchd &lt;span class="nt"&gt;--config&lt;/span&gt; /etc/manticoresearch/manticore.conf &lt;span class="nt"&gt;--auth-non-interactive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Warning: The command above includes the password in plain text. In production automation, feed the three input lines to standard input from your secret-management system.&lt;/p&gt;

&lt;p&gt;Bootstrap creates the first administrator with all actions, including &lt;code&gt;replication&lt;/code&gt;; no additional grant is needed. The command does not return a bearer token. If the administrator needs HTTP Bearer access, connect as that user and run &lt;code&gt;TOKEN&lt;/code&gt;, or use the HTTP &lt;code&gt;POST /token&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;For a multi-node deployment, create the auth store once. Use a temporary daemon with its own empty data directory, PID file, and listeners. Bootstrap the administrator and shared service users, then stop the daemon cleanly. Copy the resulting auth store to every participating node before enabling authenticated node-to-node traffic. Do not recreate the same users independently: matching names and passwords can still produce different stored authentication material.&lt;/p&gt;

&lt;p&gt;Next, create staging users based on the notes from the previous phases. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'ReadPass#2026'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'app_ingest'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'IngestPass#2026'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;write&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'app_ingest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'schema_job'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'SchemaPass#2026'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'schema_job'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'security_admin'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'AdminPass#2026'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;admin&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'security_admin'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store the returned Bearer tokens in secure storage. Remember: raw tokens must not be left in logs, shell history, or unprotected files. If you need to rotate one, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SHOW TOKEN&lt;/code&gt; is not a way to recover the raw token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review users and permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;USERS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;PERMISSIONS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;PERMISSIONS&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run one allow and one deny test for every user. For the read-only user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;app_read_token&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:9308/sql?mode&lt;span class="o"&gt;=&lt;/span&gt;raw &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM products LIMIT 10"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify that an unauthorized operation is denied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;app_read_token&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:9308/sql?mode&lt;span class="o"&gt;=&lt;/span&gt;raw &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"INSERT INTO products(id,title) VALUES(1,'test')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this HTTP request, expect a &lt;code&gt;403 Forbidden&lt;/code&gt; response. Over SQL/MySQL, a permission denial returns &lt;code&gt;ERROR 1045&lt;/code&gt; with a permission-denied message.&lt;/p&gt;

&lt;p&gt;SQL clients should connect with a Manticore user name and password. The SQL/MySQL protocol in Manticore supports &lt;code&gt;mysql_native_password&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MYSQL_PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ReadPass#2026 &lt;span class="se"&gt;\&lt;/span&gt;
  mysql &lt;span class="nt"&gt;-h127&lt;/span&gt;.0.0.1 &lt;span class="nt"&gt;-P9306&lt;/span&gt; &lt;span class="nt"&gt;-uapp_read&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM products LIMIT 10"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP clients can use Basic authentication or Bearer tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-u&lt;/span&gt; app_read:ReadPass#2026 &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:9308/sql?mode&lt;span class="o"&gt;=&lt;/span&gt;raw &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM products LIMIT 10"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP authentication schemes (&lt;code&gt;Basic&lt;/code&gt;, &lt;code&gt;Bearer&lt;/code&gt;) are case-insensitive; user names are case-sensitive.&lt;/p&gt;

&lt;p&gt;If you edit the auth file outside the daemon during maintenance, reload it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;RELOAD&lt;/span&gt; &lt;span class="n"&gt;AUTH&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Phase 4: production rollout checklist
&lt;/h2&gt;

&lt;p&gt;Use this checklist for every deployment, then follow the procedure for your topology.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Confirm you have a current configuration and data-directory backup.&lt;/li&gt;
&lt;li&gt;[ ] Back up &lt;code&gt;manticore.json&lt;/code&gt; and the existing auth store separately.&lt;/li&gt;
&lt;li&gt;[ ] Confirm existing network protections stay in place.&lt;/li&gt;
&lt;li&gt;[ ] Confirm &lt;code&gt;pid_file&lt;/code&gt; is set in the config.&lt;/li&gt;
&lt;li&gt;[ ] Confirm where the auth file will be created or loaded from.&lt;/li&gt;
&lt;li&gt;[ ] Confirm password and token storage is ready.&lt;/li&gt;
&lt;li&gt;[ ] Confirm every participating node runs a compatible Manticore version.&lt;/li&gt;
&lt;li&gt;[ ] Rehearse the same topology and restart order in staging.&lt;/li&gt;
&lt;li&gt;[ ] Prepare one canonical auth store for users shared across nodes.&lt;/li&gt;
&lt;li&gt;[ ] Choose the standalone, distributed, or replication procedure below.&lt;/li&gt;
&lt;li&gt;[ ] Enable auth during a planned maintenance window.&lt;/li&gt;
&lt;li&gt;[ ] Expect unauthenticated clients to fail after auth is enabled.&lt;/li&gt;
&lt;li&gt;[ ] Store tokens in a protected secrets store immediately after they are issued. Do not store raw tokens in files, shell history, or logs.&lt;/li&gt;
&lt;li&gt;[ ] Update SQL connection code to send user names and passwords.&lt;/li&gt;
&lt;li&gt;[ ] Update HTTP connection code to use Basic authentication or Bearer tokens.&lt;/li&gt;
&lt;li&gt;[ ] Run the allow and deny tests from staging.&lt;/li&gt;
&lt;li&gt;[ ] Verify internal node-to-node operations when the deployment has remote agents or replication.&lt;/li&gt;
&lt;li&gt;[ ] Check the auth log.&lt;/li&gt;
&lt;li&gt;[ ] Run application stress tests that cover search, ingest, dashboards, and maintenance scripts.&lt;/li&gt;
&lt;li&gt;[ ] Rotate any temporary rollout credentials.&lt;/li&gt;
&lt;li&gt;[ ] Keep the first admin credential out of normal application use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Standalone node
&lt;/h3&gt;

&lt;p&gt;For a standalone node, the direct bootstrap sequence is sufficient:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stop Manticore cleanly and take the final backup.&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;auth&lt;/code&gt; and start &lt;code&gt;searchd&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Bootstrap the first administrator with &lt;code&gt;searchd --config &amp;lt;path&amp;gt; --auth&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create the production users and permissions.&lt;/li&gt;
&lt;li&gt;Update clients and run the planned allow and deny tests.&lt;/li&gt;
&lt;li&gt;Confirm that existing tables and known rows are still available.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Distributed tables and remote agents
&lt;/h3&gt;

&lt;p&gt;Distributed queries send remote-agent requests as the current session user. Each remote node must have the same stored authentication material for that user and grant the required permission on the remote table.&lt;/p&gt;

&lt;p&gt;For a new rollout across distributed nodes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the shared users once in the isolated bootstrap daemon from Phase 3.&lt;/li&gt;
&lt;li&gt;Stop the affected agents and masters for the coordinated cutover.&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;auth&lt;/code&gt; and place the same canonical auth store on every participating node. Preserve restrictive ownership and permissions, and compare checksums.&lt;/li&gt;
&lt;li&gt;Start remote agents before the masters that query them.&lt;/li&gt;
&lt;li&gt;Test a direct authenticated query on each agent, followed by the equivalent distributed query through the master.&lt;/li&gt;
&lt;li&gt;Create node-local users only after shared traffic works, and keep shared user records synchronized whenever their passwords, tokens, or permissions change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create shared users only once. Independently created accounts can have different stored authentication material even when their names and passwords match.&lt;/p&gt;

&lt;h3&gt;
  
  
  Existing replication cluster
&lt;/h3&gt;

&lt;p&gt;Moving an existing unauthenticated replication cluster to auth requires a coordinated restart. Do not enable &lt;code&gt;auth&lt;/code&gt; and bootstrap the first user against the existing cluster data. The empty store does not contain the persisted cluster user, so Manticore may skip the cluster descriptor.&lt;/p&gt;

&lt;p&gt;Use this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;While the unauthenticated cluster is healthy, choose its future replication identity and persist it:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;CLUSTER&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="s1"&gt;'cluster_repl'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;UPDATE user&lt;/code&gt; writes the name to the cluster metadata. Authentication is still disabled, so Manticore neither creates nor checks the account at this point. Create it in step 3, before restarting any real cluster node with authentication enabled.&lt;/p&gt;

&lt;p&gt;Verify that every node's &lt;code&gt;&amp;lt;data_dir&amp;gt;/manticore.json&lt;/code&gt; now stores &lt;code&gt;"user": "cluster_repl"&lt;/code&gt; for the cluster. If a node hosts several clusters, update each cluster to a user that will exist in the new auth store, or create and grant every persisted user before cutover.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Stop all cluster nodes cleanly and use the replication state to select the safe primary. After a clean shutdown, this is normally the node stopped last, with &lt;code&gt;safe_to_bootstrap: 1&lt;/code&gt; in its &lt;code&gt;&amp;lt;data_dir&amp;gt;/grastate.dat&lt;/code&gt;. See &lt;a href="https://manual.manticoresearch.com/Creating_a_cluster/Setting_up_replication/Restarting_a_cluster" rel="noopener noreferrer"&gt;Restarting a cluster&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bootstrap &lt;code&gt;cluster_repl&lt;/code&gt; as the first administrator in the isolated temporary daemon. The first administrator already has every action, including &lt;code&gt;replication&lt;/code&gt;, so it needs no additional grant. If the cluster will use a separate least-privilege identity, create it once and grant &lt;code&gt;replication&lt;/code&gt; before distributing the store.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stop the temporary daemon. Configure &lt;code&gt;auth&lt;/code&gt; on every real cluster node and copy the exact generated auth store to each one. Keep the files private and byte-identical. Do not start a real cluster node before this store is in place.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restart a two-node cluster in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start the non-primary peer normally and wait only for its daemon listener. Its cluster can report &lt;code&gt;closed&lt;/code&gt; at this point; do not write to it.&lt;/li&gt;
&lt;li&gt;Start the recorded safe primary with &lt;code&gt;--new-cluster&lt;/code&gt;, or use the corresponding &lt;code&gt;manticore_new_cluster&lt;/code&gt; service action.&lt;/li&gt;
&lt;li&gt;Wait for the safe primary to report &lt;code&gt;cluster_products_status=primary&lt;/code&gt; and &lt;code&gt;cluster_products_node_state=synced&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Stop the first peer cleanly and start it normally again. Wait for the same &lt;code&gt;primary&lt;/code&gt; and &lt;code&gt;synced&lt;/code&gt; values on that peer. Never use &lt;code&gt;--new-cluster&lt;/code&gt; on it.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The safe primary reads the persisted cluster user from a descriptor peer during startup, so the initial peer must already be listening. Starting the safe primary alone can fail with &lt;code&gt;failed to fetch donor user from any node&lt;/code&gt; even when the auth store is correct. For a larger cluster, rehearse the sequence in staging. Use one non-primary node as the initial metadata peer, establish the safe primary, and then start or restart the remaining peers normally.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On each node, check that the cluster component is primary, the local node is synchronized, and the pre-migration data is present:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;STATUS&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'cluster_products_status'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;STATUS&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'cluster_products_node_state'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;existing_table&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait until the two status values are &lt;code&gt;primary&lt;/code&gt; and &lt;code&gt;synced&lt;/code&gt;, respectively, before treating the node as writable.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before returning traffic, make a disposable one-row table and add it to the recovered cluster:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;migration_control&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;migration_control&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'post-auth control'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;CLUSTER&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="n"&gt;migration_control&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm that the peer returns one row from &lt;code&gt;products:migration_control&lt;/code&gt;. If this check fails after the pre-migration data checks passed, investigate table transfer rather than the migration itself.&lt;/p&gt;

&lt;p&gt;After the cluster is healthy, create the remaining administrative users and, if needed, a dedicated least-privilege replication user. Change the stored cluster identity only after the new user and its auth data are visible on every node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'repluser'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;strong-secret&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="n"&gt;replication&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'repluser'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;CLUSTER&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="s1"&gt;'repluser'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not remove the bootstrap administrator until another administrator and the final replication identity have both been verified.&lt;/p&gt;

&lt;p&gt;When a node joins an authenticated cluster, the donor's auth data replaces the joining node's local auth data. At &lt;code&gt;info&lt;/code&gt; or a more verbose auth log level, Manticore writes the previous data to &lt;code&gt;searchd.log.auth&lt;/code&gt; as a backup. The log can contain salts and credential hashes, so restrict access and redact it before sharing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication logging during cutover
&lt;/h3&gt;

&lt;p&gt;Authentication events are written to a separate auth log when authentication is enabled. If the daemon log is &lt;code&gt;/var/log/manticore/searchd.log&lt;/code&gt;, the auth log is &lt;code&gt;/var/log/manticore/searchd.log.auth&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;auth_log_level&lt;/code&gt; values are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;disabled&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;warning&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;info&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;all&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trace&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default is &lt;code&gt;info&lt;/code&gt;. Start there unless you have a reason to reduce or increase the logging detail. Use &lt;code&gt;trace&lt;/code&gt; only for diagnostics; it also includes all successful internal auth traffic.&lt;/p&gt;

&lt;p&gt;Useful cleanup and maintenance commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'NewReadPass#2026'&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;REVOKE&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SET PASSWORD&lt;/code&gt; changes the password used by SQL/MySQL and HTTP Basic auth. It does not revoke existing bearer tokens. To rotate Bearer access, create a new token with &lt;code&gt;TOKEN&lt;/code&gt; or &lt;code&gt;POST /token&lt;/code&gt; and update the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 5: rollback and troubleshooting
&lt;/h2&gt;

&lt;p&gt;For a standalone node, restore the previous configuration and network restrictions, restart Manticore, and revert the client configuration if necessary.&lt;/p&gt;

&lt;p&gt;Roll back all communicating nodes together. A mix of authenticated and unauthenticated nodes will not work. Restore the same configuration and auth state on each node, then restart remote agents before their masters.&lt;/p&gt;

&lt;p&gt;Keep the pre-cutover &lt;code&gt;manticore.json&lt;/code&gt; backup for every replication cluster. If a node started with auth before the persisted cluster user existed, stop it and compare the current descriptor with the backup. A clean stop may have saved the skipped state without the cluster descriptor; restore the backed-up descriptor before retrying. Do not recreate clustered tables or delete their data.&lt;/p&gt;

&lt;p&gt;Do not delete the rollout notes. They are usually the fastest way to see which client was updated, which token was stored where, and which permissions were created.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SQL access denied&lt;/td&gt;
&lt;td&gt;Wrong user, wrong password, or client auth mismatch&lt;/td&gt;
&lt;td&gt;Check the configured user and confirm the client can use &lt;code&gt;mysql_native_password&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 401&lt;/td&gt;
&lt;td&gt;Missing or invalid credentials&lt;/td&gt;
&lt;td&gt;Check the &lt;code&gt;Authorization&lt;/code&gt; header and whether the client uses Basic auth or Bearer token auth.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 403&lt;/td&gt;
&lt;td&gt;User authenticated but lacks permission&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;SHOW PERMISSIONS FOR '&amp;lt;user&amp;gt;'&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bearer token does not work&lt;/td&gt;
&lt;td&gt;Token was lost, copied incorrectly, or already revoked&lt;/td&gt;
&lt;td&gt;Run &lt;code&gt;TOKEN '&amp;lt;user&amp;gt;'&lt;/code&gt;, store the returned token, and update the client.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User has fewer permissions than expected&lt;/td&gt;
&lt;td&gt;Missing action grant&lt;/td&gt;
&lt;td&gt;Check whether the operation needs &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;schema&lt;/code&gt;, &lt;code&gt;replication&lt;/code&gt;, or &lt;code&gt;admin&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User has more permissions than expected&lt;/td&gt;
&lt;td&gt;Broad target or missing explicit deny&lt;/td&gt;
&lt;td&gt;Check wildcard grants, exact target grants, and any &lt;code&gt;WITH ALLOW 0&lt;/code&gt; rules.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distributed query is denied remotely&lt;/td&gt;
&lt;td&gt;Shared user is missing, differs, or lacks permission on the agent&lt;/td&gt;
&lt;td&gt;Compare the auth stores and permissions on the master and agent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existing cluster is skipped at startup&lt;/td&gt;
&lt;td&gt;Persisted cluster user is missing from the auth store or lacks &lt;code&gt;replication&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Check &lt;code&gt;manticore.json&lt;/code&gt;, &lt;code&gt;SHOW PERMISSIONS&lt;/code&gt;, and the pre-cutover backup before restarting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failed to fetch donor user from any node&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No descriptor peer is available, or daemon-to-daemon authentication failed&lt;/td&gt;
&lt;td&gt;Check the restart order, peer availability, and &lt;code&gt;searchd.log.auth&lt;/code&gt; on both nodes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Permission rules are determined by action type. When rules conflict, an explicit deny always takes precedence over an allow, even if the allow is more specific. If no matching allow exists, access is denied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final check
&lt;/h2&gt;

&lt;p&gt;Before calling the rollout done, confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] You used the procedure for the deployment's topology.&lt;/li&gt;
&lt;li&gt;[ ] Every isolated part of the system that uses Manticore Search has its own user.&lt;/li&gt;
&lt;li&gt;[ ] Every user has only the actions it needs.&lt;/li&gt;
&lt;li&gt;[ ] Users shared between nodes have the same stored authentication material.&lt;/li&gt;
&lt;li&gt;[ ] Bearer tokens are stored in a protected secrets store; raw tokens are not saved outside a controlled environment.&lt;/li&gt;
&lt;li&gt;[ ] The operations team knows that &lt;code&gt;SHOW TOKEN&lt;/code&gt; does not return the raw token, but shows its hash; use &lt;code&gt;TOKEN&lt;/code&gt; or the HTTP endpoint to get a new token.&lt;/li&gt;
&lt;li&gt;[ ] SQL and HTTP clients have been updated.&lt;/li&gt;
&lt;li&gt;[ ] Expected denials were tested.&lt;/li&gt;
&lt;li&gt;[ ] You tested distributed queries or replication operations when applicable.&lt;/li&gt;
&lt;li&gt;[ ] Every migrated replication cluster is &lt;code&gt;synced&lt;/code&gt;, and its existing data is present on every node.&lt;/li&gt;
&lt;li&gt;[ ] Auth logs are visible.&lt;/li&gt;
&lt;li&gt;[ ] The rollback procedure is clear and documented.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We wish you a smooth authentication and authorization rollout!&lt;/p&gt;

</description>
      <category>database</category>
      <category>infrastructure</category>
      <category>production</category>
      <category>security</category>
    </item>
    <item>
      <title>How to secure Manticore Search with built-in authentication and authorization</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Mon, 27 Jul 2026 05:54:33 +0000</pubDate>
      <link>https://dev.to/sanikolaev/how-to-secure-manticore-search-with-built-in-authentication-and-authorization-5f8d</link>
      <guid>https://dev.to/sanikolaev/how-to-secure-manticore-search-with-built-in-authentication-and-authorization-5f8d</guid>
      <description>&lt;p&gt;Search is often treated as infrastructure. In production, though, it behaves much more like an application API. It accepts user traffic, exposes business data, powers dashboards, and often sits close to records that shouldn't be open to every client on the network.&lt;/p&gt;

&lt;p&gt;Manticore Search now (since release &lt;a href="https://manticoresearch.com/blog/manticore-search-27-1-5/" rel="noopener noreferrer"&gt;27.1.5&lt;/a&gt; has built-in authentication and authorization for SQL over the MySQL protocol, HTTP/HTTPS endpoints, and replication-related operations.&lt;/p&gt;

&lt;p&gt;Authentication answers "who is calling?" Authorization answers "what is this user allowed to do?"&lt;/p&gt;

&lt;p&gt;Users who already work with Manticore can access the new functionality while keeping their familiar Manticore workflows. Existing SQL and HTTP clients retain their usual connection patterns. Applications require only minimal changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Manticore Adds
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;SQL/MySQL password authentication with &lt;code&gt;mysql_native_password&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;HTTP Basic authentication with the same user name and password.&lt;/li&gt;
&lt;li&gt;HTTP Bearer tokens for clients that shouldn't send a password on every request.&lt;/li&gt;
&lt;li&gt;Permissions for five clear actions: &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;schema&lt;/code&gt;, &lt;code&gt;replication&lt;/code&gt;, and &lt;code&gt;admin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Targets such as &lt;code&gt;products&lt;/code&gt;, &lt;code&gt;logs_*&lt;/code&gt;, &lt;code&gt;posts&lt;/code&gt;, and &lt;code&gt;*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;SQL commands for managing users, tokens, and permissions.&lt;/li&gt;
&lt;li&gt;Auth logging with &lt;code&gt;disabled&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;warning&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;all&lt;/code&gt;, and &lt;code&gt;trace&lt;/code&gt; levels. The default is &lt;code&gt;info&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The access model is deliberately designed small. You just create users, grant the actions they need, update your app to send credentials, and test that denied operations are actually denied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable Authentication
&lt;/h2&gt;

&lt;p&gt;Authentication is controlled by the &lt;code&gt;auth&lt;/code&gt; setting in the &lt;code&gt;searchd&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://manual.manticoresearch.com/Read_this_first#Real-time-mode-vs-plain-mode" rel="noopener noreferrer"&gt;RT mode&lt;/a&gt;, use &lt;code&gt;auth = 1&lt;/code&gt;. Manticore stores auth data in &lt;code&gt;auth.json&lt;/code&gt; under &lt;code&gt;data_dir&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;searchd&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;data_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/lib/manticore&lt;/span&gt;
    &lt;span class="py"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
    &lt;span class="py"&gt;auth_log_level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To disable authentication explicitly in RT mode, use &lt;code&gt;auth = 0&lt;/code&gt; or remove the setting.&lt;/p&gt;

&lt;p&gt;In plain mode, set &lt;code&gt;auth&lt;/code&gt; to the auth file path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;searchd&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/lib/manticore/auth.json&lt;/span&gt;
    &lt;span class="py"&gt;auth_log_level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;info&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use SSL for SQL connections or HTTPS for HTTP clients when passwords or bearer tokens cross a network.&lt;/p&gt;

&lt;p&gt;Keep the auth file private. Before the first bootstrap, Manticore may create an empty auth file. After bootstrap, that file stores auth data and credential hashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrap the First Administrator
&lt;/h2&gt;

&lt;p&gt;After enabling &lt;code&gt;auth&lt;/code&gt;, start &lt;code&gt;searchd&lt;/code&gt;. Then create the first administrator with the same configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;searchd &lt;span class="nt"&gt;--config&lt;/span&gt; /etc/manticoresearch/manticore.conf &lt;span class="nt"&gt;--auth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For automation, use the non-interactive bootstrap mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'admin\nStrongPass#2026\nStrongPass#2026\n'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  searchd &lt;span class="nt"&gt;--config&lt;/span&gt; /etc/manticoresearch/manticore.conf &lt;span class="nt"&gt;--auth-non-interactive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bootstrap requires a running daemon. It creates the first administrator and grants that user all actions.&lt;/p&gt;

&lt;p&gt;It doesn't return a bearer token. If the admin user needs HTTP Bearer access, connect as that admin and run &lt;code&gt;TOKEN&lt;/code&gt;, or call the HTTP &lt;code&gt;POST /token&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Users
&lt;/h2&gt;

&lt;p&gt;After initializing the administrator, you can use the account to manage users and permissions through SQL commands. However, do not use it in the application. Instead, create separate users for specific tasks.&lt;/p&gt;

&lt;p&gt;For example, a search frontend that only reads from &lt;code&gt;products&lt;/code&gt; needs &lt;code&gt;read&lt;/code&gt;, not &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;schema&lt;/code&gt;, &lt;code&gt;replication&lt;/code&gt;, or &lt;code&gt;admin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'ReadPass#2026'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CREATE USER&lt;/code&gt; returns a raw bearer token for the new user. Store it immediately. Manticore won't show the raw token again.&lt;/p&gt;

&lt;p&gt;To create or rotate a bearer token later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TOKEN 'app_read'&lt;/code&gt; returns a new raw token that is ready to use. &lt;code&gt;SHOW TOKEN&lt;/code&gt; shows the stored token hash, not the token to send in an HTTP request. This is useful for verification and audit: you can confirm that a token exists or changed after rotation without exposing the raw secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SET PASSWORD&lt;/code&gt; changes the password used by SQL/MySQL and HTTP Basic auth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'NewReadPass#2026'&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It doesn't revoke existing bearer tokens. To rotate Bearer access, create a new token with &lt;code&gt;TOKEN&lt;/code&gt; or &lt;code&gt;POST /token&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="s1"&gt;'app_read'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An ingest pipeline can get &lt;code&gt;write&lt;/code&gt; without broader access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'app_ingest'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'IngestPass#2026'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;write&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'app_ingest'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A table schema migration job can get &lt;code&gt;schema&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'schema_job'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'SchemaPass#2026'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'products'&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'schema_job'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An auth administrator can get &lt;code&gt;admin&lt;/code&gt; privileges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'security_admin'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'AdminPass#2026'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;admin&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'security_admin'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;admin&lt;/code&gt; privileges only manage authentication and authorization state. They don't imply &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;schema&lt;/code&gt;, or &lt;code&gt;replication&lt;/code&gt;. Grant those separately when the same user really needs them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect with SQL and HTTP
&lt;/h2&gt;

&lt;p&gt;mysql clients authenticate with a Manticore user name and password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MYSQL_PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ReadPass#2026 &lt;span class="se"&gt;\&lt;/span&gt;
  mysql &lt;span class="nt"&gt;-h127&lt;/span&gt;.0.0.1 &lt;span class="nt"&gt;-P9306&lt;/span&gt; &lt;span class="nt"&gt;-uapp_read&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM products LIMIT 10"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTTP/HTTPS clients can use Basic authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-u&lt;/span&gt; app_read:ReadPass#2026 &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:9308/sql?mode&lt;span class="o"&gt;=&lt;/span&gt;raw &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM products LIMIT 10"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They can also use a bearer token returned by the &lt;code&gt;CREATE USER&lt;/code&gt;, &lt;code&gt;TOKEN&lt;/code&gt;, or &lt;code&gt;POST /token&lt;/code&gt; commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;app_read_token&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:9308/sql?mode&lt;span class="o"&gt;=&lt;/span&gt;raw &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM products LIMIT 10"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Basic&lt;/code&gt; and &lt;code&gt;Bearer&lt;/code&gt; authentication schemes are case-insensitive. User names are case-sensitive.&lt;/p&gt;

&lt;p&gt;The permission check is the same either way. The client authenticates, Manticore identifies the user, and checks the requested action against that user's permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing After Enabling Authentication
&lt;/h2&gt;

&lt;p&gt;Tip: when enabling access to Manticore, don't stop at confirming that the client can connect after authentication is enabled. Also test denied access and how your application reacts to it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;app_read&lt;/code&gt; user should be able to read from &lt;code&gt;products&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;app_read_token&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:9308/sql?mode&lt;span class="o"&gt;=&lt;/span&gt;raw &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM products LIMIT 10"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same user shouldn't be able to write to &lt;code&gt;products&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;app_read_token&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  http://127.0.0.1:9308/sql?mode&lt;span class="o"&gt;=&lt;/span&gt;raw &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"INSERT INTO products(id,title) VALUES(1,'test')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That error is expected. Over HTTP, a valid user without permission gets &lt;code&gt;403 Forbidden&lt;/code&gt;. Over SQL/MySQL, Manticore reports &lt;code&gt;ERROR 1045&lt;/code&gt; with a permission-denied message.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Manticore Checks What Is Allowed and Denied
&lt;/h2&gt;

&lt;p&gt;Manticore permissions are action and target rules. If no rule allows the requested action on the requested target, access is denied.&lt;/p&gt;

&lt;p&gt;The short version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rules are matched for the requested action only. &lt;code&gt;admin&lt;/code&gt; doesn't satisfy &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;schema&lt;/code&gt;, or &lt;code&gt;replication&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WITH ALLOW 0&lt;/code&gt; creates an explicit deny.&lt;/li&gt;
&lt;li&gt;Any matching explicit deny wins, including over more specific allow rules.&lt;/li&gt;
&lt;li&gt;If no matching allow exists, access is denied.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'analyst'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'AnalystPass#2026'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'analyst'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="s1"&gt;'private_logs'&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'analyst'&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;ALLOW&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;analyst&lt;/code&gt; user can read other tables, but can't read &lt;code&gt;private_logs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A wildcard deny plus an exact allow can't be used as an exception pattern. If the deny matches the request, it still wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roll Out Access on an Existing Deployment
&lt;/h2&gt;

&lt;p&gt;For an existing Manticore deployment, handle auth as a staged rollout rather than switching everything at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inventory SQL and HTTP clients that connect to Manticore.&lt;/li&gt;
&lt;li&gt;Choose the auth file path: &lt;code&gt;auth.json&lt;/code&gt; under &lt;code&gt;data_dir&lt;/code&gt; in RT mode, or an explicit path in plain mode.&lt;/li&gt;
&lt;li&gt;Enable auth in staging.&lt;/li&gt;
&lt;li&gt;Bootstrap the first administrator.&lt;/li&gt;
&lt;li&gt;Create least-privilege users for search, ingest, schema changes, replication, and auth administration.&lt;/li&gt;
&lt;li&gt;Update your application's SQL connection code to send user names and passwords.&lt;/li&gt;
&lt;li&gt;Update your HTTP/HTTPS connection code to use Basic authentication or Bearer tokens.&lt;/li&gt;
&lt;li&gt;Verify expected success and expected denial for each application user.&lt;/li&gt;
&lt;li&gt;Configure an appropriate auth logging level and make sure the log is stored safely with the rest of your operational logs.&lt;/li&gt;
&lt;li&gt;Roll out gradually, and after cutover rotate credentials for extra safety and an additional test and training pass.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Distributed tables need one extra check. Remote-agent queries authenticate as the current session user, so the remote daemon needs matching auth material for that user and the required permission on the remote target table.&lt;/p&gt;

&lt;p&gt;Replication clusters use the &lt;code&gt;replication&lt;/code&gt; action. When auth is enabled, cluster joins can replace local auth data with auth data from the donor cluster, so keep auth material consistent across nodes before joining. During cluster work, handle the auth log (by default, the &lt;code&gt;searchd.log.auth&lt;/code&gt; file) carefully because it can contain salts and credential hashes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full Reference
&lt;/h2&gt;

&lt;p&gt;The manual page covers the full command set and operational details for the new functionality, including password policy, auth logging levels, &lt;code&gt;SHOW USERS&lt;/code&gt;, &lt;code&gt;SHOW PERMISSIONS&lt;/code&gt;, &lt;code&gt;RELOAD AUTH&lt;/code&gt;, distributed remote agents, and replication clusters:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://manual.manticoresearch.com/Security/Authentication_and_authorization" rel="noopener noreferrer"&gt;Authentication and authorization manual&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But for most applications, the path is direct: enable auth, bootstrap an admin, create least-privilege users, update clients, and verify that the right requests are allowed while the wrong ones are denied.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>search</category>
      <category>security</category>
    </item>
    <item>
      <title>Meilisearch vs Manticore: Setting the Record Straight</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:41:44 +0000</pubDate>
      <link>https://dev.to/sanikolaev/meilisearch-vs-manticore-setting-the-record-straight-289p</link>
      <guid>https://dev.to/sanikolaev/meilisearch-vs-manticore-setting-the-record-straight-289p</guid>
      <description>&lt;p&gt;A few days ago, Meilisearch published &lt;a href="https://www.meilisearch.com/blog/meilisearch-vs-manticore" rel="noopener noreferrer"&gt;a comparison of Meilisearch and Manticore Search&lt;/a&gt;. We like comparisons — we &lt;a href="https://manticoresearch.com/blog/manticoresearch-vs-meilisearch/" rel="noopener noreferrer"&gt;published our own&lt;/a&gt; back in 2023, with benchmarks you can reproduce. And to be fair, parts of the Meilisearch's comparison are accurate and even generous toward Manticore.&lt;/p&gt;

&lt;p&gt;But several claims about Manticore are outdated or simply not what you observe when you run the current version. So instead of arguing, we did what we always do: spun up the latest versions of both engines (Manticore Search 28.4.4 and Meilisearch 1.41/1.48) and tested the claims. Every command and response below is real — feel free to copy-paste and check us.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Full-text search and real-time analytics" — that's maybe 1% of it
&lt;/h2&gt;

&lt;p&gt;The article introduces Manticore like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Manticore Search is an open-source search engine built as a continuation of Sphinx, improving its functionality with full-text search and real-time analytics.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sphinx already &lt;em&gt;was&lt;/em&gt; full-text search, and real-time analytics is a small slice of what's been added since. Here's what "improving its functionality" actually looks like over the years:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://manticoresearch.com/blog/rt_vs_plain_mode/" rel="noopener noreferrer"&gt;RT mode&lt;/a&gt;&lt;/strong&gt; — Sphinx had RT indexes, but now the whole engine runs without config files: &lt;code&gt;CREATE TABLE&lt;/code&gt;, insert, and search on the fly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://manticoresearch.com/blog/mike-replication/" rel="noopener noreferrer"&gt;Replication&lt;/a&gt;&lt;/strong&gt; — Galera-based synchronous cluster replication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A full HTTP JSON API&lt;/strong&gt; — nearly everything SQL can do, plus Elasticsearch-compatible bulk endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://manticoresearch.com/blog/create_table/" rel="noopener noreferrer"&gt;Auto-schema&lt;/a&gt;&lt;/strong&gt; — insert data without creating a table first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://manticoresearch.com/blog/vector-search-deep-dive/" rel="noopener noreferrer"&gt;Vector search&lt;/a&gt;&lt;/strong&gt; with &lt;a href="https://manticoresearch.com/blog/auto-embeddings/" rel="noopener noreferrer"&gt;auto-embeddings&lt;/a&gt;, &lt;a href="https://manticoresearch.com/blog/quantization/" rel="noopener noreferrer"&gt;quantization&lt;/a&gt;, and &lt;a href="https://manticoresearch.com/blog/knn-prefiltering/" rel="noopener noreferrer"&gt;filtered KNN&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://manticoresearch.com/blog/hybrid-search/" rel="noopener noreferrer"&gt;Hybrid search&lt;/a&gt;&lt;/strong&gt; — full-text + semantic fused with Reciprocal Rank Fusion in one query&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://manticoresearch.com/blog/new-fuzzy-search-and-autocomplete/" rel="noopener noreferrer"&gt;Fuzzy search&lt;/a&gt; and &lt;a href="https://manticoresearch.com/blog/autocomplete-the-predictive-search/" rel="noopener noreferrer"&gt;autocomplete&lt;/a&gt;&lt;/strong&gt; — typo tolerance, keyboard-layout awareness, suggestions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://manticoresearch.com/blog/mcl/" rel="noopener noreferrer"&gt;Columnar storage&lt;/a&gt;&lt;/strong&gt; and secondary indexes — for datasets larger than RAM&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://manticoresearch.com/blog/manticore-search-27-1-5/" rel="noopener noreferrer"&gt;Sharded tables, authentication, and conversational search&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://manticoresearch.com/blog/integration-with-kafka/" rel="noopener noreferrer"&gt;Kafka integration&lt;/a&gt;&lt;/strong&gt;, &lt;a href="https://manticoresearch.com/blog/kibana-demo/" rel="noopener noreferrer"&gt;Kibana&lt;/a&gt; and &lt;a href="https://manticoresearch.com/blog/grafana-dashboard-docker/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt; support, an &lt;a href="https://manticoresearch.com/blog/mcp-manticore-server/" rel="noopener noreferrer"&gt;MCP server&lt;/a&gt; for AI assistants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Sphinx plus real-time analytics" undersells that by a wide margin.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Complex setup" and "advanced customization"? Let's count commands
&lt;/h2&gt;

&lt;p&gt;The article's verdict on Manticore:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Best for: Large-scale projects and teams that need advanced customization, SQL familiarity, and high-performance querying at scale.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And among the cons: "Complex setup: Initial configuration and tuning can be time-consuming."&lt;/p&gt;

&lt;p&gt;Here is the entire "complex setup" of Manticore, from zero to searching — three commands, no config files, no schema:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install (and start — &lt;a href="https://manticoresearch.com/blog/one-line-installer/" rel="noopener noreferrer"&gt;the one-line installer&lt;/a&gt; launches the service too):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://manticoresearch.com | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Insert a document. Note there's no table yet — &lt;a href="https://manticoresearch.com/blog/create_table/" rel="noopener noreferrer"&gt;auto-schema&lt;/a&gt; creates it from the data:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; 0:9308/insert &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "table": "products",
  "doc": {"title": "yellow travel backpack", "price": 4990, "color": "yellow"}
}'&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8938969113712132097&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"created"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Search — full-text match plus a filter on a numeric field, immediately:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; 0:9308/search &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "table": "products",
  "query": {"bool": {"must": [
    {"match": {"*": "backpack"}},
    {"range": {"price": {"lt": 6000}}}
  ]}}
}'&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"took"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timed_out"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_relation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8938969113712132097&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1356&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yellow travel backpack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yellow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4990&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything is queryable the moment it lands in the table: text fields are full-text searchable — per-field too, e.g. &lt;code&gt;{"match": {"color": "yellow"}}&lt;/code&gt; — and attribute fields like &lt;code&gt;price&lt;/code&gt; are filterable, sortable, groupable, and facetable, with no declarations and no re-indexing. Here's the schema Manticore inferred on its own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------+--------+----------------+
| Field | Type   | Properties     |
+-------+--------+----------------+
| id    | bigint |                |
| title | text   | indexed stored |
| color | text   | indexed stored |
| price | uint   |                |
+-------+--------+----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the same task in Meilisearch (we tested both the &lt;code&gt;latest&lt;/code&gt; Docker tag, v1.41.0, and the newest release, v1.48.3). Meilisearch's installer downloads a binary into the current directory, which you then run yourself — and in production mode you'll also need to configure a master key before it starts. Adding documents and searching is just as easy as in Manticore — credit where due. But then you try to filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST localhost:7700/indexes/products/search &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"q": "backpack", "filter": "price &amp;lt; 6000"}'&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Index `products`: Attribute `price` is not filterable. This index does not have configured filterable attributes."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid_search_filter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid_request"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we were re-checking everything for this post anyway, here's precisely what does and doesn't work out of the box in Meilisearch — each point verified against the docs and a live instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Searching works immediately.&lt;/strong&gt; By default &lt;code&gt;searchableAttributes&lt;/code&gt; is &lt;code&gt;["*"]&lt;/code&gt; — every field of every document is searchable, no declarations needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filtering doesn't.&lt;/strong&gt; Fields must first be declared in &lt;code&gt;filterableAttributes&lt;/code&gt;. &lt;a href="https://www.meilisearch.com/docs/learn/filtering_and_sorting/filter_search_results" rel="noopener noreferrer"&gt;Meilisearch's documentation&lt;/a&gt; is upfront about the cost: &lt;em&gt;"This step is mandatory and cannot be done at search time. … Updating &lt;code&gt;filterableAttributes&lt;/code&gt; requires Meilisearch to re-index all your data, which will take an amount of time proportionate to your dataset size and complexity."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faceting doesn't either.&lt;/strong&gt; Facets are built on the same &lt;code&gt;filterableAttributes&lt;/code&gt; list, so the same declare-and-reindex step applies. In Manticore, &lt;a href="https://manticoresearch.com/blog/faceted-search-without-manual-filter-building/" rel="noopener noreferrer"&gt;faceted search needs no manual filter building&lt;/a&gt; at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sorting doesn't.&lt;/strong&gt; &lt;a href="https://www.meilisearch.com/docs/learn/filtering_and_sorting/sort_search_results" rel="noopener noreferrer"&gt;Per Meilisearch's docs&lt;/a&gt;: &lt;em&gt;"To allow your users to sort results at search time you must … add those attributes to the &lt;code&gt;sortableAttributes&lt;/code&gt; index setting."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tuning search fields re-indexes too.&lt;/strong&gt; The order of &lt;code&gt;searchableAttributes&lt;/code&gt; determines relevancy, so restricting or re-ordering it is a step most real projects take — and &lt;em&gt;"updating &lt;code&gt;searchableAttributes&lt;/code&gt; triggers a re-indexing of all documents in the index."&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is a bug — it's a design trade-off, and each step is one small API call. But it adds up to a model where you declare capabilities attribute by attribute and pay a full, dataset-proportional re-index every time you change your mind. Manticore's model is different: fields have &lt;em&gt;types&lt;/em&gt; — text fields are full-text searchable, attributes (numbers, strings, JSON) are filterable, sortable, groupable, and facetable, vectors are KNN-searchable — and every capability of a type is available from the moment of insert. There's no per-attribute capability registry to maintain, and no re-indexing to change how you query. Deep customization — ranking formulas, tokenization, morphology, per-field weights — is absolutely there (the article's pros list is right about that), but it's opt-in, not a prerequisite. Which is why we're not sure "needs advanced customization" is pointing at the right engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  "SQL familiarity" — optional, not required
&lt;/h2&gt;

&lt;p&gt;The article frames Manticore as the choice for "teams with SQL familiarity." SQL support &lt;em&gt;is&lt;/em&gt; a distinctive Manticore strength — you can talk to it with any MySQL client, &lt;code&gt;mysqldump&lt;/code&gt;, or a BI tool (a capability the comparison's own integrations table marks as "Not supported" in Meilisearch).&lt;/p&gt;

&lt;p&gt;But notice what you just read above: the entire quick-start demo was JSON over HTTP. Practically everything in Manticore is available both ways — SQL for those who like it, a JSON API very similar to what Meilisearch and Elasticsearch users are used to. You don't need to write a single line of SQL to use Manticore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vector search: Meilisearch's own table says it best
&lt;/h2&gt;

&lt;p&gt;This is our favorite part. The use-cases table in Meilisearch's article rates the two engines on vector search:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Vector search.&lt;/strong&gt; Meilisearch: Basic vector search. Manticore Search: Better suited for hybrid and advanced setups.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We agree with both columns. Here's what "hybrid and advanced setups" looks like in practice with &lt;a href="https://manticoresearch.com/blog/auto-embeddings/" rel="noopener noreferrer"&gt;auto-embeddings&lt;/a&gt; — no external embedding pipeline, no API keys, the model downloads automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;products_ai&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;vector&lt;/span&gt; &lt;span class="n"&gt;FLOAT_VECTOR&lt;/span&gt; &lt;span class="n"&gt;KNN_TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'hnsw'&lt;/span&gt; &lt;span class="n"&gt;HNSW_SIMILARITY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'l2'&lt;/span&gt;
    &lt;span class="n"&gt;MODEL_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'sentence-transformers/all-MiniLM-L6-v2'&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'title,description'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Insert documents as plain JSON — embeddings are generated for you (there's also a &lt;code&gt;/bulk&lt;/code&gt; endpoint for batches):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; 0:9308/insert &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "table": "products_ai",
  "id": 1,
  "doc": {
    "title": "green hiking backpack",
    "description": "Lightweight backpack suitable for hiking trails",
    "price": 5999
  }
}'&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; 0:9308/insert &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "table": "products_ai",
  "id": 3,
  "doc": {
    "title": "trail running shoes",
    "description": "Lightweight shoes with great grip for trails",
    "price": 7500
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run a &lt;a href="https://manticoresearch.com/blog/hybrid-search/" rel="noopener noreferrer"&gt;hybrid search&lt;/a&gt; — full-text and semantic search fused with RRF — in a single request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; 0:9308/search &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "table": "products_ai",
  "hybrid": {"query": "gear for a mountain hike"},
  "_source": ["title", "price"],
  "size": 2
}'&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_knn_dist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.88308269&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_hybrid_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.01639344&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"green hiking backpack"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5999&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_knn_dist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.09160841&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_hybrid_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.01612903&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"_source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"trail running shoes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7500&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No document mentions the word "gear" or "mountain" — that's semantic understanding out of the box, two requests total. Under the hood there's &lt;a href="https://manticoresearch.com/blog/quantization/" rel="noopener noreferrer"&gt;vector quantization&lt;/a&gt; to cut RAM usage, &lt;a href="https://manticoresearch.com/blog/knn-prefiltering/" rel="noopener noreferrer"&gt;KNN prefiltering&lt;/a&gt; so filters work &lt;em&gt;inside&lt;/em&gt; the HNSW traversal, and &lt;a href="https://manticoresearch.com/blog/knn-hnsw-performance/" rel="noopener noreferrer"&gt;continuous KNN performance work&lt;/a&gt;. "Better suited for hybrid and advanced setups" — yes, we'll take that.&lt;/p&gt;

&lt;p&gt;And you don't have to take our word for it — these live demos all run on Manticore: &lt;a href="https://catalog.manticoresearch.com/" rel="noopener noreferrer"&gt;catalog search&lt;/a&gt; with filters, facets, typo tolerance, and semantic search; &lt;a href="https://image.manticoresearch.com/" rel="noopener noreferrer"&gt;reverse image search&lt;/a&gt;; and &lt;a href="https://chat.manticoresearch.com/" rel="noopener noreferrer"&gt;conversational search&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relevance: measured, not argued
&lt;/h2&gt;

&lt;p&gt;Search-quality claims can be tested empirically. We benchmarked Manticore Search and Meilisearch in full-text, vector, and hybrid modes across 14 public IR dataset/corpus-size groups: seven BEIR datasets, ACORD, MS MARCO Passage at 250K and 8.8M documents, TREC DL 2019/2020, Wayfair's WANDS product-search dataset, and the typo-focused DL-Typo. For each engine/mode pair, we select its best completed run in each group and compute an unweighted average across the groups. The first row then selects each engine's best mode per group before averaging. Vector and hybrid runs use the same Qwen3-Embedding-8B model on both engines. Quality is scored with each dataset's canonical metric: NDCG@10, or MRR@10 for MS MARCO.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Manticore&lt;/th&gt;
&lt;th&gt;Meilisearch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Best mode per dataset (average)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.543&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.527&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.530&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.527&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.515&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.475&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full-text&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.420&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.237&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Comparing each engine's best result per group, Manticore leads on 11 of the 14. Vector quality is nearly tied at 0.530 and 0.527; both engines use the same embedding model, while their indexing and retrieval implementations still differ. The hybrid results show a wider difference, with Manticore's RRF averaging 0.515 against 0.475.&lt;/p&gt;

&lt;p&gt;Full-text has the largest gap: 0.420 against 0.237. The wider benchmark helps explain why. The engines using BM25 or a BM25-family ranker cluster well above Meilisearch in full-text relevance. &lt;a href="https://www.meilisearch.com/docs/learn/relevancy/ranking_rules" rel="noopener noreferrer"&gt;Meilisearch instead applies an ordered set of rules&lt;/a&gt; based on matching words, typos, proximity, attributes, and exactness. It therefore lacks BM25's combination of inverse document frequency, term-frequency saturation, and document-length normalization, which is the main reason it performs poorly on these standard IR datasets. Typesense, another engine in the wider comparison that does not use BM25, shows a similar pattern. Meilisearch's ten-word query limit can hurt longer queries further because the remaining words are ignored.&lt;/p&gt;

&lt;p&gt;Manticore also leads on DL-Typo, which focuses specifically on typo robustness.&lt;/p&gt;

&lt;p&gt;The same harness also tests Elasticsearch, OpenSearch, Qdrant, Typesense, Vespa, and Weaviate. Some engines could not complete the largest tests with the resources available on the benchmark server, so the report provides two coverage-controlled leaderboards. Recomputed over the ten-group subset covered by all eight engines, Manticore's best-mode average places second overall, behind Qdrant. Across all 14 groups, it also places second among the five engines with complete coverage. Meilisearch records a lower average in both comparisons.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://gist.github.com/manticoresearch/7c9b5d85ca8a62ce0066db6f0e17a11a" rel="noopener noreferrer"&gt;full comparison&lt;/a&gt; already includes coverage, per-dataset tables, and exact per-run configurations. This work is still in progress though. We'll publish the complete methodology and remaining details soon. We'll also release our new benchmarking tool as open source, making benchmarks like this much easier to run and extend.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Primarily used for complex, large-scale workloads" — the data says otherwise
&lt;/h2&gt;

&lt;p&gt;The article claims:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Manticore is primarily used for complex, large-scale search and analytics workloads that require high performance and deep customization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And in the use-cases table, Manticore "may be overkill for simple CMS needs."&lt;/p&gt;

&lt;p&gt;We checked our &lt;a href="https://manual.manticoresearch.com/Telemetry" rel="noopener noreferrer"&gt;anonymized telemetry&lt;/a&gt;. Among Manticore instances that report data-size metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;29%&lt;/strong&gt; hold less than &lt;strong&gt;1 MB&lt;/strong&gt; of data&lt;/li&gt;
&lt;li&gt;about &lt;strong&gt;half&lt;/strong&gt; hold less than &lt;strong&gt;100 MB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;only about &lt;strong&gt;one in eight&lt;/strong&gt; exceeds &lt;strong&gt;10 GB&lt;/strong&gt;, and fewer than &lt;strong&gt;1%&lt;/strong&gt; exceed &lt;strong&gt;1 TB&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So no — Manticore is &lt;em&gt;not&lt;/em&gt; primarily used for large-scale workloads. Most real-world Manticore installations are small projects: sites, catalogs, blogs, internal tools. Exactly the "simple CMS needs" the table warns you about. A Manticore container idles at under 200 MB of RAM, and as you saw above, a working search takes three commands — it's hard to call that overkill for anything.&lt;/p&gt;

&lt;p&gt;The nice part is that the &lt;em&gt;same&lt;/em&gt; engine also holds up at the other end: &lt;a href="https://manticoresearch.com/blog/manticore-search-at-scale-on-google-cloud/" rel="noopener noreferrer"&gt;Locally serves tens of thousands of queries per second on Manticore&lt;/a&gt; after migrating from Elasticsearch. Scaling down and scaling up aren't mutually exclusive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time analytics: we'll be honest here too
&lt;/h2&gt;

&lt;p&gt;Curiously, the same table gives Manticore a compliment we'd soften ourselves: "Designed for real-time data and analytics." Manticore has solid analytics capabilities — &lt;a href="https://manticoresearch.com/blog/mcl/" rel="noopener noreferrer"&gt;columnar storage&lt;/a&gt;, aggregations, &lt;a href="https://manticoresearch.com/blog/kibana-demo/" rel="noopener noreferrer"&gt;Kibana&lt;/a&gt; and &lt;a href="https://manticoresearch.com/blog/grafana-dashboard-docker/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt; integrations, &lt;a href="https://manticoresearch.com/blog/integration-of-manticore-with-logstash-filebeat/" rel="noopener noreferrer"&gt;log-pipeline integrations&lt;/a&gt; — and it's &lt;a href="https://manticoresearch.com/blog/kibana-demo/" rel="noopener noreferrer"&gt;well-suited for log search&lt;/a&gt;. But it is first and foremost a search engine. Analytics is one of its use cases, not what it's "primarily designed" for, and not what most of our users run it for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Meilisearch's article has a point
&lt;/h2&gt;

&lt;p&gt;We promised facts, and facts cut both ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Typo tolerance by default.&lt;/strong&gt; True: Meilisearch ships with typo tolerance on. In Manticore, &lt;a href="https://manticoresearch.com/blog/new-fuzzy-search-and-autocomplete/" rel="noopener noreferrer"&gt;fuzzy search&lt;/a&gt; is opt-in — &lt;code&gt;min_infix_len='2'&lt;/code&gt; in the table definition and &lt;code&gt;fuzzy=1&lt;/code&gt; at query time. Two small settings, but Meilisearch's default is the friendlier one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Querying straight from the frontend.&lt;/strong&gt; Meilisearch's search API keys make backend-less search a first-class pattern. Manticore has traditionally lived behind a backend; &lt;a href="https://manticoresearch.com/blog/manticore-search-27-1-5/" rel="noopener noreferrer"&gt;authentication arrived in 27.1.5&lt;/a&gt;, so that gap is closing, but today the point goes to Meilisearch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation gaps.&lt;/strong&gt; Every project has them, including us — and including Meilisearch. We'd just push back on "steep learning curve": if you know a bit of SQL &lt;em&gt;or&lt;/em&gt; a bit of JSON, you know enough to start, and there are &lt;a href="https://play.manticoresearch.com/" rel="noopener noreferrer"&gt;free interactive courses&lt;/a&gt; that walk you through everything from first table to vector search.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Client libraries: the table, completed
&lt;/h2&gt;

&lt;p&gt;Since this post is a response to &lt;a href="https://www.meilisearch.com/blog/meilisearch-vs-manticore" rel="noopener noreferrer"&gt;Meilisearch's comparison&lt;/a&gt;, let's fix its integrations table too. The original lists Go twice with contradictory answers ("supported via community" and "not supported"), gets several official/community statuses wrong — on both sides, in both directions — and skips two languages entirely. Here it is again, checked against &lt;a href="https://www.meilisearch.com/docs/learn/resources/sdks" rel="noopener noreferrer"&gt;Meilisearch's own SDK docs&lt;/a&gt; and &lt;a href="https://github.com/manticoresoftware" rel="noopener noreferrer"&gt;Manticore's GitHub org&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Integration&lt;/th&gt;
&lt;th&gt;Meilisearch&lt;/th&gt;
&lt;th&gt;Manticore Search&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JavaScript (Node.js)&lt;/td&gt;
&lt;td&gt;Official client (TypeScript-based)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/manticoresoftware/manticoresearch-javascript" rel="noopener noreferrer"&gt;Official JavaScript client&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript †&lt;/td&gt;
&lt;td&gt;Same official TypeScript client&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/manticoresoftware/manticoresearch-typescript" rel="noopener noreferrer"&gt;Official TypeScript client&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Official client (sync + async)&lt;/td&gt;
&lt;td&gt;Official &lt;a href="https://github.com/manticoresoftware/manticoresearch-python" rel="noopener noreferrer"&gt;sync&lt;/a&gt; and &lt;a href="https://github.com/manticoresoftware/manticoresearch-python-asyncio" rel="noopener noreferrer"&gt;asyncio&lt;/a&gt; clients ✱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;Official client&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/manticoresoftware/manticoresearch-php" rel="noopener noreferrer"&gt;Official PHP client&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Official client&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/manticoresoftware/manticoresearch-java" rel="noopener noreferrer"&gt;Official Java client&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;Official client ✱&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/manticoresoftware/manticoresearch-go" rel="noopener noreferrer"&gt;Official Go client&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;td&gt;Community-maintained client ✱&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/manticoresoftware/manticoresearch-rust" rel="noopener noreferrer"&gt;Official Rust client&lt;/a&gt; ✱&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elixir †&lt;/td&gt;
&lt;td&gt;Not listed in Meilisearch's SDK docs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/manticoresoftware/manticoresearch-elixir" rel="noopener noreferrer"&gt;Official Elixir client&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dart/Flutter&lt;/td&gt;
&lt;td&gt;Official client&lt;/td&gt;
&lt;td&gt;No official client (HTTP JSON API)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;.NET/C#&lt;/td&gt;
&lt;td&gt;Official client ✱&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/manticoresoftware/manticoresearch-net" rel="noopener noreferrer"&gt;Official .NET client&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;Core interface&lt;/td&gt;
&lt;td&gt;Full HTTP/JSON API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Native SQL (MySQL protocol)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;† — language missing from the original table • ✱ — status corrected vs the original table (for the Meilisearch column, per Meilisearch's current SDK docs)&lt;/p&gt;

&lt;p&gt;Adding it up across the ten languages above: &lt;strong&gt;Manticore ships official clients for nine&lt;/strong&gt; — everything except Dart/Flutter. &lt;strong&gt;Meilisearch ships official clients for eight&lt;/strong&gt; — everything except Rust (community-maintained) and Elixir (not listed). Both cover REST in full, and Manticore additionally speaks SQL over the MySQL protocol. So "a wide range of clients" — the one thing the original article does say about Manticore's integrations — is accurate; the details just needed refreshing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Meilisearch is a good search engine, and the article gets a real thing right: it's polished for the instant-search, frontend-first use case. If you're choosing for the long haul, though, weigh the cons Meilisearch's own article lists too — for instance, that a search query "must involve no more than ten words; any more will be ignored." Limits like that tend to matter more in month six than on day one.&lt;/p&gt;

&lt;p&gt;But the bigger issue is that the Manticore the article describes — a complex, SQL-only system for large-scale analytics teams — isn't the Manticore that exists in 2026, and most of it you can disprove in about five minutes of terminal time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://manticoresearch.com | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then insert a JSON document and search it (&lt;a href="https://manticoresearch.com/blog/one-line-installer/" rel="noopener noreferrer"&gt;more on the one-line installer&lt;/a&gt;). No schema, no config, no "advanced customization." If you want numbers rather than words, the relevance results above, our &lt;a href="https://manticoresearch.com/blog/manticoresearch-vs-meilisearch/" rel="noopener noreferrer"&gt;benchmark-backed comparison&lt;/a&gt;, and &lt;a href="https://db-benchmarks.com/" rel="noopener noreferrer"&gt;db-benchmarks.com&lt;/a&gt; are good places to start — and if we got anything wrong about Meilisearch here, we'll happily correct it. That's how we'd want to be treated, too.&lt;/p&gt;

&lt;p&gt;Questions or disagreements? Come tell us in &lt;a href="https://forum.manticoresearch.com/" rel="noopener noreferrer"&gt;the forum&lt;/a&gt; or &lt;a href="https://slack.manticoresearch.com/" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>opensource</category>
      <category>performance</category>
      <category>search</category>
    </item>
    <item>
      <title>Built-in authentication and authorization for Manticore Search</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Thu, 23 Jul 2026 04:57:42 +0000</pubDate>
      <link>https://dev.to/sanikolaev/built-in-authentication-and-authorization-for-manticore-search-4ec3</link>
      <guid>https://dev.to/sanikolaev/built-in-authentication-and-authorization-for-manticore-search-4ec3</guid>
      <description>&lt;p&gt;Search is no longer just a box on a website. In many deployments, Manticore Search behaves more like an internal data service: applications query it, ingest workers update its tables, dashboards read from it, and operators change schemas. When all of that relies only on network-level trust, the access-control story gets weak quickly.&lt;/p&gt;

&lt;p&gt;That is why Manticore Search now has built-in authentication and authorization. Introduced in the 27.x release line and published in the &lt;a href="https://manticoresearch.com/blog/manticore-search-27-1-5/" rel="noopener noreferrer"&gt;27.1.5 release post&lt;/a&gt;, it lets Manticore check who is connecting and what that user is allowed to do.&lt;/p&gt;

&lt;p&gt;The feature works across SQL, HTTP/HTTPS clients, distributed remote agents, and replication-related operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Manticore adds
&lt;/h2&gt;

&lt;p&gt;Manticore now supports authentication and authorization for the core access paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL/MySQL clients authenticate with a user name and password.&lt;/li&gt;
&lt;li&gt;HTTP clients can use Basic authentication or Bearer tokens.&lt;/li&gt;
&lt;li&gt;Permissions are granted by action and target.&lt;/li&gt;
&lt;li&gt;Auth events can be logged at configurable levels.&lt;/li&gt;
&lt;li&gt;Users and permissions are managed through dedicated SQL commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The authorization model uses five actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;read&lt;/code&gt; for search and read-only access.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;write&lt;/code&gt; for data changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;schema&lt;/code&gt; for table and schema management.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replication&lt;/code&gt; for cluster operations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;admin&lt;/code&gt; for authentication and authorization management.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Targets are plain names and wildcards, for example &lt;code&gt;products&lt;/code&gt;, &lt;code&gt;logs_*&lt;/code&gt;, or &lt;code&gt;*&lt;/code&gt;. That keeps the model close to how people already think about Manticore tables and clusters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes for operators
&lt;/h2&gt;

&lt;p&gt;It is common for a client application to be able to search a table without having permission to write to it. A data ingestion process may update documents without changing the schema. A schema migration task may run without being granted security administrator privileges. An operator can manage authentication and authorization without automatically gaining read access to business data.&lt;/p&gt;

&lt;p&gt;Those permission boundaries matter once search becomes a part of shared infrastructure. The point is not just to keep outsiders out; it is to keep each trusted client limited to what it actually needs.&lt;/p&gt;

&lt;p&gt;Manticore now gives those controls a native home. Users authenticate through the protocols they already use. Manticore checks permissions against actions and targets. Operators manage access with SQL commands. Applications keep their normal SQL or HTTP integration pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for SaaS and shared search
&lt;/h2&gt;

&lt;p&gt;SaaS and shared-service teams often need one search layer to serve several products, teams, or tenants. Some integrations are customer-facing, some are internal tools, and some run in the background. They should not all inherit the same level of trust.&lt;/p&gt;

&lt;p&gt;Before built-in auth, teams had to solve much of that outside Manticore. A common workaround was to put nginx in front of Manticore and rely on HTTP Basic authentication there. That could protect one entry point, but it did not give Manticore its own user and permission model. Now teams can model more of it directly where the data is served.&lt;/p&gt;

&lt;p&gt;That makes Manticore easier to use for customer-facing search over sensitive data, internal tools with different permission levels, and shared search clusters used by multiple services.&lt;/p&gt;

&lt;p&gt;Authentication and authorization are not a compliance program by themselves. Still, they are the kind of control reviewers expect to see. For GDPR, SOC 2, and internal security reviews, it helps to show named users, limited permissions, and authentication logs instead of a shared credential that can do everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollout notes
&lt;/h2&gt;

&lt;p&gt;Operators can enable authentication, bootstrap the first administrator, and then manage users and permissions with familiar SQL commands. Applications do not need a new Manticore-specific gateway just to start sending credentials. SQL clients use usernames and passwords. HTTP clients use Basic authentication or Bearer tokens.&lt;/p&gt;

&lt;p&gt;For existing deployments, treat the switch as a rollout, not just a config change. By default authentication is disabled until configured. Once it is enabled, clients that do not send credentials should fail, so update your application first and test expected denials before switching production traffic.&lt;/p&gt;

&lt;p&gt;For distributed or replicated topologies, plan the rollout carefully. Upgrade remote agents and replication peers first, then upgrade the masters that query or manage them, and enable auth only after the whole topology is on a compatible version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;p&gt;For a hands-on walkthrough, see the practical launch post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://manticoresearch.com/blog/how-to-secure-manticore-search-with-authentication-authorization/" rel="noopener noreferrer"&gt;How to secure Manticore Search with built-in authentication and authorization&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For existing deployments, see the rollout checklist:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://manticoresearch.com/blog/manticore-auth-migration-hardening-checklist/" rel="noopener noreferrer"&gt;Manticore Search authentication rollout checklist for existing deployments&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the full reference, see the manual page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://manual.manticoresearch.com/Security/Authentication_and_authorization" rel="noopener noreferrer"&gt;Authentication and authorization manual&lt;/a&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>search</category>
      <category>security</category>
    </item>
    <item>
      <title>Turbopuffer vs. Manticore Search on a couple of cheap VPS</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Thu, 16 Jul 2026 05:42:27 +0000</pubDate>
      <link>https://dev.to/sanikolaev/turbopuffer-vs-manticore-search-on-a-couple-of-cheap-vps-1c9</link>
      <guid>https://dev.to/sanikolaev/turbopuffer-vs-manticore-search-on-a-couple-of-cheap-vps-1c9</guid>
      <description>&lt;p&gt;Serverless vector databases are marketed on a simple promise: you don't run anything, you don't tune anything, and someone else worries about storage, scaling and uptime. &lt;a href="https://turbopuffer.com/" rel="noopener noreferrer"&gt;turbopuffer&lt;/a&gt; is one of the better examples of the category: a fast search engine built on object storage, used by Cursor, Notion, Linear and others.&lt;/p&gt;

&lt;p&gt;The promise is real, but it isn't free. So the obvious question is: for a small, well-defined workload, how much of that do you actually need, and what does the same workload cost and perform like on two cheap VPS running Manticore Search?&lt;/p&gt;

&lt;p&gt;This article answers that with numbers from an apples-to-apples benchmark on the same dataset.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workload
&lt;/h2&gt;

&lt;p&gt;We took a concrete, modest workload, the kind a lot of real applications actually have, and sized turbopuffer for it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dataset&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://storage.googleapis.com/ann-filtered-benchmark/datasets/dbpedia_openai_1M.tgz" rel="noopener noreferrer"&gt;DBpedia&lt;/a&gt; (OpenAI embeddings)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stored documents&lt;/td&gt;
&lt;td&gt;975k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector dimensions&lt;/td&gt;
&lt;td&gt;1536 (cosine similarity)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;turbopuffer latency P50 / P90 / P99 (advertised, warm namespace)&lt;/td&gt;
&lt;td&gt;14 / 17 / 27 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;turbopuffer estimated cost&lt;/td&gt;
&lt;td&gt;$75 / month (July 2026)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two turbopuffer numbers in that table are its advertised latency and its calculator-estimated cost, not measurements. The cost comes from turbopuffer's &lt;a href="https://turbopuffer.com/pricing" rel="noopener noreferrer"&gt;cost calculator&lt;/a&gt;, and the latency is turbopuffer's published target: specifically the warm-namespace P50/P90/P99 from its own benchmark:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0rjw5449igj4gi4wwitm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0rjw5449igj4gi4wwitm.png" alt="turbopuffer's advertised vector-search latency: warm namespace 14/17/27 ms vs cold namespace 874/1214/1686 ms" width="669" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two caveats come straight from that screenshot. First, those 14/17/27 ms are for a warm namespace; the same benchmark reports 874 / 1214 / 1686 ms for a cold one. A namespace that has aged out of cache is ~50–60× slower on its first queries, a direct consequence of turbopuffer's object-storage tiering. Second, turbopuffer's benchmark runs a different workload (1024 dimensions, 10M docs) than ours (1536 dimensions, 975k docs). turbopuffer's actually measured latency and throughput on our dataset are reported further down, and they differ from these advertised figures. Our benchmark dataset is 975k docs (DBpedia); both systems were loaded with the same data.&lt;/p&gt;

&lt;p&gt;That $75/month comes straight from turbopuffer's own cost calculator for 1536-dim vectors with no attributes (the calculator's "Attributes: None"). This matches our benchmark, which was pure vector search: no stored attributes and no metadata filtering on either side. Both engines support attribute filtering and full-text search (Manticore also adds &lt;a href="https://manual.manticoresearch.com/Searching/Hybrid_search" rel="noopener noreferrer"&gt;hybrid search&lt;/a&gt;); we simply didn't exercise it here, so the comparison is like-for-like on plain KNN. The pricing is usage-based, and the breakdown is the interesting part:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;th&gt;This workload&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;≤ $0.33 / GB&lt;/td&gt;
&lt;td&gt;1M docs (~3 GB)&lt;/td&gt;
&lt;td&gt;$1.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writes&lt;/td&gt;
&lt;td&gt;≤ $2.00 / GB&lt;/td&gt;
&lt;td&gt;10M writes, ~4 WPS (~61 GB)&lt;/td&gt;
&lt;td&gt;$61.44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queries&lt;/td&gt;
&lt;td&gt;≤ $1.00 / PB&lt;/td&gt;
&lt;td&gt;10M queries, ~4 QPS (~13 PB)&lt;/td&gt;
&lt;td&gt;$12.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Namespaces&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;100 × 10K docs&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;$75 / month (min $16 Launch plan)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Falam26mv6kv5ds64ksba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Falam26mv6kv5ds64ksba.png" alt="turbopuffer cost calculator for 1536-dim vectors with no attributes: $75/month, dominated by writes" width="800" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The headline number is almost entirely writes: $61.44 of the $75 is the cost of ingesting ~61 GB of vector data, while storage (3 GB) is a rounding error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkgbczbpxv4qcr50ai088.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkgbczbpxv4qcr50ai088.png" alt="Monthly cost breakdown: turbopuffer $75 (mostly writes) vs Manticore's flat $16.50" width="799" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "~13 PB" against the query line looks alarming but is a billing abstraction, not bandwidth. turbopuffer meters queries by "GB queried" with a minimum billable of 1.28 GB per query, so the calculator simply computes &lt;code&gt;10M queries * 1.28 GB ≈ 13 PB&lt;/code&gt;. The data actually moving per query is tiny (a 1536-dim float32 query vector is ~6 KB in, a top-10 result is a few KB out), yet every query on a namespace this small just bills at the 1.28 GB floor. At ≤$1/PB that whole 13 PB costs only $13, so query pricing is trivial here.&lt;/p&gt;

&lt;p&gt;This matters for the comparison because on a self-hosted engine none of these meters exist: writes are just CPU time, queries have no per-query floor, and there is no per-GB charge at all. (&lt;a href="https://turbopuffer.com/pricing" rel="noopener noreferrer"&gt;turbopuffer pricing&lt;/a&gt;, &lt;a href="https://turbopuffer.com/docs/pricing-log" rel="noopener noreferrer"&gt;pricing changelog&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  The contenders
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;turbopuffer&lt;/strong&gt; is serverless for the user: storage lives on S3/GCS/Azure object storage (~$0.02/GB), with hot data tiered through NVMe and RAM caches. You never provision or patch a server, never manage replication, never do capacity planning. You pay per GB stored / written / queried, and query compute runs on turbopuffer's managed infrastructure rather than on hardware you provision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manticore Search&lt;/strong&gt; is software you run yourself. For this comparison we used the cheapest realistic setup that still gives you redundancy:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nodes&lt;/td&gt;
&lt;td&gt;2 × Hetzner CX23 (region "eu-central") in a cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per VPS&lt;/td&gt;
&lt;td&gt;$8.25 / month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU per VPS&lt;/td&gt;
&lt;td&gt;2 cores&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk per VPS&lt;/td&gt;
&lt;td&gt;40 GB (table data uses ~6 GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Role of 2nd node&lt;/td&gt;
&lt;td&gt;Replica for redundancy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manticore total cost&lt;/td&gt;
&lt;td&gt;$16.50 / month (or $8.25 for a single node if you don't need redundancy)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Manticore stores vectors in an HNSW index (via the &lt;a href="https://github.com/manticoresoftware/columnar" rel="noopener noreferrer"&gt;Manticore Columnar Library&lt;/a&gt;) and supports scalar quantization (8-bit and 1-bit) with oversampling and rescoring to trade memory and speed against recall. For this test we used 1-bit (binary) quantization with &lt;code&gt;oversampling=2.0&lt;/code&gt; and rescoring, settings chosen specifically to land at roughly the same recall as turbopuffer, so &lt;strong&gt;the latency and throughput numbers are compared at equal quality&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line first
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;turbopuffer&lt;/th&gt;
&lt;th&gt;Manticore (2× cheap VPS)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly cost&lt;/td&gt;
&lt;td&gt;$75&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$16.50&lt;/strong&gt; (high-availability mode) / $8.25 (single node)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;recall@10 (5k queries)&lt;/td&gt;
&lt;td&gt;0.9622&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.9663&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query latency P50 / P90 / P99 (end-to-end)&lt;/td&gt;
&lt;td&gt;21 / 24 / 30 ms (incl. network round-trip)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;10 / 13 / 16 ms&lt;/strong&gt; (local, no network hop, fully compacted)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read (query) throughput ceiling&lt;/td&gt;
&lt;td&gt;~300 QPS unbatched (concurrency=8); &lt;strong&gt;~800–1,100 QPS&lt;/strong&gt; with query batching&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~330 QPS&lt;/strong&gt; (concurrency=2, unbatched)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sustained write throughput&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~1150 docs/s&lt;/strong&gt; (bulk load)&lt;/td&gt;
&lt;td&gt;490-1100 docs/s (bulk load)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops burden&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;None (fully managed)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You run &amp;amp; maintain it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On this workload, Manticore on two $8.25 VPS is ~4.5× cheaper, matches turbopuffer on recall, and, once the table is compacted (its steady state), is actually faster end-to-end (~10–13 ms vs turbopuffer's ~21 ms in-region), because a local query skips the network round-trip turbopuffer always pays. turbopuffer, in turn, reaches a higher query-throughput ceiling, but only with query batching (~800–1,100 QPS vs ~330 on two 2-core VPS); unbatched from a single machine the two are comparable (~300 QPS each). For a 4 QPS workload both have enormous headroom regardless. What you give up with Manticore is the "zero operations" part, which is the whole point of the rest of this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality: matched recall
&lt;/h2&gt;

&lt;p&gt;There's no point comparing latency or cost unless both systems return results of comparable quality. Using the &lt;a href="https://storage.googleapis.com/ann-filtered-benchmark/datasets/dbpedia_openai_1M.tgz" rel="noopener noreferrer"&gt;DBpedia&lt;/a&gt; dataset (1536-dim OpenAI embeddings, cosine similarity, 975k docs) and 5,000 queries:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;recall@10&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;turbopuffer&lt;/td&gt;
&lt;td&gt;0.9622&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manticore&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.9663&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two are within ~0.4 points of each other, close enough that the rest of the comparison is fair. Manticore reached this with &lt;a href="https://dev.to/blog/quantization/"&gt;binary quantization&lt;/a&gt; + &lt;code&gt;oversampling=2.0&lt;/code&gt; + rescoring: the binary vector representation is 32× smaller than float32, while rescoring recomputes final distances on the full-precision vectors for the top candidates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latency
&lt;/h2&gt;

&lt;p&gt;The number that matters is the one you actually observe: end-to-end, from issuing the query to getting the answer. turbopuffer is a remote service, so that includes the network round-trip to the namespace's region. Measured from a machine in the same region as the namespace (&lt;code&gt;gcp-europe-west3&lt;/code&gt;, same country as the query host):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;turbopuffer (in-region, end-to-end):   P50=21ms   P90=24ms   P99=30ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run your application farther from the region and this rises with the distance (easily 100 ms+ across continents), so co-locating the two matters, and you always pay at least the in-region round-trip.&lt;/p&gt;

&lt;p&gt;turbopuffer also reports two of its own internal timers: the time it spent running the search (~12 ms, and stable even under heavy load), and the total time on its side including requests waiting in line for an execution slot. We lean on those only to diagnose the throughput results below (the engine's own run-time stays fast under load), not as a latency figure, because any time a request waits is real latency you pay. And one point keeps the comparison honest: that ~12 ms is turbopuffer's own self-reported, engine-only time, excluding its internal queuing. Manticore has query profiling too, but we didn't isolate a single engine-only number equivalent to it; every Manticore figure here is end-to-end and already includes any internal queuing it does. So throughout this section we compare end-to-end against end-to-end, never turbopuffer's engine-only figure against Manticore's total.&lt;/p&gt;

&lt;p&gt;Manticore's latency (queried locally, so end-to-end with negligible network) under a mixed 4 QPS + 4 WPS load (10-minute run) depends heavily on how many disk chunks the table has been compacted into. A freshly bulk-loaded real-time table is split into many chunks; Manticore's auto-optimize merges them over time, and each KNN query has to consult every chunk, so fewer chunks means faster queries:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1sye9pkmc815qy4qoqv5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1sye9pkmc815qy4qoqv5.png" alt="Manticore query latency falls as the table compacts, crossing below turbopuffer's in-region band at ~2 chunks" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 32 / 16 / 8 / 4-chunk states above were produced deliberately, by optimizing to a fixed chunk count, to map out the latency curve. A real bulk load lands at far fewer: at our benchmark's batch=1000 the table settled at 7 chunks (1 insert thread) or 8 chunks (2 threads), so a freshly loaded table sits around the 8-chunk mark (~28 ms). The number you end up with is really a race between two processes: inserts flush RAM chunks to disk, and auto-optimize merges those disk chunks in the background. The faster you ingest, the faster new disk chunks pile up, so a high-throughput load (batch=1000) out-runs the background merger and leaves more chunks behind (7–8), while slow single-document inserts (batch=1) produce chunks slowly enough that auto-optimize keeps pace and the table ends at just 3 chunks, at a far lower ingest rate. So it's ingest rate, not concurrency per se, that drives the final chunk count. From whatever the load leaves behind, Manticore's auto-optimize then continues compacting toward 2 chunks (~13 ms avg).&lt;/p&gt;

&lt;p&gt;The takeaway: chunk count is the single biggest latency lever. At 32 chunks Manticore is several times slower than turbopuffer's ~21 ms end-to-end. But auto-optimized to its default floor of 2 chunks (~12–13 ms) it's already faster end-to-end than turbopuffer, and fully compacted to a single chunk (~10 ms) faster still, all on $8.25 hardware. The reason is mostly structural rather than a difference in engine speed: both do the core search in roughly the same ~10–12 ms, but Manticore runs on your own VPS and can be queried over localhost/LAN, so it pays no network round-trip, while turbopuffer always pays at least the ~9 ms in-region hop. The end-to-end gap is essentially that network hop.&lt;/p&gt;

&lt;p&gt;Read (query) latency, not write latency, tracks how compacted the table is, and keeping it compacted is Manticore's job, not yours. Auto-optimize runs in the background and merges chunks down to a target it maintains automatically (our table settled at 2 chunks). That target is a setting, not a chore: the &lt;a href="https://manual.manticoresearch.com/Creating_a_table/Local_tables/Plain_and_real-time_table_settings#optimize_cutoff" rel="noopener noreferrer"&gt;&lt;code&gt;optimize_cutoff&lt;/code&gt;&lt;/a&gt; option (per-table via &lt;code&gt;CREATE&lt;/code&gt;/&lt;code&gt;ALTER&lt;/code&gt;, or globally in &lt;code&gt;searchd&lt;/code&gt;) controls it, so if you want the very best latency you can set &lt;code&gt;optimize_cutoff=1&lt;/code&gt; and the background optimizer will keep the table at a single chunk for you, with no manual step in steady state. The only real catch is timing: right after a large bulk load the table is still at many chunks and queries are slower until auto-optimize catches up. In our run, with 8 chunks left after the concurrency=2 load, the background optimizer took about 15 minutes to compact down to 2 chunks, during which query latency steadily improved. turbopuffer has no equivalent step at all, which is part of what you're paying the premium for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Throughput
&lt;/h2&gt;

&lt;p&gt;Because each query costs the ~12 ms of engine time plus a network round-trip, a single connection is limited to roughly &lt;code&gt;1 / (engine + round-trip)&lt;/code&gt; queries per second, so throughput is recovered by issuing queries concurrently, and the in-region numbers below reflect that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;turbopuffer, in-region, plain single queries&lt;/strong&gt; (no batching). Adding concurrent connections lifts throughput, but only up to a point:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1kxy3yd5k94wlfx5bdao.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1kxy3yd5k94wlfx5bdao.png" alt="Unbatched throughput vs concurrency: both engines peak then decline (turbopuffer ~296 QPS at 8 connections, Manticore 329 at 2 connections)" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unbatched throughput peaks around 8 connections (~296 QPS) and then falls (233 QPS at 16, 153 at 32) while end-to-end latency climbs steeply (P90 33 → 108 → 423 ms at 8 → 16 → 32 connections). Through all of it, turbopuffer's own reported server-side time stays flat at ~12 ms. Because that reported time doesn't move while end-to-end latency rises, the extra delay isn't captured by turbopuffer's own timers: the bottleneck could be on our machine, the transport path, or some portion of request handling outside those timers, and this benchmark can't distinguish them. What it does show is that simply raising unbatched concurrency past 8 didn't improve throughput from this one machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz31e0wpqjgn1x28ib8gg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz31e0wpqjgn1x28ib8gg.png" alt="turbopuffer latency vs concurrency: end-to-end latency climbs past ~8 connections while turbopuffer's self-reported engine time stays flat at ~12 ms" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We therefore treat ~300 QPS as the observed unbatched ceiling from this single machine, not a documented turbopuffer service limit. (Whether more machines would push higher depends on what's actually saturating, a per-machine resource versus the shared path, which we didn't test.) The lever that took the same single machine well past ~300 was query batching.&lt;/p&gt;

&lt;p&gt;Query batching, packing several queries into one request, amortizes the network round-trip and lifts throughput much higher, with a clear quality-of-service gradient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~800 QPS cleanly&lt;/strong&gt;: concurrency=4 / batch=8 held 821 QPS with total server-side time P50=15 / P90=20 / P99=29 ms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~1,000–1,100 QPS with higher tail latency&lt;/strong&gt;: concurrency=8 / batch=8 reached 1,015 QPS but P99 rose to 58 ms; conc 4 / batch 16 hit 1,105 QPS at P99=61 ms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~1,170 QPS oversaturated&lt;/strong&gt;: concurrency=8 / batch=16 served 1,169 QPS, but the total time per request ballooned (P99=455 ms, avg ~70 ms) even though turbopuffer's reported server search time was still only ~12 ms, i.e. requests spent hundreds of milliseconds waiting in line on the server. Not a sustainable operating point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the practical batched ceiling we observed on this dataset is roughly ~800 QPS cleanly, rising to ~1,100 QPS with elevated server-side tails, comfortably above the two VPS either way.&lt;/p&gt;

&lt;p&gt;Three caveats on batching, though:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It only helps if you have many independent queries arriving at once&lt;/strong&gt; to bundle into a single request: a high-traffic search backend can, but a typical "one user, one query" path can't, so for many real apps the relevant number is the non-batched ceiling above (~300 QPS), not the ~1,000+ headline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The per-query latencies in batched mode are amortized, not wall-clock.&lt;/strong&gt; A query that has to wait for a batch to fill experiences the batch-formation delay plus the request's own latency, so end-user latency under batching is higher than the amortized figure suggests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It pushes complexity onto your application.&lt;/strong&gt; You have to collect queries, hold them briefly to form a batch, fan the combined response back out to the right callers, and reason about partial failures, often inconvenient enough that teams simply don't do it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Manticore, on the two 2-core VPS&lt;/strong&gt;, after the initial load, serving queries &lt;strong&gt;while also taking 4 WPS of writes&lt;/strong&gt; (queried locally, so no network round-trip):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concurrent connections&lt;/th&gt;
&lt;th&gt;Queries per second&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;154&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;329&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;287&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Manticore tops out around 329 QPS at 2 connections; beyond that you're oversubscribing the 2 cores, so 4 connections is slightly slower. Unlike turbopuffer's unbatched decline, this is a hard ceiling: it's the engine on 2 cores, and the only way past it is more cores or more nodes, which costs more money.&lt;/p&gt;

&lt;p&gt;These Manticore figures are unbatched, plain one-query-per-request, the same shape as turbopuffer's unbatched sweep above. Batching wouldn't lift Manticore's ceiling the way it does turbopuffer's: batching helps turbopuffer mainly by amortizing its ~9 ms network round-trip across many queries, but a local Manticore query has no such round-trip to amortize, and the real limit here is CPU on 2 cores, which batching doesn't relieve. So the fair like-for-like is unbatched-vs-unbatched, and there the two land close: &lt;strong&gt;turbopuffer ~296 QPS vs Manticore ~329&lt;/strong&gt;. What differs is what each number means: Manticore's ~329 is a hard 2-core ceiling, while turbopuffer's ~296 is simply where our single machine topped out. Adding concurrency only made it worse, and we couldn't localize the cause (turbopuffer's self-reported engine time stayed flat, but that's its own number, not something this benchmark can verify). So ~300 was the practical unbatched limit in our setup, and the proven path beyond it on turbopuffer was query batching (~800–1,100 QPS from the same machine); on Manticore it's more hardware.&lt;/p&gt;

&lt;p&gt;So on query throughput, &lt;strong&gt;turbopuffer's batched ceiling (~800–1,100 QPS, on its managed compute) is higher than two 2-core VPS (~330 QPS)&lt;/strong&gt;, &lt;strong&gt;while unbatched the two are comparable&lt;/strong&gt;, and turbopuffer's query pricing is cheap (the whole workload's query bill was ~$13). Manticore scales by buying hardware; turbopuffer scales on infrastructure you don't manage. The one nuance in Manticore's favor: at low concurrency &lt;strong&gt;Manticore is actually faster per connection (154 QPS at 1 connection vs turbopuffer's 40)&lt;/strong&gt;, because a local query has no ~9 ms network round-trip to pay.&lt;/p&gt;

&lt;p&gt;But for this workload the throughput axis is close to irrelevant. The target is 4 QPS, and both systems clear it by a huge margin: ~200× headroom for turbopuffer at clean latency, ~80× for Manticore. Throughput only becomes a deciding factor if you expect sustained query volume in the hundreds of QPS, at which point turbopuffer's higher batched ceiling is the safer bet and Manticore needs a bigger (pricier) cluster.&lt;/p&gt;

&lt;p&gt;On the write side the two land closer together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;turbopuffer&lt;/strong&gt; bulk load: 975k docs at ~1150 docs/s. This is the end-to-end rate we observed; what bounds it (its ingestion path, the network, or its own indexing) we can't tell from the outside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manticore&lt;/strong&gt; bulk load (batch=1000): 750–1100 docs/s until the first disk chunk is flushed. Here the bottleneck is visible: HNSW graph construction on 2 cores, since adding insert workers barely helps (451 → 490 docs/s from 1 → 2 threads).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ingestion in practice
&lt;/h2&gt;

&lt;p&gt;Loading the full DBpedia set into Manticore (975k docs, 1536 dims, building the HNSW graph) took:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;th&gt;Batch&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;th&gt;Wall time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;365 docs/s&lt;/td&gt;
&lt;td&gt;~45 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;451 docs/s&lt;/td&gt;
&lt;td&gt;~36 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;490 docs/s&lt;/td&gt;
&lt;td&gt;~33 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For comparison, turbopuffer loaded the same data at ~1150 docs/s, roughly 2.3x faster than Manticore on these two cheap VPS. That's expected: turbopuffer builds the index on its own (larger, managed) compute, while Manticore is doing the HNSW graph construction on the 2 inexpensive CPU cores. So turbopuffer wins clearly on raw ingest speed, though for a one-time initial load, the difference is ~14 minutes vs ~33–36 minutes, a one-off cost either way.&lt;/p&gt;

&lt;p&gt;After the initial load at batch=1000 the Manticore table sat at 7 disk chunks (1 insert thread) or 8 chunks (2 threads); adding insert workers doesn't help on a 2-core VPS. The final chunk count is driven by ingest rate rather than concurrency: inserts flush disk chunks while auto-optimize merges them in the background, so the faster you load, the more chunks the background merger is left behind by. A fast batch=1000 load leaves 7–8 chunks; slow single-document inserts (batch=1) produce chunks gently enough that auto-optimize keeps up and the table ends at only 3 chunks, but at a lower 365 docs/s instead of ~450–490. From there, Manticore's auto-optimize keeps compacting toward 2 chunks (its default target); lowering &lt;code&gt;optimize_cutoff&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; makes it maintain a single chunk, the lowest-latency state, automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage and headroom
&lt;/h2&gt;

&lt;p&gt;Manticore's table data for this workload is ~6 GB, against 40 GB of disk on each cheap VPS, so &lt;strong&gt;a single node has room for several times this dataset before you'd need a bigger plan&lt;/strong&gt;. On turbopuffer, storage is billed at ≤$0.33/GB and came to just $1.01 for this dataset, genuinely negligible. As the cost breakdown above shows, the difference between $75 and $16.50 is not about storage; it's the per-GB write charge ($61.44) that Manticore simply doesn't have.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you actually trade
&lt;/h2&gt;

&lt;p&gt;The numbers favor Manticore on cost and on latency when the table is compacted. But the comparison isn't only numbers: turbopuffer's price buys things that don't show up in a latency table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You give up (by self-hosting Manticore):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero operations.&lt;/strong&gt; turbopuffer has no servers to patch, no replica to fail over, no disk to outgrow. With Manticore, that's your job (though compaction itself is automatic, see below).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elastic scale and a higher throughput ceiling.&lt;/strong&gt; turbopuffer separates storage and compute and scales each independently; you can go from 1M to 1B docs without re-architecting, and its managed compute reached ~800–1,100 QPS with batching where two 2-core VPS cap at ~330. Two fixed VPS have a fixed ceiling: raising it means buying more hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale-to-zero economics.&lt;/strong&gt; For spiky or tiny workloads, usage-based billing can undercut even a $16.50/month fixed cost: you pay for what you query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A managed SLA&lt;/strong&gt; (uptime guarantees, support) on the higher tiers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You gain (with Manticore):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~4.5× lower cost&lt;/strong&gt; on this workload: a fixed, predictable bill with no per-GB write charge and no per-query metering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No network round-trip.&lt;/strong&gt; Querying over localhost/LAN avoids the round-trip a remote service always costs you (a ~9 ms floor even when co-located, more across regions), so a compacted table is faster end-to-end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full control and data locality&lt;/strong&gt;: your data on your servers, plus full-text search, filtering, &lt;a href="https://manual.manticoresearch.com/Searching/Hybrid_search" rel="noopener noreferrer"&gt;hybrid search&lt;/a&gt; and SQL in the same engine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security and data sovereignty&lt;/strong&gt;: your vectors and source documents never leave infrastructure you control, so nothing is sent to or stored by a third party. For teams with data-residency, GDPR, or confidentiality requirements that's often decisive: you own the encryption, network isolation, and access policy, and there's no external service to vet, trust, or breach. With a managed service the same data lives in the provider's cloud by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated compute on your own VPS&lt;/strong&gt;: fixed, predictable resources that aren't shared with other tenants.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For a large, elastic, or bursty workload, or for a team that simply doesn't want to operate a database, turbopuffer's $75/month (and the managed, serverless model behind it) is easy to justify.&lt;/p&gt;

&lt;p&gt;But for the very common case of a bounded workload (around 1M vectors, a few QPS/WPS) where someone is willing to run a couple of servers, two $8.25 VPS running Manticore deliver the same recall, comparable latency after auto-optimize (and better latency when compacted to one chunk, with no network round-trip), ample throughput headroom for the workload, and a ~4.5× lower bill. The catches are that you run and keep the cluster healthy, and that if you ever need many hundreds of QPS, turbopuffer's managed compute scales past two fixed cores.&lt;/p&gt;

&lt;p&gt;The serverless premium is real value, not a markup. The question this benchmark answers is just how much of that value a small, well-understood workload actually consumes, and the answer is: often, not $75 worth.&lt;/p&gt;

</description>
      <category>benchmarks</category>
      <category>vectordatabase</category>
    </item>
    <item>
      <title>Manticore Search 28.4.4: Faster KNN, better conversational search, easier installs and more faceting controls</title>
      <dc:creator>Sergey Nikolaev</dc:creator>
      <pubDate>Tue, 14 Jul 2026 11:24:55 +0000</pubDate>
      <link>https://dev.to/sanikolaev/manticore-search-2844-faster-knn-better-conversational-search-easier-installs-and-more-32pf</link>
      <guid>https://dev.to/sanikolaev/manticore-search-2844-faster-knn-better-conversational-search-easier-installs-and-more-32pf</guid>
      <description>&lt;p&gt;&lt;a href="https://manticoresearch.com/install/" rel="noopener noreferrer"&gt;Manticore Search 28.4.4&lt;/a&gt; has been released. This release brings faster KNN rescoring, more flexible conversational search, a simpler install and upgrade path, better faceting controls, per-table relevance defaults, and fixes across authentication, replication, SQL compatibility, distributed queries, and columnar/KNN internals.&lt;/p&gt;

&lt;p&gt;This post is a catch-up for everything shipped from &lt;strong&gt;27.2.0 through 28.4.4&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Upgrade Notes
&lt;/h2&gt;

&lt;p&gt;Please review these before upgrading:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;28.0.0 bumps the plugin ABI version &lt;code&gt;SPH_UDF_VERSION&lt;/code&gt; to 12.&lt;/strong&gt; External UDF, ranker, and token-filter plugin binaries must be rebuilt before loading them into this version. The change lets token-filter plugins receive long &lt;a href="https://manual.manticoresearch.com/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#dict" rel="noopener noreferrer"&gt;&lt;code&gt;dict=keywords_32k&lt;/code&gt;&lt;/a&gt; tokens instead of silently bypassing values above the old 126-byte plugin limit. Existing table data and configuration remain compatible, and no index migration is required. Downgrade is possible if you also restore plugin binaries built for the older ABI and do not rely on the new long-token plugin behavior. (&lt;a href="https://github.com/manticoresoftware/manticoresearch/issues/4667" rel="noopener noreferrer"&gt;Issue #4667&lt;/a&gt;, &lt;a href="https://github.com/manticoresoftware/manticoresearch/pull/4668" rel="noopener noreferrer"&gt;PR #4668&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If authentication is enabled, prioritize the 28.4.x update.&lt;/strong&gt; 28.4.2 fixes an authentication/authorization permission-check bypass in MySQL multi-statement execution. 28.3.4 also improves authenticated MySQL startup compatibility for Connector/J and PyMySQL, and 28.4.1 fixes authenticated replication-cluster restart recovery by persisting the stored replication user in cluster metadata. (&lt;a href="https://github.com/manticoresoftware/manticoresearch/pull/4713" rel="noopener noreferrer"&gt;PR #4713&lt;/a&gt;, &lt;a href="https://github.com/manticoresoftware/manticoresearch/issues/4691" rel="noopener noreferrer"&gt;Issue #4691&lt;/a&gt;, &lt;a href="https://github.com/manticoresoftware/manticoresearch/issues/4705" rel="noopener noreferrer"&gt;Issue #4705&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you manage MCL separately from the daemon, upgrade it together with Manticore.&lt;/strong&gt; This release includes &lt;a href="https://github.com/manticoresoftware/columnar" rel="noopener noreferrer"&gt;MCL&lt;/a&gt; 13.7.0, the new &lt;a href="https://manual.manticoresearch.com/Server_settings/Searchd#embeddings_threads" rel="noopener noreferrer"&gt;&lt;code&gt;embeddings_threads&lt;/code&gt;&lt;/a&gt; setting, and several columnar/KNN build, packaging, and cleanup fixes. Mixing an older library with a newer daemon is not recommended. (&lt;a href="https://github.com/manticoresoftware/columnar/pull/169" rel="noopener noreferrer"&gt;PR #169&lt;/a&gt;, &lt;a href="https://github.com/manticoresoftware/columnar/pull/186" rel="noopener noreferrer"&gt;PR #186&lt;/a&gt;, &lt;a href="https://github.com/manticoresoftware/manticoresearch/pull/4676" rel="noopener noreferrer"&gt;PR #4676&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Highlights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Faster KNN rescoring
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://manual.manticoresearch.com/Searching/KNN#KNN-vector-search" rel="noopener noreferrer"&gt;KNN search&lt;/a&gt; now batches distance calculations during the &lt;code&gt;rescore&lt;/code&gt; pass. After HNSW returns the candidate set, Manticore recomputes final full-precision distances and re-sorts the results. Batching that work reduces per-candidate overhead in the final stage of vector search.&lt;/p&gt;

&lt;p&gt;For vector-heavy workloads, this takes work out of the part of the query that runs after candidate selection. Results do not change; the final ranking pass just has less overhead when many candidates are rescored.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conversational search through SQL and HTTP
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://manual.manticoresearch.com/Searching/Conversational_search" rel="noopener noreferrer"&gt;Conversational search&lt;/a&gt; is now available through the &lt;code&gt;/search&lt;/code&gt; JSON API as well as SQL &lt;code&gt;CALL CHAT&lt;/code&gt;. That makes it easier to use Manticore's chat flow from applications that already talk to the HTTP API and do not want to add a separate SQL path just for chat requests.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CREATE CHAT MODEL&lt;/code&gt; also gained &lt;code&gt;custom_prompt&lt;/code&gt; support, so answers can follow application-specific instructions such as citation rules, tone, response length, or formatting. The feature is still built on the same Manticore Search flow: retrieve relevant documents from an existing vectorized table, build context, keep conversation history, and return an answer with supporting sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  One-line installation
&lt;/h3&gt;

&lt;p&gt;The quick-start install path is now simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://manticoresearch.com | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same installer can also upgrade an existing installation, list available versions, switch between stable and development repositories, and install a selected version. Package managers still remain the source of truth for installed files, repositories, services, and dependencies; the new path just removes the manual setup steps around them.&lt;/p&gt;

&lt;p&gt;For all options, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://manticoresearch.com | sh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nb"&gt;help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Facets can keep zero-count buckets visible
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://manual.manticoresearch.com/Searching/Faceted_search" rel="noopener noreferrer"&gt;Faceted search&lt;/a&gt; now supports zero-count facet buckets through SQL &lt;code&gt;ZEROES&lt;/code&gt; and JSON &lt;code&gt;"zeroes": true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is a small but important UI feature. In e-commerce-style filtering, you often want to keep an option visible even when the current filter combination gives it a count of &lt;code&gt;0&lt;/code&gt;. Combined with &lt;code&gt;max&lt;/code&gt;-mode facet behavior, zero-count buckets make it easier to show selected, available, and currently unavailable choices without hiding part of the filter vocabulary from the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better defaults for search relevance
&lt;/h3&gt;

&lt;p&gt;Manticore now supports &lt;a href="https://manual.manticoresearch.com/Creating_a_table/Local_tables/Plain_and_real-time_table_settings#profile" rel="noopener noreferrer"&gt;&lt;code&gt;CREATE TABLE ... profile='relevance'&lt;/code&gt;&lt;/a&gt;, plus stored per-table defaults for &lt;a href="https://manual.manticoresearch.com/Searching/Options#ranker" rel="noopener noreferrer"&gt;&lt;code&gt;ranker&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://manual.manticoresearch.com/Searching/Options#boolean_mode" rel="noopener noreferrer"&gt;&lt;code&gt;boolean_mode&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Based on our search quality tests, &lt;code&gt;profile='relevance'&lt;/code&gt; and the ranking settings it enables improve relevance in many cases. The application also no longer needs to repeat the same ranking parameters in every request.&lt;/p&gt;

&lt;h3&gt;
  
  
  More control over embedding CPU usage
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://manual.manticoresearch.com/Server_settings/Searchd#embeddings_threads" rel="noopener noreferrer"&gt;&lt;code&gt;embeddings_threads&lt;/code&gt;&lt;/a&gt; caps the CPU threads used for auto-embedding inserts, &lt;code&gt;ALTER TABLE ... REBUILD KNN&lt;/code&gt;, and text-to-vector KNN queries.&lt;/p&gt;

&lt;p&gt;This matters on shared hosts and mixed workloads. Embedding generation and KNN rebuilds can be CPU-heavy; a server-level cap makes those jobs easier to schedule without letting them take over the whole machine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug Fixes
&lt;/h2&gt;

&lt;p&gt;This release includes &lt;strong&gt;17 bug fixes&lt;/strong&gt;. The most important ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;28.4.3 fixed incompatible multi-statement handling around &lt;a href="https://manual.manticoresearch.com/Searching/Grouping#COUNT%28DISTINCT-field%29" rel="noopener noreferrer"&gt;&lt;code&gt;COUNT(DISTINCT ...)&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;28.4.2 fixed an authentication/authorization permission-check bypass in MySQL multi-statement execution, so each statement in a multi-statement request is validated correctly under auth.&lt;/li&gt;
&lt;li&gt;28.4.1 and 28.3.1 fixed replication-cluster recovery edge cases: authenticated cluster restart recovery and startup when a node was left alone in the cluster.&lt;/li&gt;
&lt;li&gt;Distributed queries with remote stored fields now fail on remote &lt;code&gt;GETFIELD&lt;/code&gt; fetch errors or malformed replies instead of returning apparently successful rows with empty or untrusted stored-field values.&lt;/li&gt;
&lt;li&gt;Interrupted columnar/KNN merge cleanup now removes temporary component files, preventing orphaned &lt;code&gt;.tmp.spc.*&lt;/code&gt; files from breaking later table rename, attach, or drop operations.&lt;/li&gt;
&lt;li&gt;DBeaver compatibility improved by accepting simple single-table aliases in &lt;code&gt;SELECT&lt;/code&gt; queries.&lt;/li&gt;
&lt;li&gt;Connector/J and PyMySQL clients can now complete authenticated native-password login flows, and harmless session &lt;code&gt;SET&lt;/code&gt; statements no longer fail under auth.&lt;/li&gt;
&lt;li&gt;The built-in Ukrainian lemmatizer now normalizes apostrophe words correctly, so forms such as &lt;code&gt;здоров'ям&lt;/code&gt; match &lt;code&gt;здоров'я&lt;/code&gt; under &lt;code&gt;lemmatize_uk_all&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Blended-keyword handling now honors the configured &lt;code&gt;blend_mode&lt;/code&gt;, restoring separator-stripped variants consistently for indexing and keyword extraction.&lt;/li&gt;
&lt;li&gt;RT auto-optimization no longer compacts a table below two disk chunks unless that lower cutoff is explicitly requested.&lt;/li&gt;
&lt;li&gt;A crash involving &lt;code&gt;percentiles&lt;/code&gt; aggregations together with terms aggregations in the same &lt;code&gt;/search&lt;/code&gt; request on multi-chunk RT tables was fixed.&lt;/li&gt;
&lt;li&gt;Long SQL parse errors now preserve UTF-8 character boundaries, so invalid queries containing Cyrillic and other multibyte text no longer produce truncated or corrupted error messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the complete list, see the &lt;a href="https://manual.manticoresearch.com/Changelog#Version-28.4.4" rel="noopener noreferrer"&gt;changelog&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Need help or want to connect?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Join our &lt;a href="https://slack.manticoresearch.com" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit the &lt;a href="https://forum.manticoresearch.com" rel="noopener noreferrer"&gt;Forum&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Report issues or suggest features on &lt;a href="https://github.com/manticoresoftware/manticoresearch/issues" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email us at &lt;code&gt;contact@manticoresearch.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>machinelearning</category>
      <category>news</category>
    </item>
  </channel>
</rss>
