Telegram Web
Палантир. Часть 10. Полнотекстовый поиск.
#палантир@eshu_coding

В прошлом посте я демонстрировал, как работает полнотекстовый поиск в постгресе в зависимости от используемого словаря.

Словари имеют некоторые особенности. Дефолтный коряво отрезает окончания, не умеет в синонимы.

Тот, который я компилировал из исходников очень неплохо справляется с падежами и склонениями. Но при этом он бьёт имена собственные: человека по фамилии Медведев он превратит в медведя.

К тому же у него есть особенность: при попадании на разбор "слова" из 80 и более символов (что-то типа ахахахаха....) он пытается съесть 10^19 бит памяти, которых на сервере естественно нет. Ещё этот словарь крашит постгрес при попадании какого-нибудь экзотического символа, например иероглифа.

Проблемы больших слов и иероглифов я решил тупо регулярным выражением, которое вырезает некондицию из текста.

В итоге я не смог выбрать, что мне больше нравится - качественный поиск по обычным словам или возможность искать имена собственные - и оставил векторизироваться и индексироваться оба словаря.

Со временем надо будет дорабатывать словарь, расширяя функционал работы с падежами и склонениями на имена собственные.

Вообще, готовое решение для полностекстового поиска на русском языке есть встроенное в Postgres Pro, но он платный, лицензия около 200к рублей.

Впрочем, Yandex.Cloud предоставляет Postgrespro как сервис, примерно за 10 тыс рублей в месяц, потому лицензию покупать не обязательно, можно арендовать пробный сервер и поиграться с ним.
Палантир. Часть 11. Быстродействие поиска.
#палантир@eshu_coding

После запуска индексирования всего массива данных всплыла проблема быстродействия поиска: в тот момент в базе было около 600 млн сообщений (сейчас за 750) и поиск по ним работал, но мееедленно, минут 10-15 на запрос.

Перепробовал все варианты, которые предлагали на хабре, после чего совершенно случайно обнаружил у себя "небольшой" косяк.

Запросы проходят ту же процедуру очистки регулярными выражениями, что и тексты. Я поставил очистку прямо в запрос, не особо задумываясь, как было в примерах.

В ходе экспериментов я совершенно случайно обнаружил, что очистка над текстом запроса выполнялась для КАЖДОЙ проверяемой записи в БД отдельно.

Вынес очищенный текст запоса в отдельную переменную, которую стал использовать в поиске и случилась магия: поиск стал работать за десятки секунд.

После этого задействовал все трюки, которые вычитал на хабре, добившись скорости поиска в секунды.

Трюки состоят в следующем: поставить расширение, где добавлен особый индекс для полнотекстового поиска, результат запоса сортировать по "похожести" и возвращать верхнюю 1000 результатов.

В целом, получилось неплохо, через какое-то время запущу для подписчиков тестового бота. Проект, кстати, получил от уважаемого @ssleg название "Палантир", так и запишем:)
Палантир. Часть 12. Юзер интерфейс.
#палантир@eshu_coding

Тестировать поиск через запросы, как напрямую в БД, так и отправляемые через Postman на API - это прекрасно, но удобно только для разработчика, т.е. меня.

Как только проект обрёл признаки MVP - появилась необходимость подключать к тестированию других людей, а стало быть пришлось заняться пользовательским интерфейсом.

Вариант с отдельным сайтом я пока отмел: с фронтендом я не знаком от слова совсем, хотя со временем было бы неплохо познакомиться, остановился на боте для телеграма.

Ботов я раньше писал, и часть, связанная с тем, чтобы сделать использование бота УДОБНЫМ, да ещё и без глюков, всегда была кошмаром.

В этот раз по началу все шло также: все было глючным и неудобным. Но в какой-то момент мне стукнуло в голову: а что если представить бота как конечный автомат?

Конечный автомат - абстракция, в которой нечто, взаимодействующее с внешним миром, представляется в виде черного ящика, имеющего ограниченный набор состояний, правил перехода между ними и реакций на внешние раздражители, зависящих от текущего состояния.

С этой абстракцией я познакомился после какого-то заваленного собеседования, подумал "что за дичь? Мне оно не упёрлось" (тогда я пилил ботов, бгг). И только сейчас, спустя 8 месяцев я осознал, зачем всё это.

Я быстренько выделил 5 основных состояний у бота, за вечер описал каждое из них, не пытаясь засунуть в голову ВСЕ варианты сочетаний раздражителей и реакций одновременно, и с ходу получил MVP, не без глюков конечно, но без фатальных проблем.

Кстати, очень удобным для реализации поиска, оказалось использование gRPC в качестве протокола общения. Бот посылает одиночный запрос, сервер в ответ начинает стрим и передаёт результаты по мере нахождения, а бот и соответственно отображает. Для пользователя это ещё сильнее увеличивает быстродействие: первый результат появляется практически сразу, а что находится потом - долетает отдельно.

Всё это можно сделать и на отдельных запросах, но такой вариант выглядит как-то изящнее. На очереди развитие бота: база данных, уровни доступа, кеширование результатов поиска, более удобный интерфейс.

#кодинг
Палантир. Часть 13. Жизненный цикл приказов, эпизод черт знает какой.
#палантир@eshu_coding

Изначальная идея взаимодействия мастера и сборщиков заключалась в следующем:

Они ничего не знают о состояниях друг друга, сборщик просит приказ, мастер его выдает, если приказ подходит под возможности сборщика - он выполняется, если нет - возвращается назад.

После трех итераций и пары месяцев работы я пришел к выводу, что этот подход не годится. В какой-то момент подобное отношение к приказам приводило к тому, что сборщики начинали передавать туда-сюда невыполнимые приказы.

Решением стала полная перекройка логики. При работе клиент телеграма сохраняет "сессию" - данные для упрощения повторных логинов и повторных запросов к чатам и каналам, которые уже встречались аккаунту на просторах телеграма. В моем случае база централизовано хранится в отдельном сервере Postgres с помощью очень удобного инструмента, ссылку на который я обнаружил, читая документацию к telethon (да, я иногда это делаю:)).

У всех чатов и канало в базе добавилась колонка Finder, где содержится массив всех номеров телефонов сборщиков, "знакомых" с этим каналом. Сборщик, когда просит данные, "представляется" и сообщает, заблокированы ли у него тяжелые запросы.

Если может запрашивать новые чаты - мастер выдает приказ на запрос, если не может - выдается приказ подтянуть историю сообщений с одного из знакомых чатиков. В дополнение к трем очередям приказов: обычной, среднего и высокого приоритета, добавилось число очередей по количеству сборщиков.

У одного чата "Finder"-ов может быть много, хоть все сборщики сразу. Потому, во избежание размножения дублирующихся записей, пришлось распихивать приказы по всем соответствующим очередям одновременно и делать потокобезопасное поле-флаг, где отмечено состояние приказа. Если была попытка получить приказ, находящийся в состоянии отличном от "требуется исполнение" - приказ выбрасывается из очереди, а на его место забирается следующий.
Эшу быдлокодит
Палантир. Часть 12. Юзер интерфейс. #палантир@eshu_coding Тестировать поиск через запросы, как напрямую в БД, так и отправляемые через Postman на API - это прекрасно, но удобно только для разработчика, т.е. меня. Как только проект обрёл признаки MVP - появилась…
К вопросу о конченных конечных автоматах.

Сегодня, обсуждали с коллегой - программистом по образованию - довольно сложную штуку: работу аснхронности (в разрезе многопоточности) в c#. Про неё написаны книги, огромное количество статей на хабре.

Объяснять суть своими словами (особенно импровизируя в разговоре, а не читая подготовленную лекцию) можно долго.

- Знаешь что такое конечный автомат?
- Да.
- Ну вот всё приложение в целом представляется средой выполнения чем-то похожим. А используемые await-ы - границы между состояниями . И вот оно по ним щелк-щелк, используя потоки из пула.
- Аааа, понятно!
Палантир. Часть 14. Дубли в базе. Боль и страдания.
#палантир@eshu_coding

Одной из первых проблем были дубли в данных: одно и то же сообщение засасывалось более одного раза.

В какой-то момент я принял решение просто наплевать на них: ну есть у меня 15% дублирующихся данных, да и черт с ними. Но тут случился эпик фейл.

Запись данных в БД существенно опережала индексацию сообщения в поиске. В какой-то момент механизм защиты от дублей дал сбой и в базу поперли дубли. Некоторые сообщения дублировались по 20 раз.

Я решил проиндексировать вообще все сообщения: загрузка опережала поисковую индексацию на 250 млн сообщений. Удалил индекс со столба с полнотекстовым данными, подождал примерно сутки и решил запускать индексацию (часа 3-4) по моим оценкам.

Через 4.5 часа создание индекса рухнуло с ошибкой "слишком много дублей". И вот я остался с почти терабайтной базой с морем дублей и без рабочего поиска.

Может быть сделать тупое построчное удаление? Добавить сервис, который будет построчно брать данные из основной таблицы, искать к ним дубли и удалять их. Посчитал - обработка займет от 15 до 20 лет.

Нагуглил запрос, который угробит все дубли в таблице. Запустил. Спустя 6 часов PostgreSQL съел 8 Гб оперативки, 10 Гб файла подкачки, после чего ушёл в Out Of Memory.

Попробовал тот же запрос на маленьком кусочке данных - получилось, что в сумме дубли будут чиститься пару недель.

В итоге я пришел к довольно топорному решению: сделать отдельную таблицу, в которую вошли ключи недублирубщихся данных. От дублей - только один, первый встретившийся ключ.

А дальше запрос, который удаляет из исходной таблицы все строки, id которых нет во вспомогательной. Молотило оно больше суток, но мусор почистило.

Итого, в базе осталось около 550 млн уникальных сообщений. Дублей было около 250 тысяч (в какой-то момент закачка обновлений сошла с ума и выкачивала вместо обновления всю историю чата повторно в течение двух недель).

Всего было записано в таблицу около 1 млрд сообщений, часть из которых была угроблена предыдущими попытками уничтожения дублей.

В этот раз жизнь заставила меня сделать следующий шажок в глубину баз данных: научиться пользоваться планировщиком запросов.

Итого на борьбу с дублями я потратил дней пять. И хорошо, что это - мой личный проект.

Мораль сей басни такова: всплыла проблема - решай сразу, а не жди когда она выстрелит под нагрузкой на работающем продукте.

#postgresql
Наверное самое ценное в канале - когда более опытные программисты, наткнувшиеся на него, по прочитанному дают обратную связь - что и как можно было бы сделать лучше/оптимальнее.

Спасибо за обратную связь!
Палантир. Часть 15. Окончательное решение вопроса дублей.
#палантир@eshu_coding

Несмотря на локальную победу над дублями, они продолжали потихоньку попадать в базу.

В далёкие времена, когда я проектировал базу данных, я собирался использовать составной primary key в таблице с сообщениями: временная метка сообщения, id чата и порядковый номер сообщения. Но что-то пошло не так. Стандартный способ защиты таблицы с помощью триггера "before insert" не годился на секционированной таблице.

Делать проверку внутри хранимой процедуры для записи оказалось медленно: проверка занимает около 10 мс, на каждое из примерно 1000 сообщений, прилетающих каждую секунду. База мигом захлёбывается.

В итоге я забил на дубли, к чему это привело можете судить по прошлым постам.

Спасибо доброму человеку @vekhden_speak, он подсказал решение, с помощью которого я окончательно поборол дубли. Как окалось, в самом insert-e можно предотвратить конфликт добавив предложение "on conflict on constraint messages_pkey do nothing".

За сутки вычистив базу от остатков дублей, я дописал хранимую процедуру для записи данных в БД, после чего перешёл к изначальной идее ключей. Вот теперь дубли изжиты окончательно.

#postgresql
Палантир. Часть 16. Клиентская часть для пользователей.
#палантир@eshu_coding

Работа над сборщиком данных в телеграме подошла к финальной стадии:
доделал mvp (минимально жизнеспособный продукт) части для пользователей, на выходе получилось два типа ботов:

Поисковик по телеграму @palantir_search_bot
Сервис оповещений @space_observer_bot

Поисковик просто ищет по скачанной истории сообщений за выбранный интервал времени, выплевывая 1000 самых похожих на запрос результатов.

Сервис оповещений проверяет все входящие сообщения (не старше 6 часов), и если они совпадают с заранее введенным запросом - оповещает пользователей.

Пока что реализована только демо версия, которая отрабатывает по следующему запросу:
'(илон <-> маск) | рогозин | космос | ракета | космическая <-> станция | астероид | галактика | солнечная <-> система | комета | марс | юпитер | сатурн | плутон |венера | солнечные <-> пятна | солнечный <-> ветер | байконур | роскосмос | space <-> x | spacex | орбита | космический <-> мусор | МКС | космонавт | астронавт'

Оператор | означает "или", оператор <-> - объединение слов по бокам в фразу. За первый же день работы на небольшую группу бета-тестеров стало очевидно, что все поисковые запросы и оповещения нужно приправлять блокировкой порнухи, ставок на спорт, крипты, политоты и экстремизма, чтобы случайно не заработать себе статью.

Занятной получилась реализация сервиса оповещений. Анализ текста у меня происходит на уровне базы данных, с использованием словарей для полнотекстового поиска. При этом, из базы результат нужно как-то доносить до пользователя.

В итоге родилась идея: приправить основную таблицу триггером after insert, который будет пытаться вставить сообщение, если оно свежее 6 часов, в другую таблицу, получившую название spotter (наводчик).

На таблице spotter висит триггер, который делает select из таблицы queries (хранящей запросы), давая ответ: подходит под запрос или нет.

После этого вызывается функция pg_notify("test", "информация о сообщении"), которая передает информацию о сообщении всем, кто выполнил команду listen "test" и продолжает висеть на связи. В сообщении отправляется ссылка на сообщение и коротенькое превью из 200 первых символов.

Бот-слушатель соответственно рассылает сообщения подписантам.

Теперь для адекватной работы оповещалки (сокращения времени от опубликования до нахождения сообщения до 30-60 минут) нужно в очередной (в 8й) раз переделать менеджер команд сборщикам.

#postgresql
Forwarded from Библиотека программиста | программирование, кодинг, разработка
Егор Рогов из Postgres Professional подробно и доступно рассказывает теорию и практику работы с PostgreSQL:

📌 Индексы

- Механизм индексирования
- Интерфейс метода доступа, классы и семейства операторов
- Hash
- B-tree
- GiST
- SP-GiST
- GIN
- RUM
- BRIN
- Bloom

📌 Изоляция и многоверсионность

- Изоляция, как ее понимают стандарт и PostgreSQL
- Слои, файлы, страницы — что творится на физическом уровне
- Версии строк, виртуальные и вложенные транзакции
- Снимки данных и видимость версий строк, горизонт событий
- Внутристраничная очистка и HOT-обновления
- Обычная очистка (vacuum)
- Автоматическая очистка (autovacuum)
- Переполнение счетчика транзакций и заморозка

📌 Журналирование

- Буферный кеш
- Журнал предзаписи — как устроен и как используется при восстановлении
- Контрольная точка и фоновая запись — зачем нужны и как настраиваются
- Настройка журнала — уровни и решаемые задачи, надежность и производительность.

📌 Блокировки:

- Блокировки отношений
- Блокировки строк
- Блокировки других объектов и предикатные блокировки
- Блокировки в оперативной памяти

📌 Запросы

- Этапы выполнения запросов
- Статистика
- Последовательное сканирование
- Индексное сканирование
- Соединение вложенным циклом, а также будет продолжение про соединение хешированием / слиянием и сортировку

А еще у Postgres Professional есть учебные курсы, которые доступны всем желающим.
Создавая базу знаний, описывающую реальность, какой id вы присвоили бы понятию "религия"?

Я нашёл это понятие под id 666, у добавившего всё хорошо с чувством юмора.

P.S. на другие интересные сочетания цифр ничего не нашлось.
Экспериментирую сейчас с MongoDB.
Установил, разрешил подключения из внешнего мира, сижу, отлаживаюсь.

И тут в монге появляется новая база данных с сообщением, что все мои данные архивированы, плоти биткоины за расшифровку.

Спасибо ребятам за указание на дыру в безопасности:
по умолчанию в монге доступно анонимное подключение, но только локально.

Разрешив подключение из внешнего мира я забыл вырубить возможность подключиться просто по 27017 порту, хорошо, что сейчас, а не на проде. Заодно и уползу на другой порт.
Forwarded from Kedr to Earth | Земля, я Кедр ( Yuri Ammosov)
Кажется, пора учредить премию "Безумный банщик", и собирать туда самые идиотские и нелепые причины, за что люди попадали в бан на фейсбуке.
Начну.
1) за преследование и оскорбление гравитации путём именования её собакой женского пола (пока что первое место, история произошла на днях с френдессой, у меня глаза как выпали на клавиатуру, так и лежат)
2) за употребление слова "хатифнатт" (пост удалили, выкатили предупреждение)
3) за употребление словосочетания "этрусский скот" в беседе об античной истории
4) за ироничное цитирование одного товарища (товарища оставили, мне выкатили предупреждение)
5) за фотографии статуй из Летнего сада
6) за фотографии маленьких детей на пляже - потому что детская порнография
7) за цитаты из классической литературы (не помню, какие, но довольно известные и вполне невинные)
8) за фразу "Резать, не дожидаясь перитонита"
9) за фото кильки в булке (порно)
10) за анонс лекции с изображением распятия из-за "демонстрации жестокости"
11) за "грузинских с--обак", хотя имелись в виду именно животные вида Canis lupus familiaris, обитающие в Грузии
12) за цитату статьи Даля о глаголе "майданить"
13) за скриншот антисемитского высказывания в свой адрес (автор скриншота забанен не был)
14) бан на неделю за публикацию перевода фразы, автоматически сделанного фейсбуком (и неверного, насколько я понимаю)
15) за фото своего кобеля, где чуть-чуть видны его яйца - порнография (видимо, на собаку мужского пола желательно надевать трусы перед тем, как сделать фото - прим. Улисса)
16) за слово а р * б в адрес лошади - разжигание
17) за комментарий о том, что мне не нравятся чёрные собаки - расизм. К комментарию, где я говорю, что белые собаки мне тоже не нравятся, у фб претензий нет.
18) за фото обычной морской черепахи. Потому что обнажёнка. Апелляцию не удовлетворили.
19) человека забанили на 30 дней за эпитет "жирненькая" по отношению к традесканции.
20) забанили бизнес-аккаунт магазина шмоток. На фото была куртка из коричневой кожи, обвинили в работорговле. Апелляцию не удовлетворили.
21) В группе томатоводов китайская хохлатая сожрала рассаду баклажанов. Ну народ ржал и обсуждал особенности лысых китайских с-бак. Фб забанил полгруппы вместе с админами
22) за поговорку "Россия родина слонов"
23) за комментарий о том, что автор не может ни одну нацию назвать тупой
24) за упоминание поэта начала 20 века, псевдоним которого состоял из имени Саша и наименования одного неполиткорректного цвета
25) за частое редактирование поста (чую, доиграюсь я сама с этим постом в итоге ^^)

https://facebook.com/story.php?story_fbid=10215431865910389&id=1849806973
Палантир. Часть 17. Оптимизация базы данных.
#палантир@eshu_coding

В ближайшее время я планирую запустить поисковик @palantir_search_bot в общее пользование. Пришло время нагрузочного тестирования. Для этого я изобразил отдельный проектик, который имитирует поискового бота.

Для формирования тестовых поисковых запросов я использовал следущий поход: набил штук 100 разных слов (поток сознания там получился занятный, от "фавызм" (именно в таком написании) до хурмы и дыни.

Из этих слов случайным образом формируется пул запросов.

Запрос отправляется на сервер, ожидается ответ. Сразу по получении ответа - посылается следущий запрос. И так в N потоков.

Запустил в 1 поток: всё отлично.
Запустил в 10 потоков: всё отлично, постгрес не замечает нагрузки.
Запустил в 30 потоков. Постгрес не замечает нагрузки. Проходит пара минут и всё виснет.

Полез смотреть на сервер - 8 Гб оперативки кончились, вместе с 10 Гб файла подкачки, все съел постгрес, у которого установлен размер кэша в 2 Гб. Но каждое отдельное подключение, через которое шел поток данных, отъелось и в итоге память кончилась.

Оставил на ночь - постгрес не отвис: память утекла.

Итого, корень зла оказался в том, что подключения к постгресу, когда живут долгое время, не освобождают съеденную оператианую память (или освобождают не всю).

#postgresql
Палантир. Часть 18. Оптимизация базы данных.
#палантир@eshu_coding

Подключения к базе данных устанавливаются относительно продолжительное время, потому принято пользоваться ими длительное время. Мои боты, которых я писал летом 2020 висят на постоянных подключениях иногда по нескольку месяцев и проблем не было.

Но как через подключения потекли значительные объемы данных, проблемы резко возникли. А с учётом того, что в поисковике ожидается много пользователей, подключений используется тоже много (в среднем 200-300, с максимальным лимитом в 1500).

Проблему утечки памяти из прошлого поста я решил переработкой менеджера подключений. Некоторое время назад я написал класс, который ведёт учёт всех подключений к базе (чтобы не выйти за установленное максимальное число), держит несколько готовых подключений в горячем резерве и закрывает те, которые больше не нужны. Добавил к нему функцию закрытия подключений, которые живут и используются слишком долго и в которых мог накопиться мусор.

В принципе, в ConnectionString, строке, которой описывается подключение к PostgreSQL, есть группа параметров, отвечающих за тот самый пул подключений, который я сам наколхозил. И время жизни соединения там тоже можно задать. Но соединение там рвется только при неактивности в течение N секунд, а мне нужен костыль, ограничивающий время жизни вне зависимости от активности использования.

По хорошему, хранением соединений должна заниматься библиотека, с помощью которой я цепляюсь к базе данных - Npgsql. Но вот функции горячего резерва N подключений и безболезненного ограничения максимального числа в ней нет: мой менеджер подключений ждёт пока освободится слот, а Npgsql кидает исключение, что мне в этом проекте неудобно.

В итоге я пришел к следующим настройкам пула: 200 подключений в резерве, время жизни подключения 30 секунд, проверка и обслуживание пула раз в 3 секунды.

#csharp
Палантир. Часть 19. Результаты нагрузочного тестирования.
#палантир@eshu_coding

В настоящий момент Master сервер,на котором осуществляется поиск, представляет собой следующее:
8 Гб оперативки, 4 ядра CPU, 1.5 Тб SSD диск. ОС - 18я серверная убунта, база данных - PostgreSQL 13.3. Объем базы - чуть больше 1 Тб, около 800 млн строк в основной таблице, по которой и осуществляется полнотекстовый поиск. Принцип формирования запросов я рассказывал выше.

После устранения утечки памяти (рассказывал выше) и оптимизации конфигурации Postgres по части памяти, сервис свободно держит 100 RPS (запросов в секунду).

В рамках экспериментов я выкручивал мощность машины до 16 ядер и 64 Гб оперативки. В таком сетапе удерживается нагрузка в 500-750 RPS.

Постгрес могёт однако.

#postgresql
1
Палантир. Часть 20. Ускорение поиска.
#палантир@eshu_coding

Оценить результат вы можете в боте: @palantir_search_bot

В какой-то момент, читая про то, как работает постгрес по одной из ссылок в этом посте, я наткнулся на упоминание, что можно указать держать в оперативной памяти (кешировать) конкретную таблицу. Для этого используется расширение pg_prewarm.

Сначала я пропустил этот момент мимо: у меня база за терабайт, держать в оперативке её мягко говоря дорого.

А потом в какой-то момент мне стукнуло в голову решение: основная таблица - messages - у меня секционирована по месяцам.

Секционирование - разбиение одной большой таблицы на группу по-меньше. Для каждой из таблиц существует отдельный индекс, что позволяет существенно ускорить работу: работать со 100 индексами 10-ти гигабайтных таблиц быстрее, чем с одним монстроиндексом терабайтной таблицы.

Соответственно, таблица messages представляет собой около 200 таблиц вида messages_01_2021, messages_02_2021 и так далее, с 2014 по 2030 год.

Для того, чтобы поиск для пользователя выглядел мгновенным, я держу в оперативной памяти данные за последние 2 месяца, а остальное - пусть лежит на диске и используется по мере необходимости. Пользователь сразу получает хоть какой-то результат, а "хвост" долетит со временем.

Кроме загрузки таблиц в память я пробовал другой вариант - загнать в память только индексы, по которым идет поиск. Идея к сожалению себя не оправдала.

Также был доработан поисковый запрос на уровне c# - теперь он проводится в 3 этапа:
1. Запрос в последний месяц
2. Запрос в предпоследний месяц
3. Запрос во всю остальную базу, если нужно.

Результаты замеров скорости отклика на поиске глубиной в месяц:
1. Без кеширования - среднее время 200 мс, максимальное - 15 секунд
2. С закешированными индексами последних двух месяцев - среднее время 120 мс, максимальное - 6 секунд
3. С закешированными таблицами messages_10_2021 и messages_11_2021 - среднее время 80 мс, максимальное - 1.5 секунды

Под вариант "удобно использовать" подходит исключительно 3й, потому у сервера теперь 32 Гб оперативки (+3 тысячи к месячной плате)

#postgresql
Палантир. Часть 21. Боты, рефакторинг.
#палантир@eshu_coding

Идея с Final State Machine оказалась удачной, но первая реализация естественно вышла комом. Как только появилось понимание архитектуры, которую я хочу видеть в ботах, я сел и провел глобальный рефакторинг, оставив только логику и некоторые удачные модули, а заодно сменил базу, с которой работают боты, на MongoDB. У них в нынешнем виде вся логика работы с базой сводится к двум операциям: вставить/прочитать информацию по известному id.

Выбор базы обусловлен следующими причинами:
1. Операции без сложной логики "вставить/прочитать" в ней работают ощутимо быстрее, чем в постгресе.
2. У монги достаточно агрессивный механизм кеширования, который удобен для логики работы бота: Монга как хомяк набивает оперативку "горячей" информацией до тех пор, пока не выйдет за установленный лимит или не съест всю память. Информации там не будет очень много (перерасход 1-2 Гб оперативки я даже не замечу), а вот дополнительная скорость доступа к данным о юзерах, активных в настоящий момент не повредит.

В целом, MongoDB мне понравилась, с третьего подхода. Теперь хотя бы понятно, где она в тему, и в чём лучше PostgreSQL: скорость работы на некоторых операциях и простота использования.

При этом, главная её особенность, я бы сказал киллер-фича - простота построения распределенного хранилища, которое просто расширять и администрировать, мной пока не используется.
Палантир. Часть 22. Бот-агрегатор.
#палантир@eshu_coding

Ранее я описывал механизм работы "оповещалки" о происходящем в телеграме по ключевым словам: мной пишется запрос для полнотекстового поиска, который через каскад триггеров вызывает функцию pg_notify и сообщает внешним сервисам, что искомая фраза найдена.

Изначально этот функционал крутился в основной базе, на которой и так складирование всей информации и обслуживание поисковика. Чтобы разгрузить базу, я использовал следующий трюк: самодельную подписку на gRPC. Все желающие сообщений могут постучаться с запросом на сервер, после чего между ними и сервером повиснет gRPC канал, через который master-сервер потоком сливает все сообщения, только что пришедшие от сборщиков данных.

А на другом конце провода - практически полный клон master сервера, я его назвал NotificationProvider, с практически идентичной базой, в которой и происходит анализ. Но оповещение вылетает не в бота на прямую, а публикуется в брокер сообщений RabbitMQ, к которому уже цепляются боты - подписчики. RabbitMQ, NotificationProvider и PostgreSQL запущены с помощью Docker-compose и работают как единый организм.

Такой подход позволяет плодить ботов-агрегаторов в неограниченных количествах, хотя сейчас их всего 6 штук.
2025/07/10 15:49:25
Back to Top
HTML Embed Code: