Сайт Романа ПарпалакаБлог

Одновременная вставка уникальных значений в словарные таблицы — В кресле препода №3

30 августа 2020 года, 23:39

Как правильно добавлять данные в словарную таблицу с уникальными строками одновременно из нескольких потоков? В PostgreSQL вот так:

CREATE TABLE words (
  id   SERIAL PRIMARY KEY,
  word TEXT NOT NULL UNIQUE
);

BEGIN;
SELECT id FROM words WHERE word = 'a';
INSERT IGNORE INTO words (word) VALUES ('a');
SELECT id FROM words WHERE word = 'a';
COMMIT;

В видео рассказываю, почему именно так, и показываю, как это работает.

00:25 Пример
01:34 Демонстрация наивной реализации вставки в словарные таблицы
02:32 Недостаток: появление дублей
03:45 Демонстрация уникального индекса
04:47 Недостаток одного только уникального индекса
05:55 Нет поддержки целостности ⇒ нужны транзакции
06:37 Демонстрация параллельной вставки в таблицу с уникальным индексом в транзакции
08:59 Вставка с игнорированием
09:17 Демонстрация вставки с игнорированием в транзакции с уровнем READ COMMITTED
12:15 Демонстрация дедлока при вставке с игнорированием в транзакции с уровнем REPEATABLE READ
13:44 Особенности метода в MySQL

    4 комментария

Кеширование в nginx

28 августа 2020 года, 22:12

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

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

Иногда папку с кешем приходится удалять, если она слишком сильно разрослась. Или если я вношу правки в систему, и кеш устаревает. Посетители популярных страниц генерируют множество запросов к одинаковым формулам. В кеше их нет. Nginx направляет запросы к PHP. PHP на каждую формулу вызывает консольный скрипт латеха. Раньше у меня не было защиты от того, чтобы сервер в нагруженном состоянии хотя бы не делал одно и то же много раз. Это классическое условие гонки.

Как оказалось, приемлемое решение — включить кеш в nginx и настроить блокировку. Тогда он пропускает на бэкенд разные запросы, а одинаковые выстраивает в очередь ожидания. Результат записывает в свой внутренний временный кеш и отдает всем ожидавшим клиентам.

В блоке конфигурации http указываем папку и другие параметры зоны кеша:

fastcgi_cache_path /var/data/i.upmath.me levels=1:2 keys_zone=i_upmath:10m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

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

Далее в нужном location подключаем зону:

fastcgi_cache i_upmath;
fastcgi_cache_valid 200 10m;
fastcgi_cache_methods GET HEAD;
fastcgi_cache_lock on;
fastcgi_cache_lock_age 9s;
fastcgi_cache_lock_timeout 9s;

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

На моем сервере ограниченное количество процессов php-fpm могут генерировать картинки. Это сделано, чтобы не мешать другим сайтам. Чтобы после очистки кеша запросы к картинкам дожидались их генерации, я увеличил таймауты nginx для ожидания бэкенда:

fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
fastcgi_connect_timeout 90;
fastcgi_send_timeout 90;

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

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

    Оставить комментарий

Кеширование и условие гонки

20 августа 2020 года, 10:57

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

Кеширование не только экономит ресурсы и делает систему более отзывчивой. Без кеширования долгих операций система в принципе не сможет работать в режиме высокой нагрузки. Если вы разрабатываете кеширование в такой системе, важно помнить об одной частой ошибке проектирования — условии гонки (race condition). Расскажу об этой проблеме на выдуманном примере главной страницы интернет-магазина. А в следующий раз поговорим о частном решении на уровне веб-сервера nginx.

Предположим, на главной странице интернет-магазина выводятся карточки наиболее популярных товаров. Товаров и отзывов много, и запрос к базе данных на получение списка выполняется 5 секунд. А остальную часть страницы можно сгенерировать за 100 миллисекунд. Предположим также, что у вас на сервере умеренное количество памяти, и он может одновременно обрабатывать не более 20 запросов (каждый в своем процессе). В таких условиях без кеширования вы в принципе не сможете обработать более 60 / 5,1 · 20 = 235 запросов в минуту к главной странице.

235 запросов в минуту — вроде как не очень мало. Но сайт быстро ляжет, если ссылку разместят на каком-нибудь относительно популярном ресурсе, и на него одновременно перейдут несколько десятков человек. Да и сайт, который не может открыться в течение 5 секунд, в современном вебе никому не составит серьезную конкуренцию.

Если запрос к базе данных выполнить один раз и запомнить список наиболее популярных товаров, скажем, на час, то сервер сможет выдержать до 60 / 0,1 · 20 = 12 000 запросов в минуту. Эта грубая оценка, конечно, уже не отражает реальную возможность сервера. Скорее всего, производительность окажется ниже из-за нехватки ресурсов процессора, сети и т. д. Но оценка показывает, что запрос популярных товаров перестает быть узким бутылочным горлышком в системе.

Однако запомнить результаты выполнения запроса недостаточно. Рано или поздно они устареют, и список популярных товаров надо пересчитать заново. Здесь и кроется та самая ошибка — условие гонки. Если результаты выполнения запроса устареют сразу для всех посетителей, и этих посетителей много, тяжелый запрос начнет выполняться одновременно.

Одновременное выполнение тяжелого запроса — неприятная ситуация по многим причинам:

  1. Несколько конкурентных запросов могут нагружать базу данных и выполняться медленнее, чем один запрос.
  2. Процессы приложения вместо полезной работы будут ждать окончания выполнения долгих запросов (если, конечно, у вас не асинхронная архитектура; хотя я сомневаюсь, что в этом случае вы бы стали сейчас читать в интернете об условии гонки). При достаточном количестве посетителей 20 процессов израсходуются очень быстро, и сайт перестанет открываться, пока, наконец, не завершится выполнение долгих запросов.
  3. Кроме того, если вы еще и допустили ошибку при программировании самого кеша, и система записывает данные в него неатомарно (например, в файл с помощью fopen/fwrite, file_put_contents и т. д.), вы с большой вероятностью получите испорченные данные (записанные вперемешку байты из разных процессов). Если система не готова к некорректным данным в кеше, она может вообще перестать работать, пока не посчитает, что данные в кеше устарели. А если готова — продолжит пытаться выполнить тяжелый запрос в конкурентном режиме и не восстановится до тех пор, пока не посчастливится записать корректные данные в кеш, или пока не упадет нагрузка.

Как избежать условия гонки? Есть два способа.

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

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

    Оставить комментарий

Вруны из Теле2, или телефонный маркетинг

16 августа 2020 года, 22:56

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

Надо сказать, что я с 2004 года использую номер на Мегафоне как основной. Подключился к Теле2 ради мобильного интернета, так как тарифы Мегафона были слишком грабительскими. Приятным бонусом оказались включенные минуты на все мобильные номера московского региона.

Телефонист начал рассказывать о новом тарифе, какой он крутой, какие в нем бесплатные звонки на междугородние номера, как много в нем мобильного интернета. После этого спрашивает: ну что, переходим прямо сейчас?

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

— Вы мне сказали, что я сейчас плачу 240 рублей в месяц, а буду 350 рублей. Почему я должен это делать?

Телефонист стал объяснять, что я тратил больше 240 рублей в последнее время из-за междугородних звонков. Я ответил, что не готов дать согласие, так как мне нужно посмотреть детализацию звонков. Тут телефонист стал перечислять месяцы и затраты на мобильную связь, и мы потратили какое-то время на обсуждение дополнительных расходов.

— Ладно, вы сказали, что предоставляется скидка на полгода. Что произойдет через полгода? Я стану платить в два раза больше?
— Нет, мы видим, что вы не платите столько денег, потому не заинтересованы, чтобы вы платили больше.

Я отказался, и телефонист сообщил, что в течение месяца я смогу перейти на новый тариф. После звонка пришла смс:

И тут стало всё ясно: новый тариф стоит 700, а телефонист не просто завуалировал истинную стоимость предлагаемого тарифа, а соврал на прямо поставленный вопрос.

Никогда не ведитесь на такие «индивидуальные предложения» каких бы то ни было компаний. Они никогда не будут стараться ради того, чтобы брать с вас меньше денег. Если предложение действительно интересно, возьмите паузу и внимательно прочитайте условия.

И не жалейте людей на той стороне телефона. Набирая ваш номер, они не думают о вас. Они пытаются выполнить свой план, чтобы получить зарплату и премию. И, как показывает практика, делают это не всегда честно. Такие звонки отвлекают от дел и впустую отнимают время. Не стесняйтесь отвечать «мне это не подходит» или «мне это не интересно», и класть трубку.

    Оставить комментарий

Стили для печати и конвертация в PDF

26 июля 2020 года, 16:16

Постоянные читатели помнят, что у меня есть двухпанельный редактор математических текстов Upmath: слева пишете текст с разметкой на маркдауне и латехе, справа получаете результат.

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

Главная задача сервиса Upmath — подготовка математических текстов для публикации в вебе. Результат его работы — html-код. Если кому-нибудь нужен PDF, его можно получить с помощью самого латеха. На выходе будет превосходно сверстанный документ со всеми типографскими плюшками.

Но сейчас я задумался. Не всем нужен высококачественный PDF. Кто-то готовит текст самостоятельной работы для учеников и студентов. Кто-то распечатывает черновики, чтобы править ошибки. Что мешает получить PDF прямо сейчас?

Печатать можно напрямую из браузера. Хоть на настоящем принтере, хоть в pdf-документ. Я до сих пор не задумывался об этом, и не подготовил стили для печати (раньше вообще svg-картинки нормально не печатались). Получалась бесполезная страница с началом текста и его исходником:

Мне ничего не стоило добавить стили для печати. Результат сразу преобразился:

Качество получающихся документов мне не очень нравится. Я добился того, чтобы картинки не разбивались на две страницы. Но исключить разрыв страницы после заголовков у меня не получилось. Не понимаю, почему ни хром, ни FF не понимают инструкцию

h1, h2, h3 {
    break-after: avoid;
}

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

    Оставить комментарий

Переносим сессии при переезде между серверами

12 июля 2020 года, 23:29

Как-то нам нужно было перенести сессии PHP с одного сервера на другой. Сессии хранились в файлах. Серверы друг друга не видели. Но с рабочего компьютера оба были доступны. Решение — команда scp -3:

ssh 10.0.0.1 'sudo chmod go+r /var/www/project/var/sessions/prod/*'
ssh 10.0.0.2 'sudo chmod go+w /var/www/project/var/sessions/prod'
scp -3 user@10.0.0.1:/var/www/project/var/sessions/prod/* user@10.0.0.2:/var/www/project/var/sessions/prod
ssh 10.0.0.1 'sudo chmod go-r /var/www/project/var/sessions/prod/*'
ssh 10.0.0.2 'sudo chmod go-w /var/www/project/var/sessions/prod'
ssh 10.0.0.2 'sudo chown www-data:www-data /var/www/project/var/sessions/prod/*'
ssh 10.0.0.2 'sudo chmod go-r /var/www/project/var/sessions/prod/*'

После этого серверам переназначили ip-адреса. В итоге получился бесшовный переезд.

    2 комментария

Серебристые облака — 4

7 июля 2020 года, 00:10

Прошлой ночью опять наблюдал серебристые облака.

Попробовал заснять облака на видео. Получилось не очень впечатляюще. Вот ускоренная в 4 раза запись.

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

    Оставить комментарий
Смотрите также:  Серебристые облака — 5 · Серебристые облака — 3 · Серебристые облака — 2 · Серебристые облака

Видео Ирумы

5 июля 2020 года, 12:53

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

    Оставить комментарий

Очередь на основе PHP-FPM

27 июня 2020 года, 13:13

Применил на практике прием, когда асинхронная очередь обработки сообщений реализовывается через PHP-FPM по протоколу fastcgi. На удивление, всё заработало сразу, никакой наладки не потребовалось.

Обычно PHP-FPM обрабатывает запросы от веб-сервера, например, nginx. Но никто не запрещает обращаться к PHP-FPM напрямую. Если все доступные рабочие процессы заняты, сообщения в нем как раз и ждут своей очереди на обработку.

Положительные стороны:

Отрицательные стороны:

Чтобы сделать такую очередь, возьмите готовые библиотеки для общения по протоколу fastcgi, например, hollodotme/fast-cgi-client.

    Оставить комментарий

Серебристые облака — 3

24 июня 2020 года, 01:23

Сегодня серебристые облака красивее и насыщеннее.

В кадр попал самолет.

Обратите внимание на темное пятно. Это обычное облако. Обычные облака темнее фона, а серебристые — светлее.

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

    Оставить комментарий
Смотрите также:  Серебристые облака — 5 · Серебристые облака — 4 · Серебристые облака — 2 · Серебристые облака

← сюда туда →

Поделиться
Записи