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

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

No data в Grafana

Некоторое время назад наткнулся на довольно противный баг в графане: добавил дашборд, вижу данные, потом меняю интервал на более продолжительный… и графики исчезают, вместо них вижу “No data”.

Сначала грешил на комбинацию нестандарного способа запихивания данных и кэширование, с которым вроде не все так просто в этом Mimir (ссылки потерял, поверьте, вы все равно не хотите это знать). Но реальность оказалась еще хуже: все данные на месте, просто… не запрашиваются.

Количество точек в запросе для отображения графика зависит от размера окна (sic!). Если быть точнее, то от ширины панели. При запросе к Prometheus вычисляются значения для точек start, start+interval, start+2*interval, … end. interval — это период времени, деленный на количество точек. Если значения для какой-то точки нет, то Prometheus возвращает самое новое значение из предыдущих, но только если оно не старше какого-то периода (иначе метрика считается без данных, если она не обновлялась). У меня метрики отсылались 3 раза в день, вот и получалось, что данные просто “не находились”. Я это полечил костылем, выставив Max data points = 11000 (больше нельзя) и Min interval = 30m в настройках запроса.

Вроде как у каждого шага есть обоснование, почему это должно быть так, и в типичном сценарии, когда метрики опрашиваются каждые 5 секунд, все будет работать из коробки, но епрст… Мне даже официальная поддержка ответила копипастой отсюда, официального и подробного туториала я по этой теме не смог быстро найти. И вообще, совсем не ожидал, что так сложно будет нарисовать график по точкам…

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

Компилируемые и интерпретируемые языки

Перебросили мне тут вопрос про эту классификацию. Первая мысль — “ну это же очевидно”: компилируемые компилируются, например C++, а интерпретируемые построчно исполняются, например питон! Но потом появляется какой-нибудь .pyc файл — скомпилированный байт-код. И возникают вопросики — а чем это от java тогда отличается (ведь там тоже байт-код)?

А потом можно вспомнить про существование REPL почти для каждого современного языка и простая классификация вообще сыплется. Ошибочно говорить “язык компилируемый”, более корректно говорить “Y реализация языка X имеет компилятор”. Еще одна сложность — это JIT. Вроде как компилирует, но во время исполнения уже… А если машинный код программы запускается на эмуляторе или в виртуалке — можно ли считать ее по-настоящему скомпилированной?

Вообще, если посмотреть википедию про интерпретаторы, то там будет JVM.

И компилятор, и интерпретатор производят практически одни и те же операции, чтобы в итоге получить из исходного кода машинный. Как у многих классификаций, четкой границы тут нет, скорее спектр вариантов. В википедийной статье достаточно разносторонне рассмотрен этот вопрос: процесс разработки (надо ли ждать компиляции), развертывания (build once run everywhere), производительности и т.п. А еще можно погрузиться в шаблонные интерпретаторы, микрокод и т.д.

Так что я бы сказал, что разделять языки на “компилируемые” и “интерпретируемые” занятие неблагодарное и даже немного вредное. В конечном счете почти любая программа компилируется (транслируется из одного языка в другой), а потом интерпретируется (исполняется).

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

Стоимость боксинга в Scala

Отличная статья с бенчмарками. Для справки: боксинг — это замена примитивного типа (int) на ссылочный (Integer) или, в более общем случае, просто увеличение уровня косвенности (когда чтобы добраться до “настоящего” значения нужно больше ссылок).

TLDR:

  • Стоимость боксинга зависит от JVM и оптимизаций.
  • Opaque-типы почти бесплатны
  • ФП-стиль “дороже” императивного при использовании OpenJDK.
  • Боксинг довольно хорошо оптимизируется в GraalVM, с ее использованием почти нет разницы, в каком стиле писать.
СсылкаКомментировать

Почти всё, что надо знать знать про кодировки

Отличный доклад, в котором рассказана основная история вопроса от первого телеграфа до UTF, а также освещены основные моменты про управляющие символы, диакритику, виды нормализации, UTF-16 vs UTF-8, эмодзи, флаги и т.п. Подано хорошо и с юмором, рекомендуется к просмотру детям программистам всех возрастов.

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

PRQL

SQL — это sequel, а PRQL — это prequel. Как и положено, второй появился позже первого и эксплуатирует его наследие:)

Довольно занятная штука. Писать что-то длинное в SQL не очень удобно из-за неестественного порядка записи — это как на питоне пробовать в функциональном стиле писать с filter и map (хотя можно привыкнуть). PQRL нацелен решить эту проблему и подобно тому, как TypeScript транслируется в JS, может быть транслирован в обычный SQL. Его даже в ClickHouse добавили.

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

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

Варианты реализации совместимости типов

Статические системы типов можно условно поделить на два вида: номинативные и структурные. В номинативных системах совместимость и равенство типов определяется по имени. Классический пример — java. Вот есть два класса:

class Duck {
	String quackSound;
	void quack();
}

class Quaker {
	String quackSound;
	void quack();
}

С точки зрения java — это два разных типа: имя разное, и пофиг, что начинка одинаковая. А вот в структурной системе типов это будет считаться одинаковым типом как раз потому что структура одинаковая. Структурную систему типов имеет TypeScript и этим он достаточно сильно отличается от других “мейнстримовых” языков. Джависты в шоке от того, что можно делать с помощью TypeScript, а кто-то даже предложил на нем тесты писать для java-проекта (sic!).

Еще есть утиная типизация — ее типы вообще не заботят, а только поведение. Вот такой класс

class McDuck {
	Int wealth;
	void quack();
}

ни по имени, ни по структуре не совпадает ни с одним из предыдущих. Но в Python все три класса будут совместимы, если от них нужен только quack.

Разумеется, почти все практические языки не на 100% строги. В какой-нибудь Scala вполне спокойно можно организовать структурную типизацию.

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

Управление зависимостями в Gradle

Хороший доклад про то, почему нельзя просто так взять и добавить зависимость от чего-то, чем отличается maven и gradle с точки зрения решения конфликтов, зачем нужны api и implementation, и как можно решить проблему с несколькими реализациями логгеров.

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

Миграция на Kotlin multiplatform

Мигрировал проект про статистику пулл-реквестов GitHub с Kotlin/JS на multiplatform. На удивление, сама миграция прошла довольно гладко — немного пошаманить с билд-скриптом, переименовать папочки и готово.

Решил заодно попробовать добавить native-реализацию. Проблем было меньше, чем я ожидал:

  1. actual/expect использовал только для тестов: чтобы сделать обертку для запуска корутин и чтобы загрузить ресурсы. Вообще, по-хорошему, обе эти функции должны из коробки идти.
  2. Для работы с файлами в стандартной библиотеке ничего нет. Есть kotlinx-io, но она нормально не завелась из-за проблемы с импортом AutoClosable. Ловя флешбеки от качества библиотек Jetbrains, я в итоге использовал Okio. Не могу сказать, что API великолепно, но хотя бы работает.
  3. Называть платформу native — небольшое лукавство. Это скорее POSIX, если судить по поддерживаемым платформам, и это местами откровенно торчит.
  4. Ktor клиент не смог сам подтянуть из зависимостей себе HttpClientEngine и кинул не очень помогающую ошибку. Оказалось, что для native есть несколько реализаций и надо явно указать нужную. Почему нельзя это сделать на этапе компиляции, не ясно.
  5. Больше всего времени потратил на десериализацию JSON (sic!). Предыдущий костыль перестал работать с унылым ArrayIndexOutOfBoundsException. Я уже раз в четвертый пытался понять великую задумку kotlinx.serialization, даже пробовал сделать изолированный баг-репорт, но ничего толком не вышло. В итоге поменял один костыль на другой: избавился от шаблонных классов и сделал класс-композицию.
  6. Старые проблемы остались по большей части в силе.
  7. Наконец-то использовал по делу \r! Он весьма пригодился для простенького отображения прогресса.

Несмотря на проблемы, в целом получилось норм. Можно улучшить валидацию ввода и добавить альтернативные способы вывода результата, но решил, что там все понятно и не стоит тратить на это время.

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

Атака на мультимодальные нейронки

На днях вышла занятная статья. Я надеюсь, многие помнят про adversarial атаки, когда на картинку панды накладывали шум и нейронка распознавала ее как гиббона или когда из-за наклейки банан распознавался как тостер. Так вот, в статье сделали что-то подобное для мультимодальных GPT (которые понимают не только текст, но и картинки): на изображение или звук накладывается наклейка или шум, и нейронка выполняет дополнительные действия: всегда упоминает корову, вставляет вредоносную ссылку в ответ, представляет себя пиратом и т.д. Рекомендую посмотреть хотя бы картинки с примерами.

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