Разрабатываю сервисы, которые делают жизнь проще
10,4
рейтинг
3 марта 2013 в 19:41

Разработка → Проектирование высокопроизводительных систем: о чем не расскажут в книгах



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

Почему так происходит? Обеспечение высокой производительности и надежности ошибочно почитается многими за «черную магию». И неспроста — чуть ли не в каждой книге или статье на эту тему вы первым делом наткнетесь на утверждение типа «нельзя просто так взять и повысить производительность».


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

Рискуя навлечь на себя гнев высоколобых теоретиков софтостроения, скажу: есть конкретные, понятные практики, следуя которым, вы на 99% решите все проблемы быстродействия, надежности и доступности вашего ПО. И я готов с вами этими практиками поделиться. Сразу отмечу, что речь идет прежде всего о серверных приложениях с бизнес-логикой более сложной, чем обычный CRUD.

Итак, поехали — 7 практических советов повышения производительности, которые реально работают.

Не вникайте в базу данных



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

Используете классическую реляционную базу типа Oracle? Если возникнут проблемы, то есть специально обученные специалисты СУБД, они подкрутят настройки где надо, и все будет хорошо. Используете NoSQL-хранилище вроде MongoDB? Так там и вовсе ничего знать не надо, разработчики 10gen уже обо всем позаботились за вас.

Не надо думать за разработчиков СУБД. У вас есть ORM или клиентская библиотека — вот ее и используйте. Как оно там работает на стороне хранилища, никого не волнует. К тому же, вдруг вы захотите сменить движок БД? Нельзя «затачиваться» на специфику конкретной базы.

Одиночные операции вместо пакетных



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

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

Никаких кэшей


Доморощенные оптимизаторы будут советовать вам использовать кэши (для контента страниц, бизнес-объектов, сложных результатов вычислений и тому подобного). Якобы эти кэши позволяют снизить время отклика и уменьшить нагрузку на систему. Ну да, конечно! А как вы будете решать вопрос с устареванием данных в кэшах? Их непротиворечивостью? Эластичностью? Лишними ресурсами, которые они потребляют?

Мой вам совет — просто не используйте кэши. Дисковые подсистемы в современных ОС сами знают, что и когда кэшировать. Добавьте к этому быстрые SSD-диски, и вы поймете, что время кэшей безвозвратно прошло.

Используйте единственный примитив синхронизации



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

Не надо использовать высокоуровневые паттерны многопоточности — все эти неблокирующие коллекции, атомарные типы, агенты и тому подобное. Все они переусложнены и навязывают вам совершенно неудобную модель использования (чего стоит один метод compare_and_set, грубо нарушающий принцип single responsibility).

Если потребуется дальнейшая оптимизация, то от блоков синхронизации можно просто избавиться, и система заработает еще быстрее! Конечно, могут возникнуть небольшие проблемы с конкурирующими потоками, но в итоге все будет в порядке (вы наверно слышали, это называется eventual consistency — достаточно актуальная сейчас тема).

Применяйте как можно более простые алгоритмы



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

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

Используйте умолчательные настройки


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

Локальное взаимодействие ничем не отличается от удаленного



Допустим, у вас в системе есть API, который используется локально. Подойдет ли он для взаимодействия по сети, когда клиент может находиться в любой точке земного шара? Конечно, да! Нужно ли как-то его модифицировать, уменьшать гранулярность, особым образом обрабатывать прерывание соединения, вводить дополнительную типизацию ошибок, поддерживать несколько версий интерфейса? Конечно, нет! Библиотеки и фреймворки делают удаленное взаимодействие совершенно прозрачным для клиента и сервера, а стремительный рост пропускной способности и стабильности глобальных сетей нивелирует их отличие от сетей локальных.

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

Смело берите 7 советов на вооружение, и вы очень скоро увидите результат в своей системе!



P.S. Иллюстрации любезно предоставлены пользователями Flickr:
Антон Жиянов @algenon
карма
33,0
рейтинг 10,4
Разрабатываю сервисы, которые делают жизнь проще
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (90)

  • +76
    Мне кажется, или тут не хватает таблички «сарказм»?
    • +30
      Если после прочтения статьи еще нужна эта табличка, значит я как автор не доработал.
      • +27
        Как человек лишь интересующегося темой (ибо разработку веду на мобильных платформах) принял бы за чистую монету. Мало ли какие тенденции в хайлоаде. Но при прочтении возникает смутное сомнение. Так что автору хвала, достаточно тонко, но уловимо.
        • +2
          По мере прочтения комментариев я понял: принимая человека в хайлоад, у него забирают чувство юмора. Хорошо, что вы занимаетесь мобильной разработкой!
          • +10
            Автор, вы чего? Какой юмор на хабре, вам тут насуют в карму за любую шутку по поводу чего угодно. Начиная от Apple и заканчивая предпочтениями о написании скобок в коде. Особенно когда шутки такие, где не написано, что это шутка.
            • 0
              Ну пусть насуют, что делать. Я не обидчивый в этом плане.
            • +5
              Слишком тонко для нашего цирка
          • +6
            Эх прошло время шуток без смайликов.
      • 0
        А вы еще до сих пор в этом сомневаетесь?
      • +5
        Даже если прочитать наоборот, ваши советы неоднозначны

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

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

        Ум и опыт, опыт и ум. И поэтому да, нельзя просто так взять и поднять производительность, если ты дядя Вася, даже с набором вредных советов, но без опыта и ума
        • +7
          Да, тут вы меня уели. Надо было добавить еще один совет:

          Старайтесь поменьше думать

          Последние результаты исследований в области нейрофизиологии убедительно доказали, что причиной преждевременного старения является излишнее использование головного мозга. Для рутинной деятельности, к которой можно отнести и разработку, вполне достаточно ресурсов спинного мозга и (в особо сложных случаях) подкорки.
      • 0
        Прям вредные советы Остера.
      • 0
        Всё. Я хочу от тебя ребёнка! Ей-богу уже начиная со 2-го пункта меня пробрало. :)
    • +2
      Однозначно не хватает. Уже после первого принципа начал ее искать — не нашел. Это скорее антипаттерны, но все зависит от конкретного контекста. Немного увеличить производительность — да, это может помочь. В настоящем HighLoad это антипаттерны, представленные как серебрянная пуля. Был в гостях у Елизарова на jug.ru/, объективно говорилось совершенно об обратном.
      • +10
        Подождите, вы хотите сказать, что эти советы могут быть вредными?
        • 0
          Да, могут быть. ORM, по типу MS Entity Framework, легко сгенерит Вам запрос с 300+ джойнами.
          На счёт кешей и одиночных операций — тоже крайне спорный момент.
          Выключать мозг, в любом случае, нельзя.
        • +1
          Не вникайте в базу данных

          Обычно так делают в самом начале проекта. Идеальный ORM — универсальный ORM. Когда же начинаются реально высокие нагрузки, то алгоритмы специализируются на данных. Вплоть до того, что вот из этой таблицы мы всегда извлекаем быстро N-записей, это и будет верный результат. Извлекать вот так-то очень быстро. Естественно без джоинов. Нормальные формы часто тоже становятся препятствием, но не во всей же БД делаются такие «заточки», а только в самых узких местах.

          Тюнинг БД из той же оперы.

          Одиночные операции вместо пакетных

          Конвейер наше все. Как начал Форд их использовать, так мы и продолжаем. Те же видеокарты, имеют кучу конвейеров. Промышленные ЭВМ содержат специфические конвейеры для разных операций. Например, лучше быстро складывать числа, чем иметь универсальный модуль, но жутко медленный.

          Еще пример. По сети приходят пакеты. Мы их как-то обрабатываем и пишем в БД. 100 пакетов в минуту. Наша ИС обрабатывает 10 пакетов в минуту, но зато быстро пишет в БД. Обрабатываем первый пакет, потом сразу берем пачку, обрабатываем и быстро пишем в БД. Частый прием оптимизации.

          Никаких кэшей

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

          Используйте единственный примитив синхронизации

          Какой? Все зависит от задачи. Мой сосед по общежитию как то сказал профессору на экзамене по ассемблеру: «Мой код на ассемблере никогда не будет так эффективен, как ассемблерный код, выданный компирятором». Свои велосипеды надо писать когда другие средства уже не помогают, а это, поверьте мне, бывает очень редко.

          Применяйте как можно более простые алгоритмы

          Конечно, все используют эффективные алгоритмы. Других просто нет. Все зависит от задачи и проблемы. Где простые алгоритмы дают приемлемую производительность, то сложные (реально сложные) алгоритмы могут дать колоссальную производительность. Обратное тоже верно.

          Используйте умолчательные настройки

          На старте да, это возможно, но как только начинается продуктивная эксплуатация, то ни о каких стандартных настройках и речи быть не может. Самый простой пример: у меня, как у разработчика, вся ИС размещается в 2-ух гигабайтах оперативной памяти. Вполне приемлемый объем. На сервере же, эта система запросто может занимать от 16-и гигабайт и выше. Хочешь выделить памяти виртуальной машине — задай нужный флаг. А это уже настройка.

          Локальное взаимодействие ничем не отличается от удаленного

          Да, классный вариант для архитектуры. Есть клиент, есть сервер. Никакой разницы, на одной машине выполняется код или на разных. Но это до поры до времени. 99% ИС это устроит. Но вот если вам надо обмениваться реально большими объемами данных, то тут и shared-memory и собственные блоки памяти, управляемые из аддона к ядру ОС и пр. шалости.
          • +6
            Даже не знаю, как вам сказать…

            sarcasm.jpg.to
    • 0
      Шелдон?
      • +6
        скорее, Григорий Остер.
  • 0
    Дефолтные настройки Apache + MySQL сиииильно вас порадуют, ага.
    • +5
      То есть они не для всех проектов подходят, да?
      • +5
        Я не понял сарказма статьи, извините :)
        • 0
          Главное, что вы не следуете ей в жизни :-)
      • 0
        Для домашней странички или мелкого интернет-магазина или сайта, коих большинство — подходят. Но когда начинается «надо запустить это на VPS с гарантированными CPU 512Mhz и 64 MB RAM» — вот тут уже придется лезть в настройки и заморачиваться кешированием. Или когда на ваш маленький сайт придет Хабра-эффект. Или когда вас зачем-то будут DDoS-ить.
    • +2
      Дефолтные настройки postgresql вообще ни к черту.
      Дефолтные параметры запуска KVM зачастую нацелена на универсальную возможность запуска виртуалки на любом железе (производительность же второстепенна).
      • 0
        а что у него не так с дефолтами, если не секрет?
        • +1
          У постгреса или kvm?
          Постгрес по умолчанию настроен на работу таким образом как будто он запущен на машине с памятью меньше 1GB (shared_buffers) на одном диске (effective_io_concurrency) и как-будто в него совсем не будет записи (checkpoint_segments). Пожалуй к этому можно добавить ident авторизацию (по умолчанию в RHEL/CentOS) которая вводит в прострацию новичков)))).
          В KVM может использоваться дефолтное кэширование (writethrough) которое во некоторых случаях не идеально (гости на внешних хранилищах по iSCSI или NFS). Не используются hugepages, отключены некоторые вкусные процессорные флаги. Паравиртуальные драйвера для устройств опять же не всегда по умолчанию.
          • 0
            Постгрес интересовал. Спасибо за ответ!
            А если детальнее — shared_buffers это совсем плохо? Где можно почитать про оптимизацию постгреса?
            • 0
              Более детальней посмотрите на раздел с описанием использования ресурсов, оф.справка PG всегдя является хорошей отправной точкой.
              Почитать про оптимизацию постгреса можно в блогах, например у Depesz, у него достаточно часто появляется свежая инфа и есть статьи с фундаментальными вещами.

            • +1
              Мне лично понравилась и помогла книга Васильева, там много полезно habrahabr.ru/post/158617/
              • 0
                Вот спасибо
  • +11
    Не надо использовать высокоуровневые паттерны многопоточности — все эти неблокирующие коллекции, атомарные типы, агенты и тому подобное.

    Правильно, их же дураки придумали, а мы умные, мы сами все сделаем гораздо лучше.
    • +9
      Вы совершенно правильно уловили мою мысль!
  • +1
    Статья напомнила огромное количество серверов и fulltext поиск в одноклассниках.
    Зачем придумывать логику если можно наращивать вычислительную мощь.

    Автор, Вы сами имели дело с HighLoad?
    • +6
      Конечно, разве я не написал, что все это основано на личном опыте?
      • 0
        Используйте умолчательные настройки

        Пример месячной давности. С ростом посещаемости нагрузка на MySQL выросла в разы. Тюнинг настроек дал результат из 400 запросов/сек до 2.5-3к/сек.
        Никаких кэшей

        Если не использовать разумно продуманное кэширование, то мне при генерации страницы нужно делать порядка 70-80 запросов к БД. Если у меня сейчас 2-3к/сек при кэшированных запросах, я даже представить себе не могу что скажет сервер если я выключу кэш.
        • +8
          Хм, я уважаю личный опыт, поэтому ничего не буду здесь возражать. Возможно, нам удастся избежать противоречий, если вы инвертируете каждый из советов статьи?
          • +1
            Когда читал статью не видел тега «вредные советы». Он изначально был?
            • +7
              Был. Хотя, я думал, что все будет понятно и без него. А оказалось, что надо было большими красными буквами в начале писать. Чудно.
              • 0
                Объясните тогда вообще цель Вашей статьи если не затруднит.
                Или Вы считаете правильным сначала прочитать а потом инвертировать все из прочитанного?
                • +4
                  Ну какая может быть цель у «вредных советов»? Показать точку зрения автора на то, как делать не надо (типичные грабли, если угодно).
                  • +2
                    Недостаточно серьезно. Чтобы все догадались, имхо нужно больше пафоса, чтобы жир аж стекал с монитора
                    • +2
                      Может, если кто-то не понял сарказма — ему просто рано в highload?
      • 0
        А что в вашем понимании highload?
  • +2
    Отказаться от кеша? Забить на настройки? Время при обработке по одному объекту примерно такое же, как и при пакетной? Приведите пример высоконагруженной системы, которую Вы спроектировали таким образом. Протестируем ее хабраэффектом )
    • +24
      В статье действительно не хватает таблички «сарказм».
      • +1
        Фух… Прошу прощения ) Тег не заметил. Я уж начал думать кто сошел с ума: я или все вокруг )
  • +6
    Но это частности, вызванные несовершенством окружения в котором вынуждена работать система, и они не заслуживают серьезного рассмотрения.

    Браво!
    • +3
      Спасибо. А то после первых комментариев мне стало как-то не по себе.
  • НЛО прилетело и опубликовало эту надпись здесь
    • +6
      Первый тег статьи — «вредные советы».
      • НЛО прилетело и опубликовало эту надпись здесь
    • +4
      Хорошо, в следующий раз я предварительно буду джва года писать годные, полезные статьи на хабр, и картинкой к посту поставлю эту: facepalm.jpg

      Хотя знаете что? Пожалуй, не буду.
      • НЛО прилетело и опубликовало эту надпись здесь
        • +4
          Я не думаю, что выбор между вариантами а) и б) должен хоть как-то зависеть от выслуги лет. Если вы попробуете применять этот принцип в жизни, то, возможно, поймете почему.
          • НЛО прилетело и опубликовало эту надпись здесь
            • +2
              Ну и славно. Тем более, что я как раз «очередной дуралей» в вашей терминологии, на контрибьюшены не претендую.
      • 0
        Да ладно вам — крутая статья. Не обращайте внимания на этих.
        • +2
          Интересно, что хотя на «советы» в совокупности все гневно машут руками, я неоднократно встречал специалистов, которые с пеной у рта отстаивали отдельные постулаты.
  • +3
    По мне так, хотя советы и вредные, но их стоит придерживаться в стартапах. Бывает так, что тратятся месяцы на закладывание в систему зазора на высокую нагрузку, начинается борьба с проблемами которых еще нет, а по факту теряется время и деньги. К тому же как показывает практика большая часть стартапов никогда не взлетит и высокой нагрузки там просто не будет.
    • +10
      В иных стартапах достаточно ограничиться статическими html-макетами с подключенной гугл-аналитикой, чтобы убедиться, что они никому даром не нужны :-)
  • 0
    Пассаж про Оракл непонятен — разработчик может не быть настолько спецом в Оракле, чтобы подкрутить именно то, что нужно в данной ситуации. Особенно учитывая, что ораклоидов часто нужно хранить отдельно от остальных разработчиков ;)
    • 0
      «Настолько» — может не быть, да. но базовые представления о особенностях работы оракла нужны обязательно.
      Я встречал (и приходилось перепиливать) перлы «разработчиков», добавляющих в каждом новом экземпляре одного и того-же запроса рандомные псевдонимы к табличкам, чтобы оракл, не дай бог, не догадался что запрос-то один и тот-же и можно его из курсор-кеша брать…
  • +5
    Я уверен, что писать подобные «юморные» советы должен узнаваемый и уважаемый человек.

    Иначе такие статьи просто воспринимаются как «мудрость» от какого-нибудь 19-ти летнего Дениса, пишущего архиватор. Я по диагонали проглядел, автоматически минусанул, а потом только понял, что сдаётся мне господа, это был юмор.
    • +2
      Я старше Дениса на 10 лет, но узнаваем и уважаем только в узком семейном кругу. Не сомневайтесь, вы все сделали правильно. Вдруг молодые начнут следовать этим советам?!
  • +7
    Из комментов складывается впечатление, что у части хабропользователей история ценится выше контента.
    • +9
      между прочим, типичный пример протухшей комьюнити — когда вместо обсуждения идеи начинается обсуждение личности
      причем в зеркало заднего вида
      осталось еще добавить, чтобы размер шрифта в комменте зависел от кармы
  • –1
    Кстати, пункт 3 и 4 содержат лишь долю шутки. К сожалению, некоторые компании воспринимают пункт 3 полностью всерьёз :(.
  • +2
    Ох, начал читать — подумал, неужели все так хорошо: используйте дефолтные настройки, забудьте про сложные алгоритмы, не вникайте в БД…
    А потом увидел теги…
    Это за гранью тонкости…
  • +1
    тонко вышло! моё почтение!
  • +2
    Дошло после второго пункта.

    Поглядев на комментарии к статье, пришёл к выводу, что я гениален.
    • +19
      пришёл к выводу, что я гениален.

      Девушка говорит парню:
      — Вань, ты такой умный, находчивый! Ты так много историй знаешь, с тобой так интересно всегда!
      — Маш… да я нормальный, это просто ты дура!

      Статья отличная, а вот хабр шокировал.
  • +4
    Автор молодец!
  • 0
    Прочитал первый же совет, не понял, позвал коллегу, прочитали вместе — не поняли, пошли читать комментарии, дружно поржали дочитав статью вместе. Чувствую хабр, судя по оценкам, не понял настолько тонкого юмора.
  • 0
    Спасибо, автор открыл глаза :)
  • +5
    Последовал всем советам. Ищу новую работу.
  • +1
    Вы забыли про потоки.
    Зачем геммороиться и строить схему взаимодействия потоков, если каждый объект/соединение можно обрабатывать в отдельном потоке?
  • 0
    А как же совет хранить картинки в базе данных? И отдавать их файликом вроде getimage.php? Ведь это решает кучу проблем масштабируемости, и даёт дополнительные возможности — например ресайз фото на лету, без ненужного хранения всех размеров которые могут понадобиться. А еще можно проверяить права доступа к изображению! И да, это была шутка если что)
  • –4
    Имхо, статья о том, как НЕ НАДО делать. Только логично было бы автору еще в конце дописать, что и программисты тоже не нужны, — ставишь себе CMS и из коробки получаешь уже готовый и настроенный GOOGLE…
  • 0
    Даже всякий шлак на хабре уже в почете, печально.
  • +2
    Так и тянется рука злобно отпародировать прочитанное в приложении к HPC :)
  • +1
    Автору — респект. Данная статья учит критически относиться к таким вот опусам в блогах, типа «Уникальные советы».
    Но! Те, кто не в теме, могут воспринять статью серьезно (как некий success story — мало ли как бывает, ну делегировал автор настройку БД всяким там DBA, забил разбираться сам — и все равно получил работающий продукт). Вы бы «вредные советы» в заголовок вынесли, что-ли.
  • +1
    Средний пользователь хабрахабра так умен и элитарен, что не воспринимает сарказм без скриншота из «теории большого взрыва».
  • 0
    Если потребуется дальнейшая оптимизация, то от блоков синхронизации можно просто избавиться, и система заработает еще быстрее!
    Да как же я сразу до этого не додумался!!! Пошел убирать все объекты синхронизации.
    • 0
      Вы знаете, для меня это тоже в свое время было сродни озарению. Так вдруг стало все просто и понятно.
  • 0
    Я могу гордиться своей работой! У нас половина этих советов уже используется.
  • +1
    А в чем цель статьи? Посмеяться на тему? Заставить задуматься? Простая инверсия данных советов не даст рецепта «как писать хайлоад-приложения».
    В разработке действительно больших и сложных систем нет серебряных пуль. А вопросы когда лучше докупить сервер, а когда заняться оптимизацией вообще каждый раз решаются отдельно, и далеко не всегда понятно, что окажется рентабельнее в долгосрочном плане.
  • 0
    Сбербанк пользуется данными советами уже давно.
  • +2
    Опять mail.ru со своими докладами!

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