Фильтр публикаций


Зашёл в доку flatbuffers, не узнал. В прошлом году не так модно выглядела.


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

UPD: о, что-то не ровно посчитал, похоже придётся срезать с правого конца немного(
UPD2: да и внизу чуть больше вышло, как так то, там не спилить уже. Надеюсь если первая норм будет, то вторую уже с исправлениями замоделирую :). Точнее я пока всё равно внутренние шестиугольники печатаю группами, нужно будет рамку под них подогнать, отцентрировав соты.


До этого я моделировал максимум карты для Counter-Strike в далёком детстве и какие-то домики в SketchUp, не помню уже зачем.

А тут познакомился с параметрическим моделированием, что выглядит как очень приятная магия.

Мы рисуем от балды отрезки, расставляем точки, дуги, прочее. А затем накладываем на них ограничения, говоря, например, что вот те два отрезка должны быть параллельны, а вот те перпендикулярны. Радиус окружности должен быть вот такой, а вот тот отрезок касается этой окружности по касательной. Точка должна быть по центру между двумя другими и сама лежать на другом отрезке.

С каждым ограничением число степеней свободы уменьшается и двигая что-нибудь видим, что ещё нужно ограничить. Пока не получим зелёненькое Fully constrained. Меняя размеры/углы оно автоматически перестраивается как нужно.

И поверх этого всего ещё и можно использовать формулы, вычисляющие размеры по размерам определённым в других скетчах, как явно заданные, так и измеренные между сущностями.

Если бы интерфейсы верстались такими штуками, то жизнь бы стала налаживаться :)


Про то как мне понравилось 3D-моделирование всё-таки позже расскажу, спать пора 🙂


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

#diffbelt


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

Таким образом оно будет не только тестировать корректность wasm'а, но и корректность самих трансформаций и работу базы. Такие тесты всё равно когда-нибудь нужно будет писать (у меня есть тесты на базу, которые её запускают целиком и проверяют что методы работают как нужно, есть отдельные тесты на прогон транформаций (но без запущенной базы), а вот будут и полные, которые прогоняют трансформации на живой базе). И тут сильно меньше работы получается.

Всё что будет нужно — писать human-readable функции для коллекций (они пригодятся в будущем, когда буду UI делать, чтобы глазами на коллекцию смотреть) и... всё. Тесты эти будут писаться wasm-функцией, в итоге она сможет переиспользовать механизм запросов который будет доступен из трансформаций, единственное что нужно сделать кроме — дополнительную функцию запуска трансформаций, но это тривиально. И не нужно писать human-readable функций для промежуточных типов данных, они будут прятаться внутри вычислений и проверяться только итоговый выход (и его тоже проще будет проверять, т.к. не нужно будет сериализировать-десериализировать их туда-сюда, а просто сравнить либо побайтово, либо структурно, смотря что там за значения лежат в коллекциях.

Не уверен что найду сильно много вечеров/выходных чтобы плотно этим всем заняться, я тут ещё и Path of Exile 2 скачал, плюс новый DLC Factorio всё ещё не пройден, но по чуть-чуть постараюсь возвращаться.

Запишу себе тут план, чтобы видеть путь и свет в конце туннеля:

◾️ Написать flatbuffers-схемы запросов которые могут понадобиться для тестов (старт поколения, запись значений в коллекцию, коммит поколения, чтение коллекции, по сути и всё, наверное и взятие диффа между поколениями не нужно пока)
◾️ Реализовать в cli команду инициализации базы согласно определённым в конфиге коллекциям (странно, я думал она была)
◾️ Дополнить парсинг конфига, при виде специального типа тестов создавать временный каталог, стартовать новую базу в нём, создавать коллекции
◾️ Сделать новый wasm-биндинг запуска трансформации по имени из конфига (такая команда в cli уже есть, нужно просто вызвать нужную функцию)
◾️ Написать самих тестов, которые создадут новые записи в изначальной коллекции, попросят запустить агрегацию, прочитают итоговую, сравнят с ожидаемыми значениями, промутируют коллекцию, снова запустят, посмотрят что обработка диффа тоже работает
◾️ Тесты ожидаемо упадут, т.к. у меня не реализована пока сама логика этих агрегаций, реализовать их
◾️ Всё ещё будут падать, т.к. не реализован механизм запросов, реализовать их (нужно будет помимо json-апи поддержать и flatbuffers)
◾️ Должно будет сойтись

Ну а когда всё-таки осилим это, сделаю cli-команд для чтения коллекций и перейду к реализации взаимодействия с телеграм-ботами по схеме https://t.me/alexandersmind/521 , т.к. у меня помер антиспам-бот (форк Shieldy с фичей кастомных вопросов вместо каптчи) и не особо хочу его чинить (там что-то мутное с нарушением уникальных ключей в монге, я так и не понял). Реализую его новую версию, выкинув все фичи кроме совсем необходимых, запущу, посмотрю, нужен ли он ещё кому-нибудь. Он будет генерировать какое-то количество метрик (ничего криминального, никаких сообщений не логирую, только количества, тайминги), которые пообрабатываю всей этой ерундовиной. Только wasm попробую не на расте писать, хочу Zig ещё попробовать, а воркер на Go бахну, давно на него не смотрел и кажется подойдёт для того чтобы без конца ходить в апишку за изменениями, парсить и класть задачи в коллекции, которые затем будут обрабатываться трансформациями.

#diffbelt


9 месяцев не было #diffbelt, жесть. Ничего практически не делал, словил какой-то сильный fatigue относительно него. А потом переезд, покупка 3D-принтера и как-то отложилось. Про 3D в следующем посте немного напишу.

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


- name: parsed-log-lines:1d
human_readable:
wasm: example
key_to_bytes: parsedLogLines1dKeyToBytes
bytes_to_key: parsedLogLines1dBytesToKey
value_to_bytes: parsedLogLines1dValueToBytes
bytes_to_value: parsedLogLines1dBytesToValue


Затем описываешь транформации, например:


- name: parsed_lines_1d
source: parsed-log-lines
target: parsed-log-lines:1d
reader_name: parsed-log-lines
aggregate:
wasm: example
map: aggregateMap
initial_accumulator: aggregateInitialAccumulator
reduce: aggregateReduce
merge_accumulators: aggregateMergeAccumulators
apply: aggregateApply
human_readable:
wasm: example
bytes_to_target_key: parsedLogLinesBytesToTargetKey
bytes_to_mapped_value: parsedLogLinesBytesToMappedValue
mapped_value_to_bytes: parsedLogLinesBytesToMappedValue
bytes_to_accumulator: parsedLogLinesBytesToAccumulator
accumulator_to_bytes: parsedLogLinesAccumulatorToBytes


Где ещё кучка таких функций, а map/initial_accumulator/reduce/merge_accumulators/apply реализуют саму логику агрегации и работают с байтами.

И затем идёт секция с тестами, где для каждой из функций можно накидать значения на вход и ожидаемый выход. Которые распарсятся, превратятся в байты, подёргают wasm-функций, получат результат, сериализируются в строки и сравнятся с ожидаемым. Для map/reduce это довольно тривиальная задача и требует только чтобы были определены у самих коллекций функции человекочитаемости. А для агрегаций это ещё вот пачка функций которые нужно написать.

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


За всю жизнь реализовал, наверное, больше двух десятков вариаций батчилок (реализовал бы одну и сделал библиотекой, да каждый раз вокруг то mobx, то redux-thunk, то redux-saga, то effector вообще) — классов, которые параметризируются чем-то, что берёт из очереди сколько нужно задач, выполняет их пачкой (делает запрос, например) и выдаёт результат для каждой задачи отдельно. С разными вариациями в виде настроек времени между пачками, throttle/debounce для сбора пачки, поддержки отмены задач, объединения одинаковых задач добавленных несколько раз по уникальным ключам, кастомизации сбора пачки в случаях когда нельзя в одну пачку складывать данные разных категорий (и не хочется делать для них отдельные экземпляры очередей), может ещё что-то было.

И мне нравится когда в реакте ты можешь просто в месте где тебе нужны какие-то данные вызвать хук/обернуть компонентом и когда оно отрендерится (и, опционально, будет видно на экране) — добавит задачу в очередь, множество таких мест на экране соберутся в одну пачку, игнорируя дубли, сделают один запрос и покажут нужные данные.

Как мне кажется это более правильный путь, чем пытаться во всех местах где мы получаем данные искать места где доступен весь список сущностей и делать один запрос там, без сложной машинерии очередей и группировки. Особенно не хочется думать о том, что делать, если часть исходных данных поменяется и нужно повторить запрос только для изменённых сущностей, придётся делать неизвестно что (чаще всего проще забить и перезапросить вообще всё), а в варианте с одиночными запросами всё просто — зависимости изменились, снова добавились в очередь, автоматически объединились с прочими запросами и запросили только то, что нужно.

#фронтенд #react


https://www.architecturalkatas.com/about.html
https://www.architecturalkatas.com/rules.html
https://www.architecturalkatas.com/kata.html (там их много если рефрешить)

Это они прикольно придумали 🙂


Репост из: Мои страх и ненависть
https://cantunsee.space/

Микроквиз на умение искать проблемы дизайна. Делитесь результатами


Если вдруг у вас где-то запущен SOCKS5, а вам нужен SOCKS4, либо вообще хотите даунгрейднуть его до HTTP-прокси (или не особо хотите, но там где вы хотите его использовать просят такой), то есть тула gost которая без проблем собирается в один go build и которой можно делать так:


gost -L http://127.0.0.1:8888 -F=socks5://user:password@12.34.56.78:8090


Что сделает локальный HTTP-прокси на 8888 порту через socks-прокси где-то далеко (и там целая куча других вариантов и можно использовать в разных комбинациях).


Когда услышал про regreSSHion и нет ключей от своего серверочка, а подключение говорит remote software version OpenSSH_8.9p1.

Надеюсь не поздно :)


Репост из: Random Rust Dev
Когда надоело говорить "боровить", говори "реквизировать"


Статическая типизация это добро для рефакторингов.

Поменял тут &[u8] на AlignedBytes, а затем просто компилировал и фиксил, пока не собралось.


Чот по привычке уже сижу в этой cli ветке, кстати, хотя пару команд у него уже есть и осталось только допиливать, мержанул, нафармил квадратиков :)

Хотя теперь ридми скорее всего врёт, да и нужно про остальное дописать будет как-нибудь. Но вы поняли, когда я это сделаю: попозже)


Переделал фантомные записи, чтобы они были валидными только для следующего поколения которое мы создаём, т.к. у меня не было никакого механизма по их удалению. Теперь когда фантом создаётся — его идентификатор сохраняется в коллекции, а при коммите/отмене поколения увеличиваем идентификатор последнего фантома до максимального из существующих.

Кое-как впихнул в query-запросы код который когда видит фантом с идентификатором меньше разрешённого — удаляет его.

Затем попытался в диффы подпихнуться, но там как-то сложно сильно всё оказалось, решил заодно итератор который ходит по записям RocksDB переписать, чтобы во-первых заиспользовать raw-итераторы, которые выдают &[u8] вместо Box для ключей/значений, во-вторых чтобы оно прозрачно у себя там проверяло фантомы эти и если нашло — то запомнило что его нужно удалить и выдало следующее значение.

Но не получилось, тесты свалились. Разбирался-разбирался, плюнул, решил диффы совсем переписать. Второй раз заиспользовал FSM/FSA подход для выделения чистой логики нетривиального алгоритма от конкретных кусков его выполнения (первый раз был для трансформаций). Забил на I/O, определился какое состояние у меня должно быть, сделал два енама: Action и Input и начал обрабатывать приходящий Input в зависимости от текущего состояния, выдавая в результате Action. И они простенькие довольно, вроде «установи курсор вот на такую запись, дай мне ключ который там нашёлся, а так же дай следующий изменённый ключ». Если интересно, можно откуда-то отсюда посмотреть https://github.com/anfivewer/diffbelt/blob/cc747e642a873cbc7ff8e3d86815d0ba1c806dd0/crates/diffbelt/src/raw_db/diff_logic/mod.rs#L245 , там по методам более или менее понятно, что происходит. Поленился только для будущего себя комментарий написать, почему всё именно так.

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

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

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

Сейчас нужно научиться из васма считать перцентили, чтобы догнать функциональность прототипа, а так же сменить источник данных которые используются в качестве примера. Пока использую куски логов телеграм бота одного, а хочется сделать в самом diffbelt логирование наконец, чтобы понимать что происходит, сделать жёсткий режим логирования в режиме трассировки и скормить логи diffbelt самому diffbelt, нарисовать графичков разных по временам исполнения/прочему, которые в любом случае для мониторинга пригодятся. Так же нужно написать e2e тестов, которые заставляют систему сгенерировать пару килограмм логов, обработать их, что сгенерирует уже пару тонн логов, обработать их, а затем сравнить результат с тем, что было более тупыми алгоритмами посчитано.

Ну и если всё сойдётся, то по фану реализуем эту штуковину для взаимодействия бота с телеграмом https://t.me/alexandersmind/521 , запилю бота к фиесте (давно данные не обновлял, кстати), может и антиспамного бота перепилю, а там посмотрим 🙂

#diffbelt


Это, кстати, в тему о том, что на не сильно большом количестве элементов лучше забыть о всяких больших O() и что вставка в начало/середину массива это O(N), ужас-ужас, нужно на связный список переделывать.

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


Решил таки жить с последним вариантом и не искать сериализатор, который даст мне штуковину, как, например, у rkyv с AlignedVec, который решает эту проблему (да и альтернатив не особо видно, плюс не хочется сейчас формат сериализации менять).

Написал три бенчмарка, надеюсь нигде не протупил с black_box() и оно ничего не оптимизировало, результаты там такие:


test bench_serialize ... bench: 2,825 ns/iter (+/- 65)
test bench_deserialize ... bench: 2,385 ns/iter (+/- 56)
test bench_deserialize_unchecked ... bench: 410 ns/iter (+/- 12)
test bench_move_1 ... bench: 44 ns/iter (+/- 0)
test bench_move_2 ... bench: 44 ns/iter (+/- 1)
test bench_move_3 ... bench: 44 ns/iter (+/- 0)
test bench_move_4 ... bench: 44 ns/iter (+/- 0)
test bench_move_5 ... bench: 44 ns/iter (+/- 0)
test bench_move_6 ... bench: 44 ns/iter (+/- 0)
test bench_move_7 ... bench: 44 ns/iter (+/- 0)


Я сериализировал/десериализировал структурку с массивом из 16 элементов, в каждом из которых два 64-битных числа, строка на 32 байта, 64 случайных байта. Итого получилось примерно 2 килобайта данных.

И сделал 7 штуковин, которые эти два килобайта перемещают внутри вектора на n байт влево. Как видим — это пыль совсем. Т.е. мы можем спокойно на всякий случай префиксовать данные местом под потенциальное выравнивание, а перед тем как нам это нужно читать — проверить, и если не повезло (чего по идее вообще не будет происходить в большей части случаев), то просто сместить. Возможно на больших объёмах оно становится хуже, но скорее всего не сильно значительно, да и если будет беспокоить — тогда и посмотрим/форкнем библиотечку, сделаем эту замену вектора на вектор пачек байт, предложим PR, все дела.

#rust


Теперь жить стало ещё страшнее. Думаю как бы вкорячить возможность просить из васм-кода делать запросы, само апи у меня сейчас JSON'овое, хочу приделать ещё flatbuffers, во-первых судя по flamegraph сэкономлю процентов эдак 10 на парсинге json'ов, во-вторых ещё сколько-то на base64, в-третьих оно покомпактнее будет, в-четвёртых схема всех методов более наглядно будет описана, в общем, выглядит как хорошая идея.

А затем по коже пошёл холодок, когда осознал, что до этого мне просто очень сильно везло, когда я использовал метод, который создавал Vec и копировал туда байты результата сериализации.

Смотрите, flatbuffers работает так: даём ему (либо он сам создаёт) пустой Vec, используем кодогенерированные структурки чтобы записать какие-то данные, передаём в сериализатор WIP-объект (выдаются после записей структур) финального узла и финализируем сериализацию. После этого у нас есть три вещи: Vec держащий байты, head: usize говорящий с какого байта идут сериализированные данные, len: usize говорящий сколько там байт этих данных.

Иногда мне приходится копировать эти данные (например, из памяти васма, к которой относительно сложно обращаться безопасно, но думаю позже наворочу ансейфа и местами перестану копировать, а всё-таки возьму указатель и напишу себе каких-нибудь обёрток, которые будут гарантировать, что не натворю делов; но пока время этих оптимизаций не пришло, по тому же flamegraph видны другие проблемы, да и я пока не распараллелился даже). Ну так вот, я и делал Vec::from(&buffer[head..(head+len)]), который брал эти сериализованные данные и помещал в новый Vec, красота.

Но у нас опять контры с выравниваниями получаются. При валидации/чтениях этих сериализованных данных flatbuffers может запаниковать, сказав что я дурачок и дал ему невыровненные байты. Т.е. был там какой-нибудь u64, требующий выравнивания в 8 байт, был head = 5 чтобы гарантировать, что адрес где находится этот head правильно выровнен, чтобы все остальные адреса были хорошими, а я взял и всё сломал, сдвинув на пару байт в сторону.

И принимая эти сериализированные данные в виде байт по сети, если я запишу их в обычный Vec — нет гарантий, что всё сойдётся. u8 требует выравнивания в 1, переопредилить его нельзя. Чисто в теории можно создать Vec со структурой в восемь байт, но работать с этим потом будет ну совсем неудобно.

Хотел по коду std найти, почему оно не сломалось у меня, но за пару минут не вышло, бросил. Вероятнее всего потому что текущий аллокатор да ещё и на 64-битной системе старается всё с 8-байтовым выравниваем создавать, не знаю.

Что делать? Не знаю, как это другие делают, но я пожалуй добавлю себе в утилитки структуру аля AlignedBytesData(Vec) (где ALIGN < 255, но на практике пока только 8 нужно, ибо во flatbuffers нет 128-битных чисел), которая будет делать следующее:

◾️ Если нужно скопировать сериализированные данные, то аллоцирует вектор с размером данных + ALIGN байт. Узнаёт адрес первого байта, записывает в первый байт ALIGN минус остаток от деления на ALIGN, с этого смещения и записывает байты.
◾️ Если нужно прочитать — читает первый байт, узнаёт смещение, отдаёт слайс начиная с него и до ALIGN минус смещение с конца.

Завтра ещё в чатике спрошу по этому поводу и возможно в 64-битной системе это лишний оверхед, но я ведь живу и в 32-битном васме, который вполне может взять и аллоцировать вектор на полуслове, в который я затем запишу данные и всё развалится.

UPD: эта схема не прокатит из-за реаллокаций памяти вектора, после которого смещение может оказаться сломано.

#rust


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

Теперь посплю и попробую собрать не тесты, а выполнение этого добра на реальных данных :)

#diffbelt

Показано 20 последних публикаций.