В этом посте рассмотрим поиск заданного списка слов в миллионах документов, на основе примера задачи из книги “Cracking the coding interview”.
У нас есть миллион документов, и список слов. Нужно найти все документы, которые содержат все заданные слова. Слова в документе могут располагаться в произвольном порядке. Но слово не должно быть частью какого-то другого слова. Например, “кофе” не равно “кофемашина”.
Для начала надо выяснить, будет ли наша функция поиска вызвана единожды или много раз? Предположим, функция будет вызываться много раз, поэтому резонно с точки зрения производительности провести предварительные преобразования.
https://dmatveeva.github.io/search-words-in-millon-docs/
У нас есть миллион документов, и список слов. Нужно найти все документы, которые содержат все заданные слова. Слова в документе могут располагаться в произвольном порядке. Но слово не должно быть частью какого-то другого слова. Например, “кофе” не равно “кофемашина”.
Для начала надо выяснить, будет ли наша функция поиска вызвана единожды или много раз? Предположим, функция будет вызываться много раз, поэтому резонно с точки зрения производительности провести предварительные преобразования.
https://dmatveeva.github.io/search-words-in-millon-docs/
Подготовка к интервью по System Design
Поиск слов в миллионах документов
В этом посте рассмотрим поиск заданного списка слов в
миллионах документов, на основе примера задачи из книги “Cracking the coding interview”.
миллионах документов, на основе примера задачи из книги “Cracking the coding interview”.
В этом посте разберем еще одну задачу из книги Cracking the coding interview. Изначально задача про биржу, но мне кажется удобнее ее немного обобщить.
https://dmatveeva.github.io/how-to-distribute-data/
https://dmatveeva.github.io/how-to-distribute-data/
Подготовка к интервью по System Design
Как передавать пользователю данные?
В этом посте разберем еще одну задачу из книги Cracking the coding interview.
Задача про биржу, но мне кажется удобнее ее немного обобщить, что я и сделала в заголовке.
Задача про биржу, но мне кажется удобнее ее немного обобщить, что я и сделала в заголовке.
Завела канал на Youtube - https://www.youtube.com/@SystemDesign_Matveeva
И смонтировала первое видео :)
https://www.youtube.com/watch?v=sk-w0bWJaPA
И смонтировала первое видео :)
https://www.youtube.com/watch?v=sk-w0bWJaPA
YouTube
Масштабируем веб-сайт
В этом видео рассказываю некоторые стратегии масштабирования веб-сайтов. Они помогут обеспечить работу системы, когда количество пользователей растет с одного до миллиона.
❤1👍1
Что такое протокол gossip, где применяется и для чего?
https://dmatveeva.github.io/gossip-protocol/
https://dmatveeva.github.io/gossip-protocol/
System Design
Протокол gossip
В этом посте - поверхностный обзор протокола Gossip.
Немного об устройстве компьютера и реализации многопоточности.
https://dmatveeva.github.io/how-computer-works/
https://dmatveeva.github.io/how-computer-works/
System Design
Как работает компьютер
В этом посте я бы хотела рассказать об устройстве компьютера и затронуть тему реализации многопоточности.
Немного о скоростях, с которыми работает компьютер, и ссылки на инфографику:
https://dmatveeva.github.io/latency-numbers/
https://dmatveeva.github.io/latency-numbers/
System Design
Как быстро работает компьютер
C какой скоростью работает компьютер? Примерные цифры - в таблице.
Версионирование базы данных с Liquibase:
https://dmatveeva.github.io/liquibase/
https://dmatveeva.github.io/liquibase/
System Design
Liquibase
Как произвести изменение схемы базы данных в работающем приложении?
👍1
Первый пост-обзор на книгу "Микросервисы. Паттерны разработки и рефакторинга".
https://dmatveeva.github.io/microservices/
https://dmatveeva.github.io/microservices/
System Design
Микросервисная vs. Монолитная архитектура
В этом посте начну обзор книги “Микросервисы. Паттерны разработки и рефакторинга” Криса Ричардсона.
Рассмотрим, чем микросервисная архитектура отличается от монолитной, их преимущества и недостатки.
Рассмотрим, чем микросервисная архитектура отличается от монолитной, их преимущества и недостатки.
🔥2
Сейчас наша команда активно ищет новых разработчиков на Java, и я уже несколько недель участвую в собеседованиях.
Наблюдения:
▫️ Теперь понимаю, почему иногда, будучи кандидатом, получала вопросы, казавшиеся странными.
Например вопрос интервьюера, много ли вы писали кода на рабочем месте? Казалось бы, конечно, ведь я разработчик. Но вот например один из кандидатов сказал, что уже несколько месяцев не пишет код, а занимается задачами devops, хотя в резюме как раз указано разработчик .
▫️ Обычно наш тимлид дает несколько фрагментов кода и просит найти и исправить ошибку. И почему-то кандидаты неохотно пишут код. Большинство людей просто размышляли вслух, всего пару человек писали и исправляли. Один из них правда не приблизился к решению, но начал активно рефакторить существующие классы, из-за чего решение перестало компилироваться.
▫️И уже несколько человек сказали, что долго работали в реактивном стэке, и поэтому забыли Hibernate.
Наблюдения:
▫️ Теперь понимаю, почему иногда, будучи кандидатом, получала вопросы, казавшиеся странными.
Например вопрос интервьюера, много ли вы писали кода на рабочем месте? Казалось бы, конечно, ведь я разработчик. Но вот например один из кандидатов сказал, что уже несколько месяцев не пишет код, а занимается задачами devops, хотя в резюме как раз указано разработчик .
▫️ Обычно наш тимлид дает несколько фрагментов кода и просит найти и исправить ошибку. И почему-то кандидаты неохотно пишут код. Большинство людей просто размышляли вслух, всего пару человек писали и исправляли. Один из них правда не приблизился к решению, но начал активно рефакторить существующие классы, из-за чего решение перестало компилироваться.
▫️И уже несколько человек сказали, что долго работали в реактивном стэке, и поэтому забыли Hibernate.
👍2😁2
Сейчас на нашем проекте идет активный перевод существующих spring boot микросервисов с oracle на postgres. Задача разработчика - адаптировать нативные запросы, затем завести микросервис локально у себя на машине и вызвать пару эндпойнтов, чтобы убедиться в минимальной работоспособности. Затем микросервис развертывается на дев стенде и передается в тестирование QA команде.
Этот процесс осложняется тем, что микросервисы часто имеют множество зависимостей от других микросервисов, и чтобы запустить один, нужно запустить несколько других.
И вдобавок, не все сервисы содержат юнит-тесты.
Поэтому нужно было писать довольно много тестов. И очень удобно оказалось запускать их лишь для репозиториев с нативными запросами, не поднимая весь контекст спринга:
1. К тестовому классу добавила аннотацию DataJpaTest (вместо SpringBootTest, которая поднимает весь контекст)
2. DataJpaTest использует embedded in-memory БД. Чтобы подключиться к нужной базе, использовала AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
3. Ну и ActiveProfile("test"), чтобы Spring Boot подхватил параметры подключения к базе из профиля test в test-application.yml
И когда нативные запросы отлажены с помощью такой конфигурации, для тестирования эндпойнтов использую MockMvc.
А в планах разобраться с WireMock, чтобы мокировать запросы к другим сервисам, и, соответственно, избавиться от необходимости дополнительно их развертывать.
#рабочее
Этот процесс осложняется тем, что микросервисы часто имеют множество зависимостей от других микросервисов, и чтобы запустить один, нужно запустить несколько других.
И вдобавок, не все сервисы содержат юнит-тесты.
Поэтому нужно было писать довольно много тестов. И очень удобно оказалось запускать их лишь для репозиториев с нативными запросами, не поднимая весь контекст спринга:
1. К тестовому классу добавила аннотацию DataJpaTest (вместо SpringBootTest, которая поднимает весь контекст)
2. DataJpaTest использует embedded in-memory БД. Чтобы подключиться к нужной базе, использовала AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
3. Ну и ActiveProfile("test"), чтобы Spring Boot подхватил параметры подключения к базе из профиля test в test-application.yml
И когда нативные запросы отлажены с помощью такой конфигурации, для тестирования эндпойнтов использую MockMvc.
А в планах разобраться с WireMock, чтобы мокировать запросы к другим сервисам, и, соответственно, избавиться от необходимости дополнительно их развертывать.
#рабочее
❤3👍1🔥1
Продолжаю заметки по миграции spring boot микросервисов с oracle на postgres.
Во время адаптации миграции несколько раз сталкивалась с особенностями конвертации полей различных типов в двух БД.
Вот несколько примеров:
✔️ В oracle в нативном запросе есть джойн по колонкам с типами varchar and number.
Например:
Oracle неявно делает конвертацию
✔️Изначально в oracle в таблице БД есть колонка с типом number, а в коде, в сущности hibernate, она маппится на поле с типом String. В postgres колонка мигрирует с типом bigint. При работе приложения, oracle спокойно делает конвертацию, а postgres выдает ошибку на несоответствие типов.
Решение - заменить тип поля в сущности на Long.
Чуть было не пропустила эту ошибку, т.к. на первых порах писала тесты лишь для тех методов репозиториев, где объявлены нативные запросы. А она обнаружилась случайно как раз для сущности, для которой нет таких методов. После этого начала писать тесты вообще для всех сущностей и отловила не одну такую ситуацию.
✔️ В oracle в таблице БД есть колонка с типом varchar, которая маппится на поле с типом boolean. Предполагается, что в БД значения колонки 0/1.
Опять же, oracle конвертирует, а для корректной работы в postgres, нужно добавить аннотацию
#рабочее
Во время адаптации миграции несколько раз сталкивалась с особенностями конвертации полей различных типов в двух БД.
Вот несколько примеров:
✔️ В oracle в нативном запросе есть джойн по колонкам с типами varchar and number.
Например:
create table employee ( id number )
create table tasks ( id number, employee_id varchar )
select * from employee e join tasks t on e.id = t.employee_id;
Oracle неявно делает конвертацию
to_number(t.employee_id)=e.id, и если в таблице employee колонка employee_id содержит только строки, которые можно конвертировать в число, то запрос отработает нормально. Но postgres выдаст ошибку, и для решения нужно будет явно привести один тип к другому.✔️Изначально в oracle в таблице БД есть колонка с типом number, а в коде, в сущности hibernate, она маппится на поле с типом String. В postgres колонка мигрирует с типом bigint. При работе приложения, oracle спокойно делает конвертацию, а postgres выдает ошибку на несоответствие типов.
Решение - заменить тип поля в сущности на Long.
Чуть было не пропустила эту ошибку, т.к. на первых порах писала тесты лишь для тех методов репозиториев, где объявлены нативные запросы. А она обнаружилась случайно как раз для сущности, для которой нет таких методов. После этого начала писать тесты вообще для всех сущностей и отловила не одну такую ситуацию.
✔️ В oracle в таблице БД есть колонка с типом varchar, которая маппится на поле с типом boolean. Предполагается, что в БД значения колонки 0/1.
Опять же, oracle конвертирует, а для корректной работы в postgres, нужно добавить аннотацию
@Type(type="org.hibernate.type.NumericBooleanType") на поле сущности.#рабочее
🫡3
Продолжение заметок по миграции spring boot микросервисов с oracle на postgresql.
1. Конструкция limit в postgresql заменяет подзапрос с аналитической функцией row_number в oracle.
Postgresql:
Oracle:
2. Склейка строк через
3. Неочевидные ошибки вида Error near 'type', Error near 'precision'
Оказалось, что type и precision - это ключевые слова в postgresql, и при использовании их в качестве алиасов требуется указать as.
4. Insert и not null поля.
Cоздание сущности employee (через hibernate) работало в oracle, а в postgresql стало падать с ошибкой, что не заполнено обязательное поле is_retired.
Оказалось, что в oracle поле в таблице employee было объявлено с конструкцией on null, а в postgresql мигрировало без него, т.к. нет прямого аналога:
Oracle:
Postgresql:
А если в postgresql явно передается null, то это переопределяет default.
В итоге пришлось в коде явно прописать дефолтное значение для атрибута isRetired.
#рабочее
1. Конструкция limit в postgresql заменяет подзапрос с аналитической функцией row_number в oracle.
Postgresql:
select product_id
from product
order by create_date limit 1
Oracle:
select product_id
from (
select product_id, row_number() over (order by modify_date) as rn
from product
) t
where t.rn=1
2. Склейка строк через
|| в postgreql не null-безопасна в отличии от oracle. Если один из аргументов is null, то вся строка превратится в null. Поэтому лучше использовать функции concat и concat_ws.3. Неочевидные ошибки вида Error near 'type', Error near 'precision'
Оказалось, что type и precision - это ключевые слова в postgresql, и при использовании их в качестве алиасов требуется указать as.
4. Insert и not null поля.
Cоздание сущности employee (через hibernate) работало в oracle, а в postgresql стало падать с ошибкой, что не заполнено обязательное поле is_retired.
Оказалось, что в oracle поле в таблице employee было объявлено с конструкцией on null, а в postgresql мигрировало без него, т.к. нет прямого аналога:
Oracle:
create table employee (
is_retired numeric default on null 0 not null
)
Postgresql:
create table employee (
is_retired numeric not null default 0
)
А если в postgresql явно передается null, то это переопределяет default.
В итоге пришлось в коде явно прописать дефолтное значение для атрибута isRetired.
#рабочее
🔥4👍1
https://dmatveeva.github.io/sql-relational-theory-1/
В статье – обзор первой главы книги “Sql и реляционная теория” Дж. Дейта.
Что такое реляционная модель и зачем она нужна разработчику?
При изучении sql реляционная модель обычно упоминается мимоходом, а основной упор делается на изучение языка sql, или даже какого-то его диалекта, например для oracle. Между тем, SQL и реляционная теория - не одно и то же, а профессионал в области баз данных должен знать реляционную теорию, т.к. вся область знания об управлении базами данных построена на ней. Разработчик может использовать БД разных вендоров, различные диалекты, но базовые принципы всегда останутся те же самые.
В статье – обзор первой главы книги “Sql и реляционная теория” Дж. Дейта.
Что такое реляционная модель и зачем она нужна разработчику?
При изучении sql реляционная модель обычно упоминается мимоходом, а основной упор делается на изучение языка sql, или даже какого-то его диалекта, например для oracle. Между тем, SQL и реляционная теория - не одно и то же, а профессионал в области баз данных должен знать реляционную теорию, т.к. вся область знания об управлении базами данных построена на ней. Разработчик может использовать БД разных вендоров, различные диалекты, но базовые принципы всегда останутся те же самые.
System Design
Реляционная модель и SQL
В этой статье – обзор главы 1 книги “Sql и реляционная теория” Дж. Дейта.
👍2❤1
Возникла проблема при старте сервиса - долго не отвечают heathchecks сервиса в openshift.
Для данного сервиса healthchecks в Openshift проверяли liveness и readiness эндпойнты актуатора в Spring boot, и проблема оказалась в readiness probe.
Выяснилось, что при старте сервиса запускается продолжительный по времени метод формирования отчета, который сохраняется в БД в ожидании, когда его считает другой сервис.
Возник вопрос, насколько актуальна readiness probe для сервиса, который не принимает входящих запросов и лишь формирует отчет? Однако оказалось, что отключить ее нет возможности.
Поэтому следующим шагом стал разбор того, что происходит при старте.
Метод формирования отчета запускался с помощью аннотации:
В документации к классу
В документации к классу
Выглядит логично, что
Посмотрим, когда выставляет статус UP.
В методе
Здесь вызывается
А
Поэтому, пока он не завершится, readiness probe не будет переведена в статус UP.
Переопределить метод
#рабочее
Для данного сервиса healthchecks в Openshift проверяли liveness и readiness эндпойнты актуатора в Spring boot, и проблема оказалась в readiness probe.
Выяснилось, что при старте сервиса запускается продолжительный по времени метод формирования отчета, который сохраняется в БД в ожидании, когда его считает другой сервис.
Возник вопрос, насколько актуальна readiness probe для сервиса, который не принимает входящих запросов и лишь формирует отчет? Однако оказалось, что отключить ее нет возможности.
Поэтому следующим шагом стал разбор того, что происходит при старте.
Метод формирования отчета запускался с помощью аннотации:
@EventListener(AppricationReadyEvent.class)
public void generate() {
//логика создания отчета
}
В документации к классу
AppricationReadyEvent сказано:Event published as late as conceivably possible to indicate that the application is ready to service requests.
В документации к классу
ReadinessState:"Readiness" state of the application.
An application is considered ready when it's live and willing to accept traffic. "Readiness" failure means that the application is not able to accept traffic and that the infrastructure should stop routing requests to it.
Выглядит логично, что
AppricationReadyEvent должен быть опубликован после того, как readiness probe станет UP.Посмотрим, когда выставляет статус UP.
В методе
SpingApplication.run(...), после создания, инициализации и подготовки контекста идет вызов listeners.ready(...) public ConfigurableApplicationContext run(String... args) {
...
try {
...
context = createApplicationContext();
...
}
try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
}
...
}Здесь вызывается
EventPublishingRunListener#ready, который и выставляет статус readiness probe в UP:org.springframework.boot.context.event.EventPublishingRunListener#ready@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}
А
context.publishEvent в свою очередь вызывает org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent@Override
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
...
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null && listener.supportsAsyncExecution()) {
try {
executor.execute(() -> invokeListener(listener, event));
}...
}
invokeListeners(...) - как раз приводит к вызову нашего метода generate, помеченного аннотацией @EventListener(AppricationReadyEvent.class)Поэтому, пока он не завершится, readiness probe не будет переведена в статус UP.
Переопределить метод
EventPublishingRunListener#ready нет возможности, поэтому было решено при старте запускать формирование отчета в новом потоке. Т.к. продолжительный метод генерации выполняется в новом потоке, в основном потоке метод generateAtStartup быстро завершается, не блокируясь, и readiness probe переводится в UP.@Autowired
ScheduledExecutorService executorService;
@EventListener(ApplicationReadyEvent.class)
public void generateAtStartup() {
executorService.schedule(this::generateReport, 20, TimeUnit.SECONDS);
}
private void generate() { //логика создания отчета }
#рабочее
😁1
Прошла обучение для того, чтобы проводить технические собеседования по Java в компанию.
Провела тестовое интервью с кандидатом под наблюдением опытного интервьюера.
Также пообщалась с другими коллегами, кто имеет большой опыт проведения собеседований.
Записала советы, как проводить интервью эффективно:
▫️ Хотя и есть стандартные вопросы, которые необходимо задать, надо в первую очередь отталкиваться от уровня кандидата. Если человек на вопрос отвечает поверхностно, спросить о нюансах использования, о деталях реализации и т.д.
▫️ Если человек не может сформулировать ответ на вопрос, но при этом у него в резюме указан большой опыт в данной области, то стоит переформулировать тот же запрос, но с точки зрения практики. Например, если кандидат не может сказать формальное определение RESTful-вебсервиса, но при этом у него опыт разработки 10+ лет, то понятно, что он их разрабатывал и понимает, что это.
▫️ Если человек не понимает вопрос, нужно переформулировать на конкретном примере.
▫️ Если человек ответил неправильно или неполно, можно явно подсказать, что хочется услышать в ответе. Тот, кто разбирается в теме, сразу поймет о чем речь, и дополнит ответ.
▫️ Важно узнать, что человек понимает сложность используемых алгоритмов/структур данных.
Провела тестовое интервью с кандидатом под наблюдением опытного интервьюера.
Также пообщалась с другими коллегами, кто имеет большой опыт проведения собеседований.
Записала советы, как проводить интервью эффективно:
▫️ Хотя и есть стандартные вопросы, которые необходимо задать, надо в первую очередь отталкиваться от уровня кандидата. Если человек на вопрос отвечает поверхностно, спросить о нюансах использования, о деталях реализации и т.д.
▫️ Если человек не может сформулировать ответ на вопрос, но при этом у него в резюме указан большой опыт в данной области, то стоит переформулировать тот же запрос, но с точки зрения практики. Например, если кандидат не может сказать формальное определение RESTful-вебсервиса, но при этом у него опыт разработки 10+ лет, то понятно, что он их разрабатывал и понимает, что это.
▫️ Если человек не понимает вопрос, нужно переформулировать на конкретном примере.
▫️ Если человек ответил неправильно или неполно, можно явно подсказать, что хочется услышать в ответе. Тот, кто разбирается в теме, сразу поймет о чем речь, и дополнит ответ.
▫️ Важно узнать, что человек понимает сложность используемых алгоритмов/структур данных.
🔥4
Появилась задача - оптимизировать сложный аналитический sql-запрос.
Первый возникший вопрос - как измерять эффективность оптимизации. Ведь при повторном выполнении запроса данные кэшируются, и запрос может выполняться на порядки быстрее, чем в первый раз. Существуют хаки для очистки кэшей - остановить Postgresql, очистить кэш ОС. Но это применимо только для локальной БД. В условиях кластера это может не сработать, да и прав на такие действия может не быть.
Остается только ориентироваться на план запроса. Основная цель - на каждом этапе минимизировать количество обрабатываемых строк. В моем случае запрос представлял собой соединение основной таблицы со многими другими, фильтрация по определенным атрибутам, а затем группировка по сущностям из основной таблицы.
План показал, что промежуточные результаты содержали миллионы строк, поэтому группировка была очень медленной. Также, в отчете были необходимы поля из основной таблицы, и для этого по ним тоже проводилась группировка (просто чтобы иметь возможность указать их в select).
Что было сделано:
1. Соединения таблиц с селективными фильтрами были вынесены в подзапрос до группировки, благодаря чему количество группируемых строк уменьшилось до десятков тысяч.
2. Тут оказалось, что количество таблиц, соединяемых в основном подзапросе, оказалось больше 10. В Postgresql после определенного количества соединяемых таблиц (по умолчанию 8) оптимизатор перестает искать оптимальный план и просто обрабатывает их в порядке объявления. А таблица с наиболее селективным фильтром оказалась в конце списка таблиц, поэтому в плане сначала не было заметно никакого улучшения. Но, при перемещении этой таблицы наверх списка, селективный фильтр сработал первым и дал значительное ограничение промежуточных результатов.
3. После группировки еще раз присоединила основную таблицу, чтобы выбрать из нее атрибуты, необходимые в отчете. Так ушла необходимость "протаскивать" их наверх из основного подзапроса, и проводить по ним лишнюю группировку.
Также надо иметь в виду, что оптимизатор Postgresql строит план на основе статистики - количества строк в таблицах, селективности атрибутов, по которым идет фильтрация и т.д.. Поэтому оптимизация, проведенная на dev-стенде, может быть неактуальной для промышленных данных, и ее надо обязательно протестировать на prod-стенде.
#рабочее
Первый возникший вопрос - как измерять эффективность оптимизации. Ведь при повторном выполнении запроса данные кэшируются, и запрос может выполняться на порядки быстрее, чем в первый раз. Существуют хаки для очистки кэшей - остановить Postgresql, очистить кэш ОС. Но это применимо только для локальной БД. В условиях кластера это может не сработать, да и прав на такие действия может не быть.
Остается только ориентироваться на план запроса. Основная цель - на каждом этапе минимизировать количество обрабатываемых строк. В моем случае запрос представлял собой соединение основной таблицы со многими другими, фильтрация по определенным атрибутам, а затем группировка по сущностям из основной таблицы.
План показал, что промежуточные результаты содержали миллионы строк, поэтому группировка была очень медленной. Также, в отчете были необходимы поля из основной таблицы, и для этого по ним тоже проводилась группировка (просто чтобы иметь возможность указать их в select).
Что было сделано:
1. Соединения таблиц с селективными фильтрами были вынесены в подзапрос до группировки, благодаря чему количество группируемых строк уменьшилось до десятков тысяч.
2. Тут оказалось, что количество таблиц, соединяемых в основном подзапросе, оказалось больше 10. В Postgresql после определенного количества соединяемых таблиц (по умолчанию 8) оптимизатор перестает искать оптимальный план и просто обрабатывает их в порядке объявления. А таблица с наиболее селективным фильтром оказалась в конце списка таблиц, поэтому в плане сначала не было заметно никакого улучшения. Но, при перемещении этой таблицы наверх списка, селективный фильтр сработал первым и дал значительное ограничение промежуточных результатов.
3. После группировки еще раз присоединила основную таблицу, чтобы выбрать из нее атрибуты, необходимые в отчете. Так ушла необходимость "протаскивать" их наверх из основного подзапроса, и проводить по ним лишнюю группировку.
Также надо иметь в виду, что оптимизатор Postgresql строит план на основе статистики - количества строк в таблицах, селективности атрибутов, по которым идет фильтрация и т.д.. Поэтому оптимизация, проведенная на dev-стенде, может быть неактуальной для промышленных данных, и ее надо обязательно протестировать на prod-стенде.
#рабочее
🔥3
HikariCP и Postgresql
Чтобы выполнить запрос в базе данных, сервер приложений должен установить с ней соединение. Это может быть затратно, т.к. надо открыть соединение через драйвер, открыть TCP-сокет, выполнить запрос, закрыть сокет, закрыть соединение. Таких подключений может быть сотни и тысячи, из-за чего лимит оперативной памяти может быстро исчерпаться.
Поэтому при обращении к БД клиенты (сервера приложений) используют пулы соединений. В случае, если в пуле есть свободное соединение, оно берется из пула. Если пул исчерпан, то запросы встают в очередь и ждут свободного соединения.
У нас на проекте в качестве сonnection pooler используется HikariCP. С базой данных Oracle приложение работало без проблем, но после перехода на Postgresql БД начала падать по непонятной причине. Оказалось, что hikari заполняет пул сразу максимальным количеством соединений, а не создает их постепенно. Поэтому все сервисы резервировали максимум соединений, и БД не выдерживала. Postgresql каждое соединение обрабатывает как отдельный процесс, а Oracle - как поток внутри общего процесса. В результате Oracle лучше держит большее число соединений.
Проблема решилась установлением корректных настроек HikariCP.
#рабочее
Чтобы выполнить запрос в базе данных, сервер приложений должен установить с ней соединение. Это может быть затратно, т.к. надо открыть соединение через драйвер, открыть TCP-сокет, выполнить запрос, закрыть сокет, закрыть соединение. Таких подключений может быть сотни и тысячи, из-за чего лимит оперативной памяти может быстро исчерпаться.
Поэтому при обращении к БД клиенты (сервера приложений) используют пулы соединений. В случае, если в пуле есть свободное соединение, оно берется из пула. Если пул исчерпан, то запросы встают в очередь и ждут свободного соединения.
У нас на проекте в качестве сonnection pooler используется HikariCP. С базой данных Oracle приложение работало без проблем, но после перехода на Postgresql БД начала падать по непонятной причине. Оказалось, что hikari заполняет пул сразу максимальным количеством соединений, а не создает их постепенно. Поэтому все сервисы резервировали максимум соединений, и БД не выдерживала. Postgresql каждое соединение обрабатывает как отдельный процесс, а Oracle - как поток внутри общего процесса. В результате Oracle лучше держит большее число соединений.
Проблема решилась установлением корректных настроек HikariCP.
#рабочее
👍8
В системе, которую разрабатывает наша команда, у пользователей есть функция добавления описания различным продуктам. Появилась необходимость проанализировать, насколько осмысленно заполняются эти описания. Для этого нужно было проверить соответствующую колонку из БД Postgresql на то, являются ли ее значения комбинацией определенных строк, не несущих смысла, таких как 'описание', 'данные', 'продукт', всяких специальных символов и т.п.
Решение в лоб - просто сделать цепочку replace, заменяя каждое встреченное слово-заменитель на пустую строку, и проверить, превратилась ли все описание в пустую строку:
Но учитывая, что таких слов для проверки было более 100, это было бы весьма тяжело читать и поддерживать. Да и сам запрос немаленький, в нем рассчитываются и другие параметры.
Намного удобнее сделать проверку с использованием регулярного выражения:
Regexp_replace заменяет любое из слов, перечисленных в круглых скобках через черту, на пустую строку. Параметр g указывает на то, что заменяем все совпадения, а нет только первое встреченное, i - что игнорируем верхний/нижний регистр.
Еще удобнее не перечислять слова в теле запроса, а поместить их в отдельную таблицу. Так в будущем будет легко добавлять новые.
Тогда запрос будет таким:
Здесь с помощью string_agg собираем строку-паттерн для регулярного выражения и затем используем ее в функции regexp_replace.
В какой-то момент во время экспериментов показалось, что регулярные выражения не работают для этой задачи, т.к. на любое описание проверка возвращала true, как будто все описания состоят из заменителей. Оказалось, что проблема в точке. В regex выражении она означает любой символ, соответственно все описания подходили под шаблон. Стоило записать ее с обратной чертой \. и все заработало как надо.
#рабочее
Решение в лоб - просто сделать цепочку replace, заменяя каждое встреченное слово-заменитель на пустую строку, и проверить, превратилась ли все описание в пустую строку:
select id, denoscription, replace(replace(replace....replace(denoscription, 'описание', '')...'') ...'') = '' has_no_meaning
from product p
Но учитывая, что таких слов для проверки было более 100, это было бы весьма тяжело читать и поддерживать. Да и сам запрос немаленький, в нем рассчитываются и другие параметры.
Намного удобнее сделать проверку с использованием регулярного выражения:
select id, denoscription, regexp_replace(denoscription, '(описание|продукт|...|данные|-|,|\.)' , '', 'gi')) = '' has_no_meaning
from product p
Regexp_replace заменяет любое из слов, перечисленных в круглых скобках через черту, на пустую строку. Параметр g указывает на то, что заменяем все совпадения, а нет только первое встреченное, i - что игнорируем верхний/нижний регистр.
Еще удобнее не перечислять слова в теле запроса, а поместить их в отдельную таблицу. Так в будущем будет легко добавлять новые.
create table t_descr_substitute (
substitute varchar(2000)
);
insert into t_descr_substitute (substitute) values ('описание'); ...
Тогда запрос будет таким:
select id, denoscription, regexp_replace(denoscription, a.pattern, '', 'gi') = '' has_no_meaning
from product p
join ( select '(' + string_agg(substitute, '|') + ')' pattern from t_descr_substitute ) a on 1=1
Здесь с помощью string_agg собираем строку-паттерн для регулярного выражения и затем используем ее в функции regexp_replace.
В какой-то момент во время экспериментов показалось, что регулярные выражения не работают для этой задачи, т.к. на любое описание проверка возвращала true, как будто все описания состоят из заменителей. Оказалось, что проблема в точке. В regex выражении она означает любой символ, соответственно все описания подходили под шаблон. Стоило записать ее с обратной чертой \. и все заработало как надо.
#рабочее
🤔6💅1