Как стать автором
Обновить

Комментарии 79

НЛО прилетело и опубликовало эту надпись здесь
Да, я запрос на исходные тексты отправил, как только исходные тексты получу, так сразу же.
Резюме, надеюсь, приложить не забыли?
Мы же с вами обсуждали уже возможность работы в MS и я сказал, что мне не нравится их политика прятать исходные тексты.
А там это уже реализовано — в Windows Server 2008 R2 SP1, называется «Dynamic Memory».
Нужно будет посмотреть. Я не уверен, что это реализовано именно так. В рассылках xen'а обсуждалась возможность добавления памяти в винды (HVM), и там это реализуется через memory hotplug, что несколько более мерзко, чем зеновский balloon'инг.

Кроме того, вопрос: а отдавать попользованную память гость будет? hotplug памяти как бы hotunplug не подразумевает.
hotplug памяти как бы hotunplug не подразумевает.

С «отдавать» тут сложнее. Для этого используется специальный драйвер (у vmware он именуется balloon driver, как у MS — не помню).
Т.е. там костыли вполне сравнимые с костылями зена сейчас, гордиться нечем. Вот тот, кто сделает ОС, работающую в PV с общим пулом памяти на всех, получит все плюшки. А пока все (включая зен и hyperv) танцуют вокруг ветхих абстракций железных серверов.
Да, костыли, только костыли свои родные, а не писанные кем-то со стороны.
Увы, от костылей не деться никуда — потому как нет сейчас ОС, которая способна свободно как забирать, так и отдавать память.
Родные для кого? С моей точки зрения как раз мои костыли писанные мною, а не «кем-то со стороны». В зене, кстати, все эти костыли можно хорошо видеть в работе по исходникам (и именно чтение xenballoon меня и натолкнуло на мысль о том, как это нужно реализовывать).

Кстати, у меня вполне серьёзный вопрос: как в виндах с использованием dynamic memory решается проблема оверхеда?

Ведь таблица трансляций памяти задаётся в холодном режиме при загрузке ядра и её нужно инициализировать до загрузки. В зене это решается preballooned памятью (фактически, инициализацией ядра в рассчёте на максимальный объём памяти). А в виндах? Предположим, мы грузим систему с 256Мб оперативки. А потом начинаем хотплажить память. Гиг, два, три… Доходим до цифры 128Гб, что дальше? Или там есть лимиты на соотношение boot/maxhotplug?
Оверхед — разумеется есть, и от него никуда не деться.
При динамическом распределении памяти — используются два значения: Startup RAM — сколько памяти резервировать виртуалке для загрузки ОС, и Maximum RAM — максимальный объем памяти, который виртуалке можно выдавать. Дефолтное значение — 65536 MB. Выше него виртуалка не «прыгнет» ни при каких условиях.
Другими словами, всё что сделала майкрософт — изобрела дополнительную проблему к типичным костылям xen'а.

Смотрите: у xen'а есть два параметра: memory и maxmem. Домен (виртуалка) не может прыгнуть выше maxmem. При загрузке домена PV-ядро резервирует таблицы размером для maxmem, а pre-inflated ballloon делает ей указанную memory. Когда нужно накинуть — шарик подсдувается.

К чему тут городить ещё какой-то hotplug? Точнее, я могу сказать «к чему» — майкрософт не реализует в чистом виде PV и ему приходится обманывать виртуалку имитируя втыкание памяти. Именно это обсуждалось в рассылке xen'а на тему «memory hotplug для виндов». Вместо того, чтобы разрешить людям поправить ядро под preinftated balloon, там эмулируют хардварный механизм для тыкания памяти в сервера. Нафига?

Одно слово, майкрософт.
Одно слово, майкрософт.

Скорее всего — из-за обратной совместимости. Чтобы с этой хренью могли хоть 2000е винды работать.
Вообще то Windows 2000 и все что раньше уже давно окончила свой жизненый цикл и вышла из поддержки.
Видимо, имелись в виду 2003, которые никто в обозримом будущем хоронить не будет.
В зене для этого придумали два режима: HVM для тупых машин а-ля винды, не умеющих паравиртуализацию и кастомных ядер, которые PV умеют.

Винды вообще-то могли бы так же сделать. Основная проблема, что MS из-за своей м… модели жлобства не будет реализовывать pv_ops в ядре (сырцы придётся открывать), значит, им придётся изобретать несовместимый со всем нормальным человечеством (mac, linux, bsd) велосипед.
Основная проблема, что MS из-за своей м… модели жлобства не будет реализовывать pv_ops в ядре (сырцы придётся открывать),

Во-первых — это не обязательно, достаточно открыть API. Во-вторых — уж сами себе они сорцы откроют. В-третьих — даже открыть сорцы для них не такая большая проблема. Потому как открываются они только некоторым партнерам и под строгим NDA. Уж таким, как vmware или citrix — уж наверняка сорцы дают, иначе бы цитрикс загнулся, так и не родившись.
Не вижу проблемы, компоненты интеграции для Linux они открыли для включения в ядро. Драйвер balloon для Dynamic Memory идет вместе с компонентами интеграции. Осталось убедить Microsoft сделать поддержку DM для Linux. :-)
Да сделают, я думаю — раз уж компоненты интеграции сделали. А потом и в ядро закоммитят наверняка.
Г-н Бенджамин Армстронг в своей презентации (начиная с 07:30) как раз говорил, что они сделали для Dynamic Memory хитрый хак по поводу hotplug.
Hyper-V при использовании Dymanic Memory забирает память обратно через balloon.

Hot или не-hot, но анплаг определенно есть
Как говорят товарищи рядом, это ballooning. Новая память зачем-то plug'ается, а старая infate'ается в balloon. Глупо. В зене обошлись одним balloon'ом.
Глупо потому что «индусы из Редмонда» или у Вас есть конкретные претензии?
Я вижу только то, что для несуществующей памяти не нужно выделать таблицы/директории и соответственно нет оверхеда. Ну а после того, как память хотплагнулась — деваться некуда — приходится пользовать балулнинг.
s/выделать/выделять/
s/балулнинг/балунинг/

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

Я не видел ядер, которые бы эти таблички умели на ходу менять.
Ну во первых перестраивать PDE/PTE умеют вообще все ОС, которые умеют изоляцию процессов и виртуальную память.
Но насколько я понял, Вы о памяти ядра (там тоже каждое выделение памяти меняет эти таблицы). Наверное по какой то причине ОС не может добавлять новые директории/таблицы. Посмотрел ядро линукса — честно говоря запутался (и как назло ни комментариев нормальных ни документации). Там мемори менеджмент очень сильно закаплен с xen/kvm (или чего там в качестве виртуалки), что в общем не так уж и плохо — ибо data hiding приносится в жертву производительности.
В общем, не могу утверждать на 100%, но из того, что я увидел, память добавляется при помощи arch-specific функции arch_add_memory, которая зовет сначала init_memory_mapping а потом __add_pages. init_memory_mapping проходит в цикле по страницам и для каждой зовет kernel_physical_mapping_init и ближе к концу зовет __flush_tlb_all (что свидетельствует о том, что меппинги изменились — иной причины сбрасывать кеши этих меппингов я не вижу). kernel_physical_mapping_init уже уходит куда то в VM. __add_pages в свою очередь зовет в цикле __add_section, та зовет sparse_add_one_section, __add_zone и register_new_memory

В общем, как я уже сказал, не могу утверждать со всей уверенностью, но очень вероятно, что даже линукс перемапливает директории/таблицы при добавлении памяти. Так что аргумент «хот-адд для уменьшения мемори футпринта» остается в силе
Параметр передается при загрузке потому что того требует acpi (опять таки — я не копенгаген, это просто гугль-знания) —
Блин, отослалось как то. Так вот, hot-add память насколько я понял не может появляться где попало — только в строго отведенных местах и ОС в принципе может подготовиться к этому.
Вот-вот, это и вопрос. Насколько ОС сложнее «ждать» hotplug'а памяти по сравнению с загрузкой с pre-inflated balloon?

Кстати, ещё один аргумент про hotplug vs balloon, насколько я понимаю, в случае с balloon, единственный накладной расход — при изменении пузыря — это чистка страниц памяти. А в hotplug что-то много нужно переделывать…
Ну то есть Вы автоматически записываете девелоперов МС в «тупые индусы». У них и оба механизма есть (balloning и hot-add) и средства для диагностики (ETW/xperf), а они возьми да и выкати неэффективное решение. А хотплаг скорее всего случается по прерыванию, так что активно ничего поллить не нужно.
Я не про пуллить.

Поясню: возможно, с точки зрения разработчиков «под хотплаг» системы предполагалось, что память будет тыкаться достаточно редко (это значимое событие, оверхед которого на инициализацию памяти незначим). В моей модели память тыкается сейчас чаще, чем раз в секунду, а планируется быть тыкаемой ещё чаще.

Если hotplug отрабатывается на уровне «а вот сейчас мы в ядре пересоберём все таблички да скинем кеши, да ещё потопчимся вокруг, да ещё эвент в лог запишем», то в режиме N раз в секунду система только и будет, что самообслуживать хотплаг.

Я когда решения конкурентов изучал, обратил внимание на это — все считали, что добавление памяти это Big Deal, который требует особого внимания.
Мне кажется или Вы уже сделали вывод, а сейчас пытаетесь подвести под него «факты»?
Даже если хотплаг и дорогая операция (я не знаю — так что не могу ничего утверждать), это вообще ничего не меняет. Вместо того, чтоб выделять таблиц сразу на все 64 гига, к примеру, мы выделяем только на 1 гиг доводим хотплагом до того, что реально нужно виртуалке и дальше она использует только балунинг. Другими словами это тот же балунинг, но с адаптивным выделением места под таблицы страниц (и TLB, кстати, который дороже даже самой физической памяти).

Чтобы пояснить мысль, напомню, что std::stack в C++ чаще всего реализуется на основе std::deque. Ресайз дека — операция весьма дорогая (и из-за operator new и из-за необходимости поэлементного копирования всего содержимого), но тем не менее асимптотически операции push/pop в таком стеке имеют константную сложность. Просто потому, что дек достаточно быстро выходит на steady state и ни новых обращений к хипу ни новых копирований содержимого уже не нужно.
Я бы посмотрел в сырцах — но сырцов-то не публикуют. Так что остаются две теории: моя (пессимистическая) и ваша (оптимистическая).
Хотплаг дороже балунинга в стоимости выполнения только при редких разовой операции. Балунинг более дорогой в постоянном дополнительном расходе служебной памяти.

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

Повторю, это гипотеза. Кто из них быстрее в результате — не знаю.
Вы упускаете из виду тот факт, что после того, как память добавлена — она менеджится балунингом. Соответственно, очевидная причина для использования хотплага — выход на steady state по размеру пузыря вместо использования сразу максимального.
Ну, в принципе, да, возможно. Точнее нужно смотреть на оверхед.

PS Только что смотрел зен 4.1, там не только hotplug есть, но и hot unplug.
НЛО прилетело и опубликовало эту надпись здесь
В услуге реализовать — плевое дело. Появится у всех хостеров, как только SP1 выйдет из беты.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Потому что openvz а) не имеет свопа для гостей (обратите внимание — на графике видно, что клиенты пошли так быстро, что MOD за ними не успел, часть данных вылетела в своп — в openvz это было бы «йок апачу»), во-вторых мне не нравится их модель учёта памяти.
НЛО прилетело и опубликовало эту надпись здесь
Ну я на это ничего не могу сказать, openvz я знаю сильно хуже xen'а. Возможно, когда допилю всё что хотел в xen'е, можно будет поиграться с openvz.
Как всегда, отличная статья. Спасибо.

Молодцы, давно пора :) Термин классный тоже.

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

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

А вторая категория — это технари, вроде Вас со мной, которые знают. Я, конечно, не уверен, сколько Апачу нужно для форка, а сколько для пяти, но поставлю пять баксов, что для проекта, где веб-воркеры — апачи — нужно точно больше 512мб, наверняка и больше 1024 мб, чтоб при скачке числа форков скейлинг успел сработать.

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

А я предлагаю забыть об этом и юзать компьютер.

Посмотрите на график снизу — вы действительно можете предсказать эти движения памяти? Я — нет. Хотя я это организовал. Невозможно предсказать, сколько памяти потребуется скрипту при обработке пользовательского ввода. Правильно не предсказывать, а реагировать на потребности.

Именно это и есть принцип MOD — мы мониторим потребности ПО и выдаём ему столько памяти, сколько нужно ПО. Без необходимости рисовать с потолка цифры 512, 1024 и т.д. Посмотрите внимательно на синюю и красную линии на графике — красная это потребность. Синяя — это сколько мы дали.

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

Правильно не предсказывать, а реагировать на потребности.

Согласен.

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

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

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

Давайте таки перейдем на ты?

При всплеске в своп уходить не обязательно, память гостю можно запрашивать и в реальном времени по запросу. Правда, в этом случае все-равно могут быть другие задержи на миграцию на другую машину, если на текущей резерва памяти нет.
Я противник миграции по первому чиху -_-''' Я не видел систем, в которых бы на нагруженных машинах (а мы же про них, а не про перделку с la<0.001 на которой всё зашибись) миграция была бы полностью seamless.
Если у вас на хосте память кончилась, это не первый чих. Или вы и тогда без миграции гостю память увеличите? Инфинибендом с соседней машины смаппите?
До определённого момента буду терпеть, а гость будет выпихивать хлам в своп. Это лучше, чем по первой же потребности гонять туда-сюда.

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

NUMA у меня в списке на изучение, но времени нет категорически.
Т.е. по каким-то эвристикам снаружи определять действительно ли гостю нужна еще память и стоит/не стоит его мигрировать? Или вообще исключить миграцию?

Как-то нехорошо это. Если у гостя уже и так все в свопе и память нужна позарез, а ему в ответ — потерпи пока кто-то выключится.
М… Ну, например, простейшая эвристика: в свопе меньше 20% от TotalMem. Если больше 20%, пора мигрировать, если меньше, перебьётся (ибо негативных последствий от миграции будет больше, чем от небольшого свопа).

PS Своп как раз и решает проблему «нужно позарез». Дальше чистая математика: что лучше, миграция с лагом или небольшой своппинг. Возможно, если для свопа выделять отдельный VBD, можно будет тупо вводить триггер по интенсивности IO на своп.
Мне такое решение кажется как очень неудобным для клиента, так и несоответствующим задачам хостера продать больше услуги. Напоминает продавщицу, которая откажется продать мне колбасу, оперируя эвристикой, что я и так большое пузо наел :)
М… Не совсем так. Мне кого-то мигрануть — раз-два и пошёл. Но сервису пользователя от этого одно неудобство, правильно? Там задержка вполне ощутимая.

Если вместо этого оно у него свапонётся и отвалится обратно на старые пределы по памяти, то, возможно, это будет лучше, чем миграция…

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

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

Основной вопрос, который должен решать MOD — это скорость реакции. В настоящий момент у нас это раз в 0.9с, как только будет решено несколько внутритехнических проблем, я надеюсь, мы перейдём на время реакции порядка 50-100мс. В этих условиях вероятность вылететь в своп будет только из-за больших запросов (а память будет накинута ещё до окончания своппинга).

Серьёзный прорыв произойдёт в тот момент, когда можно будет ловить запросы на allocation. У меня в планах это есть, но нужно много копаться в ядре (до этого есть куда более интересные вещи).
Оверхед «съедается» то ли гипервизором, то ли ядром гостевой системы
Съедают оба.

ближайших перспективах написание алгоритма с оглядкой на статистику предыдущих запросов и подстройкой уровня оптимистичности выделения памяти
Полезно описать это в виде API и дать возможность пользователю самому управлять балунингом своей системы. Например, решать какой процент или объем памяти держать под дисковый кэш, С вашей стороны ведь клиент скорее всего общается с каким-нибудь tcp-сервером в dom0?

P.S. Есть достаточно легкое решения против oom — перехватывать в ядре oom-killer и вместо поиска жертвы послать заявку на увеличение памяти гостя. Правильнее было бы связывать с диспетчером памяти ядра, но так тоже сойдет.
API у нас будет строго соответствовать функционалу панели управления (собственно, панель — морда над API).

Взаимодействие идёт много хитрее, чем по tcp. Любой зенофил знает, как domU может общаться с dom0 без участия сети. :)

За идею перехватывать oom_killer спасибо. Возможно, это даже интереснее, чем перехватывать запросы на allocation.

Ещё один вариант — сделать swappiness 0 и перехватывать попытки свопа.

Короче, нужно думать.
Я не знаю как, например, в hvm без pv ops обойтись без сети, и не прослыть при этом извращенцем :) Расскажете?

С oom_killer, имейте в виду, что это last resort. Если вызов дошел до него, значит кэш очищен максимально и в свап вытеснено все, что можно, так что в качестве основного средства его использовать все-таки не стоит.
А не надо использовать HVM там, где можно его не использовать.

Насчёт oom понятно, что это аварийная затычка.
Это нужно будет не мне сказать, а клиенту, который придет с системой на HVM, например, FreeBSD.

Получается, из гостя запросы на изменение только через xenstore-write отдаете. Вероятно, есть на это веские причины.
BSD умеет работать в PV режиме. Хотя её сначала напильником пилить нужно.

Особых причин для xenstore нет, просто когда я делал, это было очевидным решением. Откровенно, я вообще пока HVM режим особо не трогал, ибо не было нужды. Насколько я знаю, HVM по всем параметрам проигрывает PV, и если говорить про продакт, то PV предпочтительнее.

PS А вот что xenstore нет в HVM для меня новость.

PPS Прогуглил, кажись есть. Не надо меня так пугать.
Напильник не помогает, для production PV слишком не стабилен.

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

Прогуглил, кажись есть. Не надо меня так пугать.
Для ОС, в которых нет, или неполные, или нестабильные PV-драйверы, лучше считать, что production xenstore в HVM нет.
Впрочем, у меня как-то негативно оценка выглядит, что на самом деле не так. Объем изученного и сделанного вызывает уважение.
довольно интересно.
Т.е. в настоящий момент вы автоматически производите определение необходимости добавления памяти. Это и хорошо и плохо. Хорошо для таких серверов как apache, плохо для таких как tomcat (для всего семейства java серверов) так как у них задается максимальный размер возможного поедания памяти указывается при старте сервера. Т.е. если будет превышен лимит ОС об этом не узнает, java сервер внутри себя подавит попытки превышения заданных лимитов.
Исходя из сказанного напрашивается следующая схема решения вопроса:
выделение памяти по запросу — сам веб-сервер внутри ОС просит добавить памяти. Вы каким-то образом вычисляете такой запрос — и выделяете память. При этом желательно реализовать асинхронный режим с обратным вызовом callback функции. Т.е. веб-сервер говорит — дай мне еще 1Гиг памяти и после того как он будет доступен запусти такой-то процесс. Этим процессом как раз таки может быть вторая (третья, ..., пятая, ...) кластерная нода веб-сервера.
В зене есть такой функционал, кстати. Более того, внешнее управление на самом деле делается как отдача команды драйверу внутри.

По tomcat'у уже прошлись. Впереди грабли с mongo. Возможно, что мы будем вынуждены рекомендовать для части сервисов технологию выключать (если не удастся побороть явовские закидоны, багрепорт в monodb-users я уже отправил).

А вот callback… Ну только если в голом юзерспейсе wait делать.
«моя твоя не понимать» :)
а можно детальнее что значит «по tomcat-у уже прошлись»? И какие именно грабли с mongo? Что за закидоны явовские? Ну и что значит Ну «в голом юзерспейсе wait делать»?

если я не ошибаюсь у вас вроде бы сейчас тестовый период идет? мы разрабатываем софт для облачного хостинга. Автоматическое масштабирование приложений на уровне серверных нод. Только масштабирование происходит на другие виртуальные или реальные машины. А тут оказывается есть возможность «раздуть ресурс» текущего инстанса… Можем провести совместную серию экспериментов.
тестеры уже выявили проблемы у tomcat'а.

У mongo такие же грабли (или оно просто падает от хорошей жизни? Не доразбирался ещё).

«голый userspace» имеется в виду — ожидание нужного состояния в виде «ждать пока не дадут».

О проверке… В принципе, поговорю с начальством. (когда выздоровею — простуда и всё такое).
> тестеры уже выявили проблемы у tomcat'а.
если вы про ограничение памяти при запуске, то это не проблемы, а архитектура. если вы про падение, тогда явно есть проблемы, в нормальном режиме работы tomcat не падает.

желаю вам скорейшего выздоровления.

и вообще эта опция динамического выделения памяти востребована кем-то?
Поскольку она по-умолчанию включена у всех, то да, востребована. Большинство вообще ничего не замечает, т.к. регуляция идёт автоматом.
Как это хозяйство работает с кешем файловой системы. Мы используем PostgreSQL, скорость его работы очень сильно зависит от того, какой объём активно используемых им файлов залезет в кеш файловой системы. На обычном железе кеш файловой системы откушивает практически всю свободную память. В вашей же системе память типа бесконечная (если не считать лимиты).

Как постгрес у вас будет работать?
Когда мы определяем «свободную память» кеш не учитывается. А вот размер кеша (то есть то, сколько памяти отдать на растерзание буфферам и кешу) вы задаёте в настройках машины. Скажете «держать 2Гб свбодным» — будем держать.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий