When a product catalog in 1C-Bitrix grows to 200 thousand items, the standard search stops coping — response time goes beyond 10 seconds. On one project with a catalog of 500 thousand products, search via LIKE took up to 18 seconds.
After implementing FULLTEXT with an ngram parser, the time dropped to 0.2 seconds. We conducted an audit, configured my.cnf, created indexes, and developed a custom component. Contact us to evaluate your project.
The standard Bitrix search works through the bitrix:search.page component and the search module. It builds its own index in the b_search_content table — all infoblock elements, pages, and forums go there.
The search runs via LIKE '%query%', which, with a volume of over 100 thousand elements, turns into a table scan with degradation to 5–15 seconds. The solution is MySQL full-text indexes (FULLTEXT) directly on infoblock tables or on b_search_content.
How FULLTEXT works in MySQL
A FULLTEXT index is built on top of text columns (CHAR, VARCHAR, TEXT). Queries are made via MATCH() AGAINST(). Two modes: IN NATURAL LANGUAGE MODE (ranking by relevance) and IN BOOLEAN MODE (support for operators: +required, -exclude, *prefix).
MySQL FULLTEXT limitations:
- Minimum word length by default:
innodb_ft_min_token_size = 3. Words shorter than this are not indexed. - Stop words (innodb_ft_server_stopword_table) — they need to be disabled or reconfigured.
- For the Russian language, proper encoding (utf8mb4) is required and, preferably, an external parser (ngram for InnoDB or Sphinx/Manticore as an alternative).
Choosing a Parser and Search Architecture
Ngram Parser for Cyrillic
The standard MySQL FULLTEXT parser is geared toward English: minimum word length of 3 characters, stop words. Cyrillic words are often shorter (prepositions, conjunctions), so they are not indexed.
The ngram parser splits text into bigrams (2 characters each) and is language-independent. This guarantees that any query of two or more characters will find matches. Setting ngram_token_size=2 in my.cnf enables bigram mode.
Using FULLTEXT Indexes
If search is needed only for the catalog, it is more efficient to index the b_iblock_element (NAME, DETAIL_TEXT) and b_iblock_section (NAME) tables directly. This reduces the load on b_search_content and simplifies the architecture.
However, for site-wide search it is more convenient to use b_search_content — it includes all content types.
Configuration and creation of indexes
Configuring my.cnf for Russian FULLTEXT
[mysqld] innodb_ft_min_token_size = 2 innodb_ft_enable_stopword = OFF ngram_token_size = 2
After the change, all FULLTEXT indexes must be recreated — simply restarting MySQL is not enough.
create a FULLTEXT index on b_search_content and infoblock tables
The b_search_content table is the central point of Bitrix search. Key columns: TITLE, BODY. For the catalog we index b_iblock_element with the SEARCHABLE_CONTENT column, which Bitrix fills automatically.
ALTER TABLE b_search_content ADD FULLTEXT INDEX ft_search_content (TITLE, BODY) WITH PARSER ngram;
ALTER TABLE b_iblock_element ADD FULLTEXT INDEX ft_iblock_element_search (NAME, SEARCHABLE_CONTENT) WITH PARSER ngram;
WITH PARSER ngram is a parser built into MySQL 5.7+ that splits text into bigrams/trigrams. It works well for Cyrillic and requires no external tools.
Monitoring and maintenance of indexes
Checking index state
-- Statistics of InnoDB FULLTEXT indexes
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
-- Forced rebuild
SET GLOBAL innodb_optimize_fulltext_only = ON;
OPTIMIZE TABLE b_search_content;
SET GLOBAL innodb_optimize_fulltext_only = OFF;
These queries help track the index population and rebuild it when necessary.
Developing a custom component for FULLTEXT
Overriding search in Bitrix
The standard bitrix:search.page does not use FULLTEXT — it works through Bitrix ORM with LIKE. To connect FULLTEXT, we override the query in a custom wrapper component or via the OnBeforeIBlockElementGetList event.
Minimal custom search via FULLTEXT:
namespace Local\Search;
class FulltextSearcher { private \Bitrix\Main\DB\Connection $db; public function __construct() { $this->db = \Bitrix\Main\Application::getConnection(); }
public function search(string $query, int $page = 1, int $limit = 20): array
{
$query = $this->sanitizeQuery($query);
$offset = ($page - 1) * $limit;
// BOOLEAN MODE with prefix search
$boolQuery = '+' . implode('* +', explode(' ', $query)) . '*';
$sql = "
SELECT sc.ID, sc.TITLE, sc.URL, sc.MODULE_ID, sc.ITEM_ID,
MATCH(sc.TITLE, sc.BODY) AGAINST (? IN BOOLEAN MODE) AS relevance
FROM b_search_content sc
WHERE MATCH(sc.TITLE, sc.BODY) AGAINST (? IN BOOLEAN MODE)
AND sc.SITE_ID = ?
AND sc.PUBLIC = 'Y'
ORDER BY relevance DESC
LIMIT ? OFFSET ?
";
$result = $this->db->query($sql, [$boolQuery, $boolQuery, SITE_ID, $limit, $offset]); $rows = []; while ($row = $result->fetch()) { $rows[] = $row; }
return $rows; } public function count(string $query): int { $boolQuery = '+' . implode('* +', explode(' ', $query)) . '*';
$result = $this->db->query(
"SELECT COUNT(*) AS cnt FROM b_search_content WHERE MATCH(TITLE, BODY) AGAINST (? IN BOOLEAN MODE) AND SITE_ID = ? AND PUBLIC = 'Y'",
[$boolQuery, SITE_ID]
);
return (int)$result->fetch()['cnt'];
}
private function sanitizeQuery(string $query): string
{
$query = preg_replace('/[+\-><()\~*"@]+/', ' ', $query);
$query = preg_replace('/\s+/', ' ', trim($query));
return mb_substr($query, 0, 255);
}
}
Custom search component
The component template uses FulltextSearcher instead of the standard module:
// /local/components/local/search.fulltext/component.php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
$query = trim($_GET['q'] ?? '');
if (mb_strlen($query) < 2) {
$this->arResult['ITEMS'] = [];
$this->arResult['TOTAL'] = 0;
$this->IncludeComponentTemplate();
return;
}
$searcher = new \Local\Search\FulltextSearcher();
$page = max(1, (int)($_GET['PAGEN_1'] ?? 1));
$this->arResult['ITEMS'] = $searcher->search($query, $page);
$this->arResult['TOTAL'] = $searcher->count($query);
$this->arResult['QUERY'] = htmlspecialchars($query);
$this->arResult['PAGE'] = $page;
$this->SetResultCacheKeys([]); // we do not cache search
$this->IncludeComponentTemplate();
Performance and parser comparison
Comparative metrics
| Method | Time for 100k records | CPU usage | Indexing |
|---|---|---|---|
LIKE %query% |
5–15 sec | 100% of one core | Not required |
| FULLTEXT (BOOLEAN) | 0.1–0.5 sec | 10–20% | Initial required |
| Parser | Minimum word length | Cyrillic support | Requires configuration |
|---|---|---|---|
| Standard | 3 characters | Partial | No |
| ngram (bigrams) | 2 characters | Full | ngram_token_size |
How to implement FULLTEXT in 5 steps
- Audit: measure the current search time, analyze the data volume.
- MySQL configuration: set
ngram_token_size=2, disable stopwords. - Creating indexes: run ALTER TABLE with FULLTEXT and the ngram parser.
- Component development: create a custom
FulltextSearcherand template. - Testing: load testing, "before/after" comparison.
What is included in the work
- Audit of the current search index, measurement of query time.
- Configuring MySQL:
ngram_token_size, disabling stopwords. - Creating FULLTEXT indexes on
b_search_contentand/or infoblock tables. - Developing a custom search component with FULLTEXT queries.
- Rebuilding the Bitrix search index (
BXSearch::reindex()). - Load testing with a "before/after" report.
- Providing documentation and migration scripts.
- Free support for one month.
Our experience in Bitrix development is over 10 years, we have successfully accelerated search on 50+ projects. Get a consultation — write to us, we will evaluate your project within 1 day.
According to the MySQL 8.0 documentation, the ngram parser ensures correct indexing of Cyrillic without external dependencies (MySQL Ngram Full-Text Parser).

