Минутка просвещения

Читать в телеге. Когда-то там были посты не только от меня.

Насколько уникален UUID?

Напомню, что UUID — это идентификатор вида ac2c6d9a-68d0-4802-948b-da8cc594ac80, который содержит 16 байт или 128 бит. При этом в 4 версии из этих 128 бит только 122 случайны (и генерируются обычно хорошим генератором случайных чисел), остальные 6 — это 4 для номера версии и 2 зарезервированных бита.

Чтобы оценить вероятность совпадения, можно воспользоваться формулой для “парадокса” дней рождения, а именно ее приближением:

p(n, b) =  1 - e^(-n(n-1)/2^(b+1)))

где n — количество сгенерированных элементов, а b — количество случайных бит (2^b — количество вариантов).

Дальше надо прикинуть, сколько будет UUID в системе. Пусть нагрузка на систему — 1000 RPS, и при каждом запросе генерируется новый UUID. Тогда за 1000 лет будет сгенерировано всего 31,5 триллиона идентификаторов, а вероятность, что будет пара одинаковых — всего 9*10^-11, чем можно спокойно пренебречь.

СсылкаКомментировать

Круглые скобки в Scala

Иногда слышу в качестве одной из претензий к Scala “почему для получения элемента из ассоциативного массива используют круглые скобки?”. В одном из докладов услышал интересную гипотезу на эту тему.

Что такое “map”? Вообще это карта, и имеет происхождение от латинского “mappa mundi” — “лист мира”. Если заглянуть в словарь поглубже, то там будет упомянуто еще математическое значение — синоним “mapping”, отображения [из одного множества в другое], что большинстве случаев можно считать синонимом функции.

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

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

СсылкаКомментировать

Action для коммита в другой репозиторий

У меня на сайте сейчас два способа подключения чего-то стороннего: через git read-tree и через git submodule. Теперь появился еще один: через github action, который пушит коммит в другой репозиторий. Этот вариант оказался самым удобным, жаль, что я на него наткнулся недавно и случайно.

Пример настройки и сгенерированный коммит.

СсылкаКомментировать

Будьте проще

Неплохой, хотя и немного спорный доклад про то, что надо делать вещи простыми, но это нелегко. Простота почти синонимична с изолированностью, модульностью. У простых вещей масса преимуществ — ими проще оперировать, их проще понимать, поддерживать, комбинировать (привет, UNIX-way).

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

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

СсылкаКомментировать

Запросы к jsonb

На SQL можно написать порой ужасные вещи, например:

SELECT id, jsonb_path_query_array(value, '$[*].entityType')
FROM demo WHERE value @? '$[*] ? (@.entityId == 1 &&
    (!exists (@.childrenIds) || exists (@.childrenIds ? (@[*] == 4))))';

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

А в запросе выбираются элементы массива, в которых есть объект с entityId и либо пустым childrenIds, либо содержащим 4. В ответе выдаются только значения полей entityType.

Полный пример
CREATE TABLE demo(
  id int not NULL,
  value jsonb Not NULL
);
INSERT into demo VALUES
  (1, '[{"entityId": 1, "entityType": "type1", "childrenIds": []}, {"entityId": 10, "entityType": "type5", "childrenIds": [30, 20]}]'::jsonb),
  (2, '[{"entityId": 1, "entityType": "type2"}, {"entityId": 15, "entityType": "type1", "childrenIds": [4, 20]}]'::jsonb),
  (3, '[{"entityId": 1, "entityType": "type3", "childrenIds": [5, 4]}, {"entityId": 25, "entityType": "type4", "childrenIds": [30, 4]}]'::jsonb),
  (4, '[{"entityId": 35, "entityType": "type4", "childrenIds": [35, 4]}]'::jsonb);

SELECT id, jsonb_path_query_array(value, '$[*].entityType')
FROM demo WHERE VALUE @? '$[*] ? (@.entityId == 1 && (!exists (@.childrenIds) || exists (@.childrenIds ? (@[*] == 4))))';

Поиграться можно тут. В ответе будет 2 и 3 запись.

СсылкаКомментировать

Local-first приложения

Мартин Клеппман (тот самый, который автор книги с кабанчиком) продвигает подход Local-first. Основная идея довольно простая: все действия должны по максимуму производиться локально на машине пользователя, а облако нужно использовать для синхронизации изменений. Когда программисты работают над кодом, им не нужно, чтобы постоянно было подключение к git-серверу, а если он взорвется, то можно будет поднять свой, и данные полностью под контролем. Так почему в каких-нибудь гугл-документах все не так?

Кроме того, с точки зрения архитектуры классический путь данных в приложении сейчас — это HTML-DOM → JS-фреймворк → REST → модель → сущность ORM → запись на диске, и большая часть кода — это конвертеры одного слоя в другой. В Local-First подходе предлагается оставить HTML-DOM → JS-фреймворк, оттуда писать в локальное хранилище, а всю синхронизацию вынести в фоновый режим. Сеть — ненадежный способ передачи данных, и в классических приложениях надо решать как технические проблемы, так и проблемы с UX. Для синхронизации данных можно использовать не привязанный ни к конкретной компании, ни к конкретным моделям данных фреймворк, основанный на CRDT (conflict-free replicated data type). Группа Клеппмана разрабатывает свой фреймворк для этого, Automerge.

Идея классная, технически интересная, и как пользователю мне она нравится, но увы, непонятно, кто готов будет за это платить. Тут, как минимум, две проблемы: единство протокола и бесконтрольность компании. С единым протоколом/форматом данных можно привести в качестве канонического примера миллиард мессенджеров, при этом плюс-минус единый стандарт — это XMPP (пользуетесь jabber’ом?), а еще и рабочий при этом — это email (которому уже больше 35 лет). А когда контроль над данными переходит от сервиса к пользователю, то автоматом всплывают проблемы с моделью продажи (это уже не SaaS, в котором можно сделать что угодно), тайной алгоритмов, стоимостью поддержки старых версий, аналитикой, обновлениями и т.п.. Кажется, что подобный подход к ПО пока могут позволить себе только энтузиасты от Open Source и единичные производители.

СсылкаКомментировать

JMX в Kubernetes

JMX (Java Management Extensions) — технология, с помощью которой можно подключится к java-процессу через сокет и посмотреть использование ресурсов, снять дамп памяти или даже поменять какие-нибудь значения в памяти через управляемые ресурсы (MBeans). Есть два плюс-минус стандартных инструмента для этого: jconsole и jvisualvm.

Но сейчас нельзя просто так взять и подключиться к чему-то по сокету — все в кубере лежит. А поды еще и со сгенерированными айдишниками. Сначала надо получить id пода от желаемого сервиса:

POD=`kubectl -n $NAMESPACE get pods -l app=$APPNAME -o name`

флаг -l фильтрует по метке пода, -o — оставляет в выводе только имя.

Потом получаем случайный порт (можно заморочиться и проверить, что он свободен):

PORT=`echo $(( $RANDOM % (65550 - 15000) + 15000))`
# или
PORT=`shuf -i 15000-65550 -n 1`

Перенаправляем порт ($JMX_PORT — номер порта, через который JMX выставлен локально в поде):

kubectl -n $NAMESPACE port-forward $POD $PORT:$JMX_PORT &

процесс запускается в фоне, и перенаправление будет работать только пока он запущен.

Наконец, запускаем jconsole или jvisualvm… и получаем облом из-за того, что удаленный порт-то не настоящий: нужно больше одного порта для подключения через RMI. Это можно вылечить работой через JMXMP-протокол, но нужно будет добавить его jar и в приложение, и в клиент. А после этого запустить так:

jvisualvm -cp:a jmxremote_optional.jar --openjmx "service:jmx:jmxmp://localhost:$PORT"

Или можно просто добавить в настройки приложения -Djava.rmi.server.hostname=127.0.0.1 и подключаться так:

jvisualvm --openjmx "localhost:$PORT"

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

СсылкаКомментировать

Null в SQL

Сколько результатов вернет этот запрос?

SELECT * FROM ABS(null) as r  WHERE (r >= 0) OR NOT (r >= 0)
Ответ

Правильный ответ — ни одного, потому что условие вернет ложь. А связано это с тем, что null обрабатывается по особому, почти как NaN: почти любое выражение с ним возвращает null: ABS(null) — это null, null >=0 и NOT null — тоже. Можно сказать, что SQL реализует тернарную логику, где любое выражение может быть правдой, ложью или неизвестным. Поэтому стоит очень аккуратно писать запросы для nullable колонок, а еще лучше избегать их, если это возможно.

СсылкаКомментировать

Когда нужно создавать корутины?

Интересный вопрос задали на форуме котлина: а как собственно стартовать корутины, если не рекомендуется использовать GlobalScope и runBlocking? TLDR: suspend fun main или явные CoroutineScope. А вообще в этом плане лучше читать статьи от Елизарова, он неплохо объясняет их нюансы (что неудивительно, с учетом того, что он сейчас лид разработчиков Kotlin).

СсылкаКомментировать

Цвета организаций

Некоторое время назад была популярна тема деления организаций на “красные”, “янтарные”, “бирюзовые” и т.п., во многом благодаря книге 2014 года “Открывая организации будущего”. В ней консультант McKinsey Фредерик Лалу описал разные подходы к управлению компаниями, во многом опираясь на спиральную динамику (модель эволюционного развития людей) из 60-70-х, в которой уровни мышления разбиты на несколько уровней. Спиральная динамика, в свою очередь, во многом опирается на теорию циклического развития систем и теорию иерархических потребностей Маслоу (та самая пирамида, которой не было в оригинальной статье 1943 г.). В некоторых статьях это все еще щедро удобряется типами личности и прочей соционикой.

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

СсылкаКомментировать