У вас есть следующая структура базы данных: <br/> <pre><code class="sql">CREATE TABLE base
(
id BIGSERIAL PRIMARY KEY,
url text NOT NULL,
login text NOT NULL,
name text NOT NULL
);</code></pre> <br/> <br/> Каковы способы ускорения поиска по полям url, login и name? <br/> При этом необходимо отметить, что некоторые индексы не могут быть применены из-за превышения предельного размера строки индекса (3432) для btree версии 4, который составляет 2704 (например, индекс "idx_base_url") (SQLSTATE 54000). <br/> <br/> В базе данных есть записи в столбце url, содержащие слово login, такие как "cloudflare.com/login" и "login.test.com". Мне необходимо реализовать поиск по таким записям, при этом результат должен содержать не более 10,000 строк и быть получен за 10 секунд. <br/> <br/> P.S.: На бэкенде используется Golang. <br/> P.S.2: Кеширование данных не является вариантом, так как после поиска строки удаляются.
Чтобы определить размер базы данных и разобраться с методами ускорения поиска по полям `url`, `login` и `name`, необходимо учитывать несколько факторов.
### 1. Размер базы данных
Если у вас есть 10 000 строк и общий объем данных составляет 500 000 000 байт (или 500 МБ), то средний размер строки можно вычислить следующим образом:
- Общий объем данных: 500,000,000 байт
- Количество строк: 10,000
Средний размер строки = 500,000,000 / 10,000 = 50,000 байт (или ~50 КБ).
Это достаточно большой средний размер строки, учитывая, что в таблице несколько текстовых полей. Рекомендуется проверять конкретное содержимое полей, чтобы оценить, не содержит ли таблица пустых или сильно нагруженных данных, так как это может повлиять на производительность.
### 2. Методы ускорения поиска
Поскольку вы имеете дело с текстовыми полями и возможны проблемы с размером индекса, вот несколько способов ускорения поиска:
1. **Индексы с использованием пэтчей**:
- Если строки в поле `url` могут превышать размер индекса, рекомендуется рассмотреть использование префиксных индексов или триггеров, позволяющих обрезать строки до допустимого размера. Например, создание индекса на первые 255 символов:
```sql
CREATE INDEX idx_base_url_prefix ON base (url varchar(255));
CREATE INDEX idx_base_login_prefix ON base (login varchar(255));
CREATE INDEX idx_base_name_prefix ON base (name varchar(255));
```
2. **Полнотекстовый поиск**:
- Использование встроенного механизма полнотекстового поиска PostgreSQL. Можно создать `GIN`-индекс на поле `url` с типом данных `tsvector`:
```sql
ALTER TABLE base ADD COLUMN url_tsv tsvector;
UPDATE base SET url_tsv = to_tsvector(url);
CREATE INDEX idx_base_url_tsv ON base USING GIN(url_tsv);
```
- Для поиска:
```sql
SELECT * FROM base WHERE url_tsv @@ to_tsquery('login');
```
3. **Партиционирование таблиц**:
- Если размер данных продолжает расти, рассмотрите возможность партиционирования таблицы по некоторым критериям (дата создания записи, тип пользователя и т.д.), чтобы облегчить обработку подзапросов и объединений.
4. **Оптимизация структуры запросов**:
- При написании SQL-запросов используйте `EXPLAIN` для анализа плана выполнения запросов и находите узкие места в производительности.
5. **Избегайте `SELECT *`**:
- Вместо этого выбирайте только необходимые поля в запросах. Это уменьшает количество передаваемых данных и может ускорить выполнение.
6. **Наиболее подходящий алгоритм поиска**:
- Учитывайте возможности, такие как `ILIKE`, чтобы сделать поиск нечувствительным к регистру, если это требуется, хотя это может потребовать дополнительных индексов.
### 3. Условия выполнения поиска
Что касается ограничения в 10,000 строк и времени исполнения не более 10 секунд:
- Убедитесь, что индексы правильно настроены и обновляются.
- Оцените нагрузку на базу данных, используя инструменты мониторинга PostgreSQL, например, `pg_stat_statements`.
- На стороне Golang оптимизируйте время выполнения, используя асинхронные запросы или системы очередей, если следует ожидать блокировок.
Для выполнения всех этих рекомендаций, вам нужно тестировать и наблюдать за поведением базы данных под нагрузкой, вносить необходимые изменения и продолжать оптимизацию, исходя из конкретных потребностей вашей системы.
Запрос
SELECT * FROM base WHERE domain LIKE '%habr%' LIMIT 10000;
Какой это нахрен поиск "по совпадениям"??? Это, блин, поиск по подстроке!!! причём в любом месте строки.
С таким шаблоном поиска можете с обычными индексами вообще не трахаться, по причине абсолютной бессмысленности действа. Читайте про GIN, GiST, RUM и прочие виды индексов и поиск с их использованием.
Первое, что приходит в голову - разделить домен и URI. Тогда можно будет сначала поискать по индексу доменов, а потом уже эту выборку по урлу.
индекс по like %login% не будет применяться. нужно добавить колонку типа булево и partial index на неё. При вставке/обновлении и один раз при создании колонки - заполнить её значением true для нужных строк.
Избавляйся от like '%...%', это худший способ искать, он индексы не использует
даже поиск с регулярными выражениями работает лучше (правда там лимит на размер строки)
Если like используется чтобы искать части url, то храни в базе не целиком а разделенную по полям, в идеале чтобы поиск был на равенство (даже если база увеличится и станет сложнее, например тебе нужно искать по параметрам GET url, т.е. потребуется создать еще одну таблицу на них, скорость все равно будет хорошей)
А мне первое что приходит на ум, реализовать собственный словарик + табличку в которой будут указаны сопоставления ID слов из словарика и ID из таблицы с текстом в которых встречается данное слово.
Я так делал, когда на проекте поиск был по большим кускам текста, который был в BLOB-ах. Я разделял текст на слова по пробелам.
Использовали этот поиск редко, но когда запускали, он висел минут 10-20, люди уходили пить чай наверно :) и всех это устраивало. Но однажды этот поиск повесил БД, и эта проблема оказалась в поле моего зрения. После формирования словарика и добавления функций чтобы по новым записям так же добавлялась инфа в словарик, поиск стал занимать доли секунды :)
SELECT * FROM base WHERE domain LIKE '%habr%' LIMIT 10000;
проще всего так, наверно:
CREATE EXTENSION pg_trgm; CREATE INDEX trgm_domain_base_idx ON base USING GIST (domain gist_trgm_ops); EXPLAIN ANALYZE SELECT * FROM base WHERE domain LIKE '%habr%' LIMIT 10000;
https://www.postgresql.org/docs/current/pgtrgm.html