• Книга о Postgres и JSON

    Поздравьте меня, что ли: в эту пятницу я закончил писать черновик новой книжки. Это будет книга о Postgres. Вся она посвящена одной специфичной теме: работе с JSON. Расскажу, как хранить в Postgres сложные документы, индексировать их, версионировать, делать отчеты, работать с ними из Python, искать разными способами и многое другое.

    Главы

    1. Введение в документы
    2. Базовые возможности JSON
    3. JSON в таблицах
    4. Индексирование JSON
    5. Ссылки и ограничения в документах
    6. Язык путей JSONPath
    7. Отчеты и функции
    8. Функции на языке Python
    9. Версионирование и архивация документов
    10. Релевантный поиск

    “Черновик” означает, что текст полностью готов, но пропущены места с кодом. Буду их заполнять. Далее верстка, обложка и печатная подготовка.

    Будет в следующем году, скорее всего летом.

  • Новая модель

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

    Ну и примеры. Какое мне дело, что в бенчмарке X модель набрала на пять баллов больше? Где примеры того, в чем конкретно она улучшилась?

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

  • Еще про API

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

    Часть параметров нужно взять из урла, например айдишки сущностей. Другие данные – из заголовков. Если это GET/HEAD, взять параметры query string. Если POST – читать multipart или парсить json. В итоге первые 5-10 строк кода уходят на то, чтобы выковырять данные из разных мест.

    Всегда поражало: почему никто не видит проблемы? Почему не заслать все в джейсоне через POST? Ради чего размазывать поля по разным местам?

    Я хочу, чтобы апишка была описана данными, например словариком с полем action и params. Все в одном месте, и дальше этот словарик разруливается по полю action. Под каждый словарик пишется схема и документашка. Что еще нужно?

    У этого подхода миллион преимуществ, вот хотя бы некоторые:

    • не нужно парсить урлы и query-параметры. Парсинг урлов, вычленение айдишек с приведением к числу – это сложно;

    • не нужно клеить урлы на клиенте. Это лютейшая боль: зачем вам /api/v1/users/123/orders/456 вместо {:user_id 123 :order_id 456}?

    • сообщенька отделяется от транспорта; становится неважно, как ее передали: по HTTP или иначе.

    • следствие 1: легко добавить новый транспорт. Фронтенд захотел веб-сокеты – пожалуйста, это делается за день. Напомню, что в веб-сокете никаких урлов нет, только данные.

    • следствие 2: очереди. Тяжелые сообщения можно складывать в кафки-реббиты.

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

    • пакетный режим: тот же JSON RPC позволяет заслать несколько сообщений за раз. Их можно обработать параллельно.

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

    Замечу, что под сообщением я имею в виду не очереди задач, а нечто другое. Сообщение – это набор данных, которые описывают действие. Удобно, когда все это хранится рядом.

    Я уж молчу о том, что в классическом REST вечно не хватает методов или они неочевидны. В одной фирме коллеги чуть не подрались, когда спорили, что использовать для изменения: PUT или PATCH. Самая жесть – это запросы, которые меняют сразу несколько сущностей. Там вся концепция ресурсов идет лесом.

    Сообщения удобней для понимания. Что легче читается: POST /api/v1/orders или CreateOrder? Другой пример: PATCH /api/v1/orders/123456/cancel или CancelOrder? То-то же.

    Сообщения используются не только в вебе. Например, любое обращение к Postgres – это обмен сообщеньками. У них общий контейнер: метка, длина и произвольное тело, которое парсится в зависимости от метки. Как только написал парсер сообщений, считай, клиент почти готов.

    В Кассандре, Кафке то же самое: заголовок/cooбщенька, заголовок/cooбщенька.

    Поэтому я уверен: нужно думать над сообщениями, а транспорт всегда найдется. Это вторичная вещь. Жаль, этого не понимают REST-маньяки со своими сваггерами, постманами, бест-практис и так далее.

  • Европейский закон о зарплате

    Пишут, что в 2026 году в Европе введут новые правила о зарплате. Вот неполный их список:

    • зарплата должна быть указана в любом объявлении о найме;

    • соискателя обязаны уведомить о зарплате еще до интервью и подписания договора;

    • разглашение зп не может быть поводом для увольнения или других воздействий;

    • сотрудникам можно запрашивать информацию о зарплатах других сотрудников;

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

    Может быть и врут, я особо не проверял. Но откровенно говоря – очень похоже на правду.

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

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

    Кстати, в этом коренная проблема Европы – тотальное регулирование. Именно поэтому Европа смотрит американский Нетфликс, ищет в американском Гугле, читает американский Твиттер, чатится в американском Вацапе, вызывает американский Убер и так далее.

    Где хостится европейский стартап? В американских AWS, Google Cloud, Azure. Куда едут условные финн Торвальдс и голландец Ван Россум? Где зарегистрированы фонды Линкуса и Питона? В Америке.

    Чтобы создать сервис, который захватит мир, нужно нарушить чьи-то права. В Америке обе стороны (фирма и работник) идут на это добровольно, и в результате американские сервисы повсюду. Европейские – нет.

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

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

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

  • Полоса прокрутки (2)

    Небольшое продолжение про Ютуб — и больше поднимать эту тему не буду.

    Всякими махинациями я добился того, чтобы полоса была под видео, а не на нем. Для этого классу ytp-chrome-bottom добавляется свойство bottom: -60px или около того. Но проблема пришла откуда не ждали, и даже не одна.

    Дело в том, что тулбар появляется только когда наводишь курсор на плеер. Так вот: если тулбар вне плеера, то наведение мыши на него ничего не дает — он остается невидимым. Простыми словами, перестает работать.

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

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

    Ну и в целом сложность: штук двадцать вложенных дивов yt-playeryt-main-containeryt-main-inneryt-video-containeryt-video-node и так далее. Каждый что-то перехватывает и наследует. Дебажить этот цирк — то еще удовольствие.

    Так что переносом тулбара вверх я пока и ограничусь. Слишком напряжно этим заниматься. Сложность Ютубного плеера колоссальная: там годы легаси и костылей, чтобы работать во всех браузерах, трекать, показывать рекламу, плашки, рекомендации и так далее. Задача мне не по зубам, признаю поражение.

  • Полоса прокрутки

    Некоторое время назад я жаловался на плеер Ютуба, мол, полоса прокрутки и кнопки залезают на видео. На это можно повлиять при помощи своего css-файла. Для Фаерфокса и его форков инструкция такая:

    • Определить папку с профилем. Для этого нажать Help -> More Troubleshooting Information -> Profile Folder -> Show in Finder

    • Создать в ней папку chrome

    • В ней создать файл userContent.css с таким содержанием:

    @-moz-document domain(youtube.com) {
     .ytp-chrome-bottom {
         position: relative !important;
         bottom: -15px !important;
     }
    }
    
    • Зайти в about:config и задать свойсво toolkit.legacyUserProfileCustomizations.stylesheets в true

    • Перезапустить браузер.

    В результате тулбар окажется сверху с зазором в 15 пикселей. Картинку прилагаю:

    Мысли следующие: пока настраивал папки-файлы, поразился этому дурдому. Почему ради простой задачи нужно совершать пять шагов? Неужели нельзя сделать примитивный textarea, куда вводишь текст и он сохраняется в файл? Почему браузер нужно перезагружать? Почему папка называется “Хром”? При чем тут хром? Какая-то жесть.

    В общем, когда стили заработали, я удивился как бухгалтер, у которого сошелся годовой отчет.

    Впечатления от новой полосы интересные. Чувство такое, что вместо 90% контента теперь видишь все 100%. Внезапно, надписи внизу читаются; у людей есть колени и ноги; у ведущего в углу кадра есть лицо. Раньше ничего этого не было, а теперь — пожалуйста.

    Могу спокойно читать субчики в “Зеленом слонике”, а раньше не мог. То-то же.

    Для Хрома и форков инструкции нет, но полагаю, найдутся расширения.

    Ни в коем случае не продавливаю свое решение — по-хорошему его надо улучить. Но какую-то свежесть оно дает, попробуйте.

  • Оружия черепашек-нинздя

    Разговаривал с одним человеком и выяснил, что он не знает, какие оружия у черепашек-нинздя. Это должен знать каждый, и я не сомневаюсь, что и вы знаете. Просто на всякий случай повторю.

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

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

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

    Последний уровень, найтмеар: что у Рафаэля, черепашки в красной повязке? Когда говорят, что трезубец, хочется кататься по полу от отчаяния. Это саи. С-а-и! Один сай, два сая, многое саев. Гугл-док не понимает и предлагает замену, но вы-то понимаете! Такие кинжалы с двумя лепестками по краям. Идея в том, чтобы принять меч противника между зубцами и резко повернуть. Твой рычаг больше, чем у противника, и он теряет оружие.

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

  • Дизайн REST API

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

    Во-первых, версия v1 в урле. Она всегда меня забавляла. Я работал во многих стартапах, и ни один из них не сменил версию. Гораздо чаще стартапы закрываются и продаются. Я считаю, апишка должна быть одна, и со временем какие-то методы добавляются, какие-то снимаются с обслуживания. А в той нумерации, что на скриншоте, слишком много пафоса. Примерно как объявить счетчик с шестью нулями для значения, которое в лучшем случае достигнет двойки. Даже если предположить, что появится новая апишка, просто сделай другой урл: /api/grpc или /api/graphql.

    Далее, иерархия. Те апишки, которые работают с матчем, требуют код турнира. Нужно передать две айдишки: турнира и матча. Зачем? Разве может матч относиться к разным турнирам? Это банальная избыточность, и делается она ради красоты. Должен передаваться только матч, а по нему легко найти турнир и проверить права.

    Еще одна забавная вещь: метод PUT не может покрыть все изменения, поэтому в ход идут костыли: частички :report, :reopen на конце урла. Это некрасиво, потому что по-хорошему операция определяется HTTP-методом, а тут урлы. Тот случай, когда шубу заправляют в трусы, а она не лезет.

    Ну и расцветка Сваггера как у цыган: зеленый, голубой, оранжевый, красный… все собрали.

    На мой взгляд, нормальная апишка должна быть по принципу RPC. Один урл с двумя параметрами: action и params. Метод один – POST. Внутри словарик с функциями и схемами, по которому раскидываются запросы. Все. Документашка со схемами строится одной командой.

    Апишка – довольно простая вещь. Она не должна быть сложной и требовать разного тулинга. Тот REST, что имеем сегодня – это вавилонская башня на ровном месте. Как и в библейской истории, разработчики сами не понимают друг друга. Но продолжают и продолжают строить эту башню.

  • Просто берите SQLite

    Тезис к размышлению: при помощи Postgres и SQLite можно решить (почти) все насущные задачи. Без распределенных кэшей, очередей, облачных паб-сабов и так далее.

    Насчет Постгреса все ясно: я уже писал о нем отдельно. А что SQLite?

    Замечаю одно и то же: иной раз требуется хранить в памяти какие-то данные. Сначала это словарь вида id -> сущность. Потом нужен поиск по полю сущности. Строится обратная мапа поле -> список сущностей. Потом нужная вторая сущность и перекрестные ссылки. Код превращается в ад.

    Наблюдаю такое в трех сервисах. Ребята читают из файлов сущности, строят прямые мапы, обратные, перекрестные… потом ходят по ним безо всяких проверок, ловят нулы и странные результаты.

    А решение простое: запиши свое барахло в SQLite! Из коробки получишь индексы, поддержку целостности, универсальный интерфейс, поддержку JSON и миллион расширений. И появился SQLite не вчера, а старше иного разработчика. И тестов под него написано чуть ли не сотня тысяч.

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

    Если данные не помещаются в памяти, можно скинуть их на диск. Красота же?

    Есть и другие рецепты использования SQLite, например использовать его для обмена данными. Каждый, кто сталкивался с сериализацией, знает, какая это боль и сколько в ней подводных камней. Один из способов их обойти – кидаться файлами SQLite.

    За счет SQLite можно выкинуть просто нелепое количество кода и самописных решений. Разумеется, не всегда – но по моему опыту, очень-очень часто.

  • О кложурных редьюсерах

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

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

    Как вы знаете, Кложа хороша своими коллекциями – это прямо алмаз, одно из немногих утешений в айти. Вместе с коллекциями прилагаются штук триста функций: всякие map, reduce и так далее, включая экзотику.

    Начиная с какой-то версии в Кложу завезли параллельные map и reduce. Пакет называется clоjure.core.reducers. Сигнатуры параллельных функций почти идентичны оригиналам. Идея в том, что ты такой хоп – заменил reduce на r/reduce, и вычисления раскидались по ядрам. Нашлись коллеги, которые, не читая документации, так и сделали – а я пожал их плоды.

    Есть огромных файл, где каждая строка – джейсончик. Нужно построить мапу {id -> entity}, чтобы быстро дергать из нее сущность по айдишке. Скажем, в Питоне это делается так:

    {entity["id"]: entity
      for entity in file.read_lines()
    }
    

    В Кложе это тоже решается тремя строчками. Но коллега использовал не простой reduce, а который параллельный. Логика такова: коллекция большая, пусть колбасится параллельно. И вот я вызываю функцию, которая строит эту мапу, и замечаю – в ней нет половины ключей. Пропали. Что такое?

    Дебажил я передебажил и выяснил вот что.

    В документаци clоjure.core.reducers написано, что коллекции не всегда обрабатываются параллельно. Критериев несколько, например коллекция мала и нет смысла ее делить. Но главный критерий таков: коллекция не должна быть ленивой. А те коллекции, что коллеги читают из файла, ленивы. Это нормально, потому что файл огромный и читать в память все разом нельзя. Но получается, что вся параллельность идет псу под хвост – параллельные r/map и r/reduce сводятся к последовательным аналогам.

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

    Почему же у меня возникла ошибка? Дело в том, что я передал вектор – не ленивую коллекцию. Она попадала под критерии параллельности, и произошло вот что. Пакет clоjure.core.reducers использует подход ForkJoin. На этапе Fork коллекция бьется на части, и каждая часть обрабатывается в своем потоке. Получаются, скажем, два словаря {id -> entity} для каждой части. Далее наступает фаза Join – их нужно объединить. Функция r/reduce принимает дополнительную функцию для сборки финального результата, но ее не передали. А если ее нет, вызывается редуцирующая функция. Она приняла два словаря и неправильно их обработала, в результате чего пропала половина данных. Нужно было передать туда функцию merge, но никто не знал, для чего это в принципе.

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

    {"a" [1 2 3], "b" [5 4 1]}
    

    Если дать эту задачу на параллельное вычисление, они могут вернуть что-то такое:

    {"a" [1 2], "b" [5]}
    {"a" [2 3], "c" [11]}
    

    То есть и в первом потоке был такой ключ, и во втором. Если тупо объединить словари, получится вот что:

    {"a" [2 3], "b" [5], "c" [11]}
    

    , то есть из “a” пропадет 1. Ошибки не будет, все пройдет молча, и догнать причину будет сложно. Правильная функция объединения будет такой:

    (partial merge-with into)
    

    С ней получим результат {"a" [1 2 3], "b" [5], "c" [11]}.

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

    В сухом остатке:

    • человек подключил либу для параллельных вычислений

    • вычисления всегда протекали последовательно

    • при попытке вычислить что-то параллельно получали ошибочный результат. Исключения нет, все тихо, ищи сам.

    В Гарри Поттере была фраза: “не доверяй тому, что мыслит, если не знаешь, где у него мозги”. Я бы перефразировал: не доверяй быстрым библиотекам, если не понимаешь, за счет чего достигается скорость. Реалии таковы, что в погоне за скоростью срезают углы. Это ни хорошо ни плохо, это факт, который нужно знать.

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

Страница 10 из 111