Pull to refresh
22
12.3
Send message

Как скормить базе данных список из 10К офисов


10K это очень маленькие данные для базы данных.

Liquibase is an open-source database schema change management solution

По факту это инструмент для миграции, а не для данных.

Если заголовок прочитать, как: Сделать миграцию 10K записей, через Liquibase" то норм.

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

В Питоне это даже внесли в синтсксис языка. Можно сделать такую сигнатуру функции, когда вызов будет только в таком виде.

def myfunc(*, height, speed):
    print(f"{height=} {speed=}")

myfunc(height="speed", speed="height")
myfunc("speed", "height") # TypeError: myfunc() takes 0 positional arguments but 2 were given
myfunc(height="speed") # TypeError: myfunc() missing 1 required keyword-only argument: 'speed' 

А если миллион?

А давайте попробуем. Возьмём Питон, он не далает оптимизаций, и в черезмерном быстродействии не был замечен.

Цикл на миллион, запущен 100 раз. То есть 100 миллионов вызовов. Звучит серьёзно.

С дополнительной функцией 2.993393 секунд
Без дополнительной функции 2.991126 секунд

Итого 0.003 секунды разницы на одном из запусков.

Погрешность мужду запусками примерно 0.002. Сопоставимо с разницей.

from timeit import timeit


def foo():
    result = 0
    for x in range(1_000_000):
        result = result + x
    return result


print("Foo", timeit("foo()", number=100, globals={"foo": foo}))


def goo(a, b):
    return a + b


def boo():
    result = 0
    for x in range(1_000_000):
        result = goo(result, x)
    return result


print("Boo", timeit("boo()", number=100, globals={"boo": foo}))

Проверка на то, что там реально миллион вызовов.

from profile import run
run("boo()")
         1000005 function calls in 0.766 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.766    0.766 :0(exec)
        1    0.000    0.000    0.000    0.000 :0(setprofile)
        1    0.000    0.000    0.766    0.766 <string>:1(<module>)
        1    0.000    0.000    0.766    0.766 profile:0(boo())
        0    0.000             0.000          profile:0(profiler)
  1000000    0.375    0.000    0.375    0.000 scratch_15.py:14(goo)
        1    0.391    0.391    0.766    0.766 scratch_15.py:18(boo)

Лишние вызовы. Создание новых объектов (чтобы не таскать из метода в метод по 10 параметров)

Это копейки. На 10 вызовах в секунду вы даже померить разницу не сможете.

Новые обращения к диску и сети (потому что теперь объекты независимо обращаются итп).

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

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

Эм, вот и просадка - вместо одного запроса к серверу получаем N. Это уже не "спички" (вызовы), сетевой запрос - тяжёлая штука.


Тяжёлая. Но, если контент тяжелее издержек, то в параллель будет быстрее. Это к тому, что даже распиливание IO на части не всегда роняет производительность, а иногда может даже ускорять. Тут аксиомы не годятся, тут нужны измерения.

Если "в лоб" разделить код по модулям (согласно тому, кто за что отвечает), можно получить отдельные хождения в IO, которые уронят производительность.


С мыслью, что если делать как попало, то получится черти-что я согласен.

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

По умному это называется разные уровни абстракции. Когда вы думает о том как полученные данные обработать, мысли о том каким способом эти данные к вам попали вас будет только отвлекать.

Нет, изи это просто проверить, что цикл есть, эта задача средняя.
https://leetcode.com/problems/linked-list-cycle-ii/

Если стараетесь избегать, значит используете, а если используете, значит есть в в этом что-то хорошие в определённом контексте.

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


Где упадёт скорость при разделении? Лишние вызовы?

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

Общая сущность будет просто удобнее, если это файл, то его можно в несколько соединений скачать по частям. Хотя разбить на части и скачать отдельными запросами тоже можно.

Распиливание же сохранения по отдельным модулям - уронит производительность.

Сохранение это данные, модули это про код. Не очень понял, что именно вы хотите этим сказать.

Что именно уронит производительность?

Как вы решали бы эту задачу?


Тестовая задача обычно рассматривается в контексте интервью. В опросе нет ни одного правильного варианта.

Кроме решения задачи, на интервью, смотрят на то, как вы работаете с требованиями и рассуждаете.

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

На своём опыте, я бы порекомендовал такую стратегию:

- Проговорить несколько вариантов, хэш, зайц и черепах, маркировка. Объяснить плюсы и минусы (сложность по памяти, сложность выполнения, сложность реализации). В процессе этого уточнять требования, типа: "Подойдёт ли решение O(n) по памяти?" (если вы на сеньёра, то скорее всего нет) или "Влезут ли данне в хэшмэпу?" (она большая, но не бесконечная). "Можно ли мутировать?" (без этого вопроса, предлагать вариант с мутацией, это путь к провалу).

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

Этим вы покажете ширину своих знаний и инженерный подход к решению задач. И пойметё, что решаете ту задачу которую нужно. Я как-то похоже на собеседовании сделал, https://habr.com/ru/articles/743514/#comment_25682476, не перезвонили.

Не знаю, на чем написана оригинальная Цивилизация 5. Но вот клон на Котлине работает ощутимо быстрее. https://github.com/yairm210/Unciv

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

Готов предположить, что после разделения ответственности по разным
классам, какое-нибудь сохранение глобальных настроек (или там, прогресса
пользователя в игре) будет дёргаться из нескольких разных мест и чаще,
чем если всё в одном God Object держать.


В таком случае разделение подразумевает создание новой сущности, которая окрестрирует всю это логику, God Object превратиться в Facade и модули. То есть в рузультате мы получим простой объект с небольшим API и это будет единственное место, где система будет работать с этой функциональностью.

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

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

Не идеально, но намного проще цикла, обработок исключений и смешения разной низкоуровневой логики (пауза, иконки, серриализация) в одном месте.

Как это повлияет на размер файла сохранения прикиньте самостоятельно.

@retry(3)
def save(filename):
    with pause(), icon("save"):
        save_character(filename)
        save_world(filename)
    print(f"Saved to '{filename}'")
    

Но только надо ещё что бы вдруг в середине процесса заказчик не
переиначил половину тз.

Мне кажется в индустрии сложилось мнение, что это какой-то редкий случай. А по факту это просто базовое свойство разработки.

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

Интересно, было ли у автора статьи такое, что написанные коментарии переставали соответствовать коду после правок

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

ну для истории, нельзя же удалять

Для истроии есть гит, удалять можно всё, и даже нужно. У меня даже почти получается.

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


А чего тут грустного?

По мне самоя большая грусть, что произоводительность обожествляют и под это творят всякую дичь. Особенно там где она не нужна, или там где она нужна, но её измеряют на глазок и оптимизируют где попало.

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

До таких приёмов как ручной инлайн и короткие имена, мне не приходилось опускаться.

Мой основной стек: Python, Java, Go, SQL

На практике, если надо разобраться в коде, то его "правильность" мало влияет на скорость понимания, а иногда и затрудняет, особенно когда код "правильно" разложен по множеству классов и файлов


А вы когда видите в коде вызовы сторонних библиотек, вы тоже в их код заглядываете?

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

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

Не все видят связь. Или не хотят видеть. Ведь если считать эффективность без этих ошибок, то можно больше премий получить.

капитально проиграл в корпоративной политике чуваку, топившему за херак-херак-и-в-продакшен

Ну продавать себя это отдельный навык, не всегда он идёт вместе в техническими навыками.

мы тут в нашем отделе для машинного обучения с плюсов на питон перешли, учи питон или GTFO

А что такого вы на С++ в машинном обучении делали?

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

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

Но вот работу с диском и протоколы связи тут вы точно зря приплетаете. Если код написанный в вашем стиле пишет 100 байтов не диск один раз, то после рефракторинга в выразительный будет 5 раз писать по мегабайту? Вы точно выразительность с чем-то другим не путаете?

Конечно вредят. Самый быстрый путь к релизу через технический долг.

Хотя и контракты и читаемость это не так долго, если уметь.

Не использовать автоматическое форматирование, это потерянное время на ревью.

В большинстве случаев вы получите такие-же отступы, но еще и скобками. Я, например, не вижу никакой проблемы ни в наличии скобок ни в отсутствие.

Во Франции сделали несколько по другому. Рабочую неделю уменьшили с 39 до 35 часов. Но некоторые конторы делают рабочий день больше 35 часов в неделю, а оставшиеся часы добавляются к отпуску.

Это же пользователь Хабра, это же автоматически подразумевает, что эксперт!

Оригинальной статье 7 лет уже, хотя тогда уже pytest вовсю использовали.

Pytest вполне совместим с unittest.mock

Ну и встроенная фикстура вполне удобна. https://docs.pytest.org/en/7.1.x/how-to/monkeypatch.html#how-to-monkeypatch-mock-modules-and-environments

Information

Rating
567-th
Registered
Activity