stdray: (Default)
У нашей системы загрузки данных есть особенности: наличие жесткого дедлайна и единомоментное массовое появление данных на ключевых источниках. То есть в теории мы пытаемся получать информацию в реальном времени, а на практике выходит, что бОльшую часть суточного объема мы получаем в конкретные часы ночью и должны полностью отпроцессить к началу рабочего дня. Двигать делайн нельзя, потому что источники начинают выкладывать данные ночью по Москве, а во Владивостоке люди уже сидят и работают. Из-за чего можно видеть пики загрузки подсистем: сначала на получении первички, потом на трансформации, потом на раскладывании данных в детальный слой, на построении витрин и, наконец, индексации на полнотекстах.

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

По логам это выглядит так: интервал между двумя записями job'а, в который, грубо говоря, происходит лишь одна операция "создание из одного гигантского хэшсета другого с добавлением туда новых элементов", составлял чуть ли не 30 минут. Плюс в этот же период идут алерты, что на сервере кончилась память, потому что процесс всё пожрал. То есть, как я понимаю, процесс начал свопить и все замедлилось в зигалион раз.

Можно сходу набросать несколько вариантов, как эффективней работать:

  • не создавать новый хашсет, а мутировать старый

  • не работать с архивами в памяти, а распаковывать их в tmp

  • уменьшить фрагментацию памяти, используюя пул стримов

  • и так далее.



Другой вопрос, что перебирать варианты опасно и долго. И тут я понял, что плохо представляю себе как профилировать память приложения, которое жрет по 16+Гб на удаленном сервере. Варианты, которые я испробовал:

  1. JetBrains dotMemory через RemoteAgent. Не сработало, потому что долго конектится к процессу, долго делает дамп, НЕВЕРОЯТНО долго качает этот дамп, еще дольше его процессит. В итоге ни один дамп таким образом у меня открыт не был.

  2. Делаешь дамп штатными средствами ОС, качаешь Far'ом, открываешь в WinDbg и через sos смотришь !dumpheap -stat. Работает, но опять же долго делать дамп и очень долго качать.

  3. Открываешь дамп в dotMemory. Эта возможность помечена как "beta", работает 100500 лет, плюс у меня дважды упала ОС из-за ентова. На третий раз всё открылось быстро, но информация была крайне странная. профайлинг ёба
    (В ЖЖ почему-то не отображается картинка, если что она есть)
    Первая попытка профилирования, когда у меня помер пека, остановила процесс, потому что с того момента он больше не делал ничего. А что за дамп загрузился после второго падения, я хз. Хотя фантазия успела разыграться, несмотря на полное несоотвествие даже абсолютных значений реальной картине.

  4. Вспомнил, что видел у Саши Голдштейна какую-то утилиту на этот счет. Она не работает. Совсем. По крайней мере, у меня.

  5. Вспомнил, что есть clrmd и написал маленькую тулу вот такого вида. Она работает около минуты, дает более-менее похожие на правду значения. Вот ей пока и пользуюсь, только модифицировал, чтобы можно еще режим задавать (не только NonInvasive, но и Passive выбрать) и поставил в планировщик писать енти "логи" каждые 15 минут. Завтра буду разбирать.



Как вообще нормально профилировать память в серверных .Net приложениях? Поделитесь опытом. А то вот это всё дикое время на создение дампа, на перекатывание его туда-сюда просто убивает. Это как смотреть на растущее дерево. Должны же быть какие-то нормальные способы.
stdray: (Default)
Хотел в заголовок поставить слово "микросервисы", но все же не про них речь.

Дело такое. Есть у меня сервис, обеспечивающий первые 2 этапа ETL-процесса: загрузка сырых данных с источников и трансформаця их в каноничный внутренний формат. Этот формат потом жрет платная сложная тула, которая раскидывает из него данные по табличкам детального слоя, data vault и всё такое.

Сервис представляет из себя планировщик quartz, в который пихаются задачи на загрузку и трансформацию. При этом с одного источникам мы можем загружать разные документы, и, скорей всего, это будут разные процессы. В простейшем случае (всего один тип документа и один "реестр") имеем 2 джоба (загрузка и трансформация), в сложном - 2-3 десятка. Есть еще такой момент, что может существовать одна реализация YobaLoaderJob, которая в конфигах тиражируется на 12 задач с разными параметрами.

С точки зрения кода, если отбросить весь common и utility код, имеем ОДНУ библиотеку, где по папочкам разложены реализации всего этого зверинца. В целом, это было достаточно удобно:
- простая и понятная структура проектов (хост, реализация, тесты)
- элементарное добавление нового источника (запилил реализацию, накалякал тестов, занес задачи в конфиг планировщика нужного контура и погнали)
- удобный рефакторинг (с точки зрения унификации - все рядышком, посмотрел, подумал, обобщил; с точки зрения правок базовых классов - всё, что можно сломать, лежит в этом же проекте)
- легкий деплой (один раз настроил сценарий разворачивания хоста и всё)

К сожалению, минусы стали такими жирными, что теперь каждый новый источник делает меня грустить. А именно:
1. правка одного источника приводит к необходимости рестартовать все (аналогично, если надо отключить один конкретный процесс)
2. библиотека с реализациями обросла диким количеством зависимостей, а ее выхлоп весит много мегабайтов
3. когда хост запущен, он штатно жрет память в пределах 8-14 Гб, что сильно осложняет снятие и исследование дампов
4. когда какие-то процессы штатно не реагируют на сигнал завершения работы (сервис не тормозит ни за 2, ни за 5, ни за 15 минут) очень трудной найти причину (см. п3)
5. примитивные мониторинги (состояние сервиса, его потребление ресурсов) становятся абсолютно бесполезны, поскольку на фоне больших и ресурсоемких источников мелкие не видны
6. все задачи кроме своего штатного расписания имеют триггер "выполниться при запуске", что реально нагибает базу, из-за чего треть процессов получает отлуп и логи краснеют
7. долгий цикл CI/CD (для одного контура оно строится 3-4 минуты, примерно столько же идут тесты, еще 3-10 минут уходит на деплой). Итого, между запуском билда и наблюдаемой активностью проходит минут 10 минимум.

Логично, что всё это дело надо распилить, но у меня нет четкого понимания, как это сделать, чтобы не усложнять разработку. Как организовать структуру проектов и как правильно выбрать гранулярность. То есть, можно представить вот такую шкалу, на какое количество логики надо создавать отдельный сервис:
1. один для всех (текущий вариант)
2. один на источник
3. один на группу зависимостей (идея примерно такая: все джобы которые, к примеру, ходят на ftp, наследуют один класс ведут себя одинаково и им не нужен остальной хлам)
4. один на каждый джоб (другая крайность)
* п2 и п3 могут только чуть облегчить боль, но проблему они не решают


Понятно, что хотелось бы иметь п4 ОДИН ХОСТ НА КАЖДЫЙ ДЖОБ. Но как этого добиться?

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

Но я не понимаю, как это организовать в коде. Я уже выше писал, одна имплементация может иметь несколько инстансов в конфиге. И так выходит, что у меня 350+ джобов, и на каждый на выходе должен получиться типо отдельный запускаемый бинарник? Очень стремно. На каждую реализацию делать по одному проекту, прикладывать туда все необходимые конфиги, а сервер приложений из них запустит по отдельному хосту?


Я в растерянности. Не знаю, акторы-хуякторы и прочие трендовые микросервисы не помогают.
Может, у кого есть подобный опыт? Ну или соображения на этот счет?
stdray: (Default)
Я тут проводил какие-то тесты над многомерными массивами дотнета, результаты которых меня несколько удивили.

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

Было (http://pastebin.com/mwqnm2re): Test fill flat array in xyz order: 26059ms (20 repeats, avr 1302ms)
Стало (http://pastebin.com/2QVsRuyP): Test fill flat array in xyz order: 10392ms (20 repeats, avr 519ms)

Скорость выросла в 2 с лишним раза, а значит компилятор не поинлайнил и JIT ничегошеньки не оптимизировал.

Во-вторых, почему доступ к элементам многомерного массива в два раза массивы-миллисекунды-указатели-коровники )
stdray: (Default)
Я тут проводил какие-то тесты над многомерными массивами дотнета, результаты которых меня несколько удивили.

Во-первых, было непонятно, что такое многомерный массив в дотнете. Он моментально выделяется и имеет относительно большое время доступа к элементам. Хотя в сравнении с моей реализацией на коленке трехмерного массива, он просто летает. Относительно Flat3d-поделки предположили, что дело в дорогих арифметических операциях и вызовах методов, которые не инлайнятся. Убрал лишний метод и немного оптимизировал арифметику. В итоге
Было (http://pastebin.com/mwqnm2re): Test fill flat array in xyz order: 26059ms (20 repeats, avr 1302ms)
Стало (http://pastebin.com/2QVsRuyP): Test fill flat array in xyz order: 10392ms (20 repeats, avr 519ms)
Из того, что скорость выросла в 2 с лишним раза следует, что компилятор не иинлайнил и JIT ничегошеньки не оптимизировал.

Во-вторых, почему доступ к элементам многомерного массива в два раза массивы-миллисекунды-указатели-коровники )
stdray: (Default)
Выбирает, видимо, себе язык под проект мой, можно сказать, знакомый. Набигает с нехитрой задачей: создать трехмерный массив определенного размера, а потом пробежаться по нему 20 раз, присваивая значения счетчика. Потрать 5 минут, говорит, сделай на F#, чтобы его показатели тоже были. Мне нетрудно - взял и написал не идиоматический код с мутабельностью и форами. Многомерный массив? Кто такой? )

August 2017

S M T W T F S
  12345
6789101112
13141516171819
20212223242526
27282930 31  

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags