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

О тестовых заданиях на собеседованиях программистов — В кресле препода №4

Я провел много собеседований. Несколько десятков, или даже около сотни. К сожалению, с самого начала подсчитывать не догадался. Точное число было бы интересным.

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

Кстати, видео снимал по методу Ильи Бирмана.

14 сентября 2020 года, 22:32     работа программиста · видео · в кресле препода     Оставить комментарий

Редактор математических текстов Mathcha

Искал онлайн-инструменты для редактирования картинок TikZ и наткнулся на редактор Mathcha.

Этот редактор — визуальный: вы сразу редактируете документ вместе с форматированием. В отличие от моего редактора Upmath, в котором вы редактируете исходник на маркдауне и латехе, хотя и сразу видите результат.

Вот рисунок, который я сделал с помощью Mathcha. Накидал основу в нем, экспортировал в TikZ и подправил исходный код уже в UpMath.

$$ \tikzset{every picture/.style={line width=0.75pt}} %set default line width to 0.75pt \begin{tikzpicture}[x=0.75pt,y=0.75pt,yscale=-1,xscale=1] %uncomment if require: \path (0,300); %set diagram left start at 0, and has height of 300 %Shape: Boxed Line [id:dp7642567693966007] \draw (130,80) -- (130,140) ; %Shape: Boxed Line [id:dp02318954146147889] \draw (130,150) -- (130,180) ; %Shape: Boxed Line [id:dp4638027067588357] \draw (130,190) -- (130,250) ; %Shape: Wave [id:dp03622557580885122] \draw [color={rgb, 255:red, 74; green, 144; blue, 226 } ,draw opacity=1 ] (215,80) .. controls (202.19,83.1) and (190,86.06) .. (190,89.5) .. controls (190,92.94) and (202.19,95.9) .. (215,99) .. controls (227.81,102.1) and (240,105.06) .. (240,108.5) .. controls (240,111.94) and (227.81,114.9) .. (215,118) .. controls (202.19,121.1) and (190,124.06) .. (190,127.5) .. controls (190,130.94) and (202.19,133.9) .. (215,137) .. controls (227.81,140.1) and (240,143.06) .. (240,146.5) .. controls (240,149.94) and (227.81,152.9) .. (215,156) .. controls (202.19,159.1) and (190,162.06) .. (190,165.5) .. controls (190,168.94) and (202.19,171.9) .. (215,175) .. controls (227.81,178.1) and (240,181.06) .. (240,184.5) .. controls (240,187.94) and (227.81,190.9) .. (215,194) .. controls (202.19,197.1) and (190,200.06) .. (190,203.5) .. controls (190,206.94) and (202.19,209.9) .. (215,213) .. controls (227.81,216.1) and (240,219.06) .. (240,222.5) .. controls (240,225.94) and (227.81,228.9) .. (215,232) .. controls (202.19,235.1) and (190,238.06) .. (190,241.5) .. controls (190,244.57) and (199.7,247.26) .. (210.89,250) ; %Straight Lines [id:da008457960885399407] \draw (190,80) -- (190,250) ; %Flowchart: Summing Junction [id:dp09695643217509597] \draw (135,132.75) .. controls (135,128.75) and (138.36,125.5) .. (142.5,125.5) .. controls (146.64,125.5) and (150,128.75) .. (150,132.75) .. controls (150,136.75) and (146.64,140) .. (142.5,140) .. controls (138.36,140) and (135,136.75) .. (135,132.75) -- cycle ; \draw (137.2,127.62) -- (147.8,137.88) ; \draw (147.8,127.62) -- (137.2,137.88) ; %Shape: Inductor [id:dp3454355331692156] \draw (35,155) -- (42.06,155) .. controls (43.68,155) and (45,156.12) .. (45,157.5) .. controls (45,158.88) and (43.68,160) .. (42.06,160) .. controls (43.68,160) and (45,161.12) .. (45,162.5) .. controls (45,163.88) and (43.68,165) .. (42.06,165) .. controls (43.68,165) and (45,166.12) .. (45,167.5) .. controls (45,168.88) and (43.68,170) .. (42.06,170) .. controls (43.68,170) and (45,171.12) .. (45,172.5) .. controls (45,173.88) and (43.68,175) .. (42.06,175) -- (35,175) ; %Straight Lines [id:da4690178114375858] \draw [dash pattern={on 0.84pt off 2.51pt}] (45,165.5) -- (130,185) -- (190,165) ; %Straight Lines [id:da10916309009890135] \draw [dash pattern={on 0.84pt off 2.51pt}] (45,164.5) -- (130,145) -- (190,165) ; % Text Node \draw (112,127) node [anchor=north west][inner sep=0.75pt] [align=left] {A}; % Text Node \draw (112,191) node [anchor=north west][inner sep=0.75pt] [align=left] {B}; \end{tikzpicture} $$

13 сентября 2020 года, 18:11     обзор · математика · upmath     Оставить комментарий

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

Как правильно добавлять данные в словарную таблицу с уникальными строками одновременно из нескольких потоков? В 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

30 августа 2020 года, 23:39     программирование · видео · в кресле препода     Комментарии (4)

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

В прошлый раз мы рассмотрели, как в теории работает кеш и какие ошибки обычно совершают при его программировании. В этот раз я расскажу, как с помощью нескольких настроек 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;
fastcgi_read_timeout 90;

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

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

28 августа 2020 года, 22:12     веб-разработка · upmath     Оставить комментарий

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

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

Кеширование не только экономит ресурсы и делает систему более отзывчивой. Без кеширования долгих операций система в принципе не сможет работать в режиме высокой нагрузки. Если вы разрабатываете кеширование в такой системе, важно помнить об одной частой ошибке проектирования — условии гонки (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-запросов. Способ универсальный и хорошо показывает себя при росте нагрузки. Но может потребовать доработку приложения, если архитектура не приспособлена к выполнению фоновых задач по расписанию.

20 августа 2020 года, 10:57     программирование     Оставить комментарий

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

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

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

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

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

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

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

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

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

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

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

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

16 августа 2020 года, 22:56     маркетинг · опсосы · lytdybr     Оставить комментарий

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

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

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

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

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

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

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

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

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

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

26 июля 2020 года, 16:16     upmath · интерфейсы     Оставить комментарий

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

Как-то нам нужно было перенести сессии 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-адреса. В итоге получился бесшовный переезд.

12 июля 2020 года, 23:29     линукс     Комментарии (2)

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

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

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

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

7 июля 2020 года, 00:10     фото · lytdybr     Оставить комментарий
Смотрите также:  Серебристые облака — 3 · Серебристые облака — 2 · Серебристые облака

Видео Ирумы

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

5 июля 2020 года, 12:53     видео · музыка     Оставить комментарий

← сюда туда →

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