Такое не забывается — Oracle Database In-Memory

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

    В последние годы логический момент для этого, казалось бы, настал. Стоимость оперативной памяти падала, падала, и упала совсем. Еще в начале века казалось, что 256 МБ памяти для сервера — это нормально, и даже много. Сегодня нас не удивишь параметром 256 ГБ оперативной памяти на сервере начального уровня, а с промышленными серверами вообще настал полный коммунизм, любой благородный дон может набрать хоть терабайт оперативной памяти на сервере, если захочет.

    Но дело не только в этом — появились новые технологии индексирования данных, новые технологии компрессии данных — OLTP-компрессия, компрессия неструктурированных данных
    (LOB Compression). В Oracle Database 11g, например, появилась технология Result Cache, которая позволяет кэшировать не просто строки таблиц или индексы, но и сами результаты запросов и подзапросов.

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

    А вот что. Давайте вспомним о том, что существует, по сути дела, две категории баз данных: строчные базы данных, которые и в буферном кэше в оперативной памяти, и на диске хранят информацию в строчном виде — Oracle Database, Microsoft SQL Server, IBM DB/2, MySQL и т.д.; и колоночные СУБД, в которых информация хранится по столбцам, и которые большого распространения в индустрии, к сожалению, не нашли. Строчные базы данных хорошо обрабатывают OLTP-операции, а вот для обработки аналитики больше подходят, вы будете смеяться, колоночные базы данных — зато DML-операции для них проблема, ну вы поняли, почему. Промышленность, как вы знаете, пошла по пути строчных баз данных, на которые в виде компромисса навешиваются аналитические возможности.

    И вот, появилась технология Oracle Database In-Memory, в которой преимущества обоих подходов наконец-то совмещены.

    И что получается?


    Получается фантастика. Обработка транзакций ускоряется в два раза, вставка строк происходит в 3–4 раза быстрее, запросы для аналитики выполняются в реальном времени, практически мгновенно! Маркетологи говорят, что аналитика стала в сто раз быстрее, но это они скромничают, чтобы не пугать рынок, реальные результаты куда более впечатляющие.

    А теперь давайте разбираться, как и в чем это работает.

    Итак, технология появилась в версии Oracle Database 12.1.0.2, и смысл ее в том, что рядом с нашим привычным буферным кэшем, который хранит строки таблиц и блоки индексов, находится новый кэш, точнее новая разделяемая область для данных в оперативной памяти, в которой данные из таблиц хранятся в колоночном формате! Вы поняли, да? И строчный и колоночный формат хранения в памяти для одних и тех же данных и таблиц! Причем данные одновременно активны и транзакционно согласованы. Все изменения, как обычно, сначала производятся в обычном буферным кэше, после чего отражаются в колоночном, или, как его называют наши англоязычные друзья, «колумнарном» кэше.

    Несколько важных деталей. Во-первых, в колумнарном кэше отражаются только таблицы, то есть индексы не кэшируется — это первое. Во-вторых, технология не делает ненужную работу. Если данные читаются, но не изменяются, то в обычном, то есть в строчном буферном кэше хранить их незачем. А вот если данные изменяются, тогда их надо хранить в обоих кэшах, буферном и колоночном. Ну, и соответственно быстрее работает аналитика потому что для нее более эффективно именно колоночное представление информации. Это второе. И в-третьих — еще раз, чтобы было понятно. В колоночном кэше хранятся не блоки данных с диска. В блоках на диске информация хранится по строкам. В колумнарном кэше информация хранится по столбцам, в своем собственном представлении, в так называемых In-Memory «компресс юнитах». Это третье.

    А теперь детали


    Мы поняли, что аналитика работает в сотни раз быстрее, потому что колоночное представление для нее более эффективно — а, собственно говоря, почему?

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

    image
    Такие операции сканирования очень характерны для ERP-приложений, для хранилищ данных в аналитических системах. Согласитесь, нужная штука для прогресса человечества.
    Технически, чтобы это запустить, нужно включить кэширование для нужных столбцов в таблице. Для этого предназначено специальное расширение синтаксиса команды ALTER TABLE:

    SQL> ALTER TABLE cities
     	INMEMORY
    	 	INMEMORY (Id, Name, Country_Id, Time_Zone) 
    	 	NO INMEMORY (Created, Modified, State);
    Table altered.
    

    Делается это один раз, информация записывается в системный словарь СУБД Oracle, после чего автоматически используется базой данных в процессе своей работы. В вышеприведенном примере служебные столбцы не участвуют в отчетах, они нужны только для внутреннего аудита приложения, и поэтому мы их не кэшируем.
    Можно указать кэширование по всем столбцам для материализованного представления:

    SQL> ALTER MATERIALIZED VIEW cities_mv INMEMORY

    Materialized view altered.

    Можно включить кэширование на уровне всего табличного пространства:

    SQL>  ALTER TABLESPACE tbs_data DEFAULT INMEMORY
    	MEMCOMPRESS FOR CAPACITY HIGH
    	PRIORITY LOW;
    Tablespace altered.
    

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

    SQL> CREATE TABLE customers .......
    	PARTITION BY LIST</b>
    	 	PARTITION p1 ....... INMEMORY,
    	 	PARTITION p2 ....... NO INMEMORY);
    

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

    За сценой


    Что происходит, когда мы включаем кэширование и информация записывается в словарь СУБД Oracle? Происходит волшебство — SQL-оптимизатор перестраивает план запроса. В первый раз, когда запрос приходит из приложения, на этапе так называемого жесткого парсинга (hard parse), генерируется план выполнения запроса.

    image

    В данном примере подсчитывается общее количество строк в табличке справочника городов CITIES. Оптимизатор видит, что выгодно выполнить запрос по колоночному представлению, и выполняет сканирование TABLE ACCESS INMEMORY FULL. Для приложения это полностью прозрачно, переписывания или модификации приложения не требуется!

    Сама база данных Oracle при этом используют ряд интересных техник оптимизации:

    1. Каждое процессорное ядро производит сканирование собственного столбца. При этом используются быстрые векторные SIMD-инструкции, т.е. специальные команды процессора, которые в качестве аргументов используют не скалярные значения, а вектор значений. Это дает сканирование миллиардов строк в секунду.
    2. Мы не просто сканируем данные. Происходит сканирование и соединение данных из нескольких таблиц. На колоночном представлении это гораздо эффективнее, чем обычные joins. Такие joins выполняются в среднем в 10 раз быстрее.
    3. В процессе выполнения запроса используется технология In-Memory Aggregation: в памяти создается динамический объект — промежуточный отчет. Объект заполняется во время сканирования таблицы и позволяет ускорить выполнение запроса. В результате отчеты строятся в 20 раз быстрее без заранее созданных аналитических кубов.
    4. Чтобы не загромождать оперативную память, используется сжатие столбцов в памяти. Есть шесть вариантов:

    • NO MEMCOPRESS — без сжатия
    • MEMCOMPRESS FOR DML — оптимизированный для DML-операций
    • MEMCOMPRESS FOR QUERY LOW — оптимальный вариант, который используется по умолчанию
    • MEMCOMPRESS FOR QUERY HIGH — оптимизированный для скорости выполнения запроса и для экономии памяти
    • MEMCOMPRESS FOR CAPACITY HIGH — оптимизированный для скорости выполнения запроса
    • MEMCOMPRESS FOR CAPACITY LOW — оптимизированный для экономии памяти

    Например:

    SQL> ALTER TABLE cities
    	INMEMORY
    	 	INMEMORY MEMCOMPRESS FOR CAPACITY HIGH(Country_Id,Time_Zone)
    	 	INMEMORY NO MEMCOMPRESS (Id, Name, Name_Eng);
    Table altered.
    


    В этом примере выбраны для сжатия столбцы данные в которых часто повторяются, уникальные столбцы не сжимаются.
    В системной таблице словаря USER_TABLES появился новый атрибут сегмента INMEMORY. Столбец INMEMORY также появился в системных таблицах *_TAB_PARTITIONS. Чтобы узнать, что и в каком объеме находится в кэше, нужно использовать специальное системное представление V$IM_SEGMENTS:

    image

    В этом примере мы видим, что в кэше находятся четыре таблицы, причем на диске каждая таблица занимает примерно по 5 МБ, а в памяти, за счет сжатия — от 100 КБ до 1 МБ. Колонка POPULATE_STATUS показывает статус информации. Видим, что таблицы CITIES, COMPANIES, и AIRPORTS уже полностью загрузились в In-Memory-кэш, а COUNTRIES — еще не полностью, 400 КБ осталось загрузить. То есть именно сейчас эта таблица транспонируется в формат по столбцам и загружается в кэш.

    Первым делом — приоритеты


    С технической точки зрения чтение в память с диска может происходить двумя способами:

    • При первом обращении к данным. Это возможность по умолчанию.
    • Автоматически после старта экземпляра БД. Эта возможность включается установкой атрибута сегмента PRIORITY.

    Во втором варианте чтение производят фоновые процессы ORA_W001_orcl (W001 — номер экземпляра), количество фоновых процессов регулируется с помощью нового параметра INMEMORY_MAX_POPULATE_SERVERS. В результате после рестарта экземпляр сразу доступен для работы в фоновом режиме, и время старта экземпляра при этом не увеличивается. Конечно, в начале возрастает нагрузка на процессор, куда ж деваться. Зато потом будут быстрее работать аналитические запросы.

    Приоритетом загрузки в кэш можно управлять, вот варианты значений приоритета:

    image

    Допустим, мы держим в таблице cities справочник городов, и этот справочник постоянно нужен всем пользователям, он постоянно участвует в отчетах. В этом случае мы должны указать для этой таблицы критический приоритет, тем самым мы заставим систему автоматически считать экземпляр этой таблицы в кэш при старте базы данных:
    SQL> ALTER TABLE cities 
    	INMEMORY
    	 	PRIORITY CRITICAL
    	 	INMEMORY MEMCOMPRESS FOR CAPACITY HIGH(Country_Id, Time_Zone)
    	 	INMEMORY MEMCOMPRESS NO (Id, Name, Name_Eng);
    
    Table altered.
    

    А как же OLTP?


    Как вы прекрасно знаете, чистых OLTP-систем практически не бывает. В любом OLTP-приложении есть поддержка отчетности, а для отчетности нужны дополнительные индексы. А что такое дополнительные индексы? Это ни что иное, как дополнительные накладные расходы на вставку данных.

    image

    А теперь разрешите мне с гордостью сообщить вам о том, что при переходе на Oracle Database In-Memory решается и эта проблема, потому что в этой технологии — правильно! — не используются индексы. Т.е. мы можем просто удалить те индексы, которые нужны нам для аналитики, и получаем парадоксальный эффект — система, предназначенная для повышения скорости работы хранилищ данных, прекрасно «разгоняет» и OLTP-приложения.

    image

    При этом тем операциям, которые и раньше работали хорошо, технология Oracle Database In-Memory не мешает и не пытается помочь (принцип «не надо чинить то, что не сломалось», в действии!), поскольку в ядре базы данных она находится, так сказать, в стороне. То есть на формат данных на диске эта технология никак не влияет, все работает точно так же, как и раньше, какую бы файловую систему вы ни использовали. Файлы данных не меняются, журнал, резервирование, восстановление данных работают по-прежнему. Все технологии, в том числе ASM, RAC, DataGuard, GoldenGate — работают прозрачно.

    Контейнерная архитектура


    Главное архитектурное новшество в Oracle Database 12c — это контейнерная архитектура. Oracle Database In-Memory полностью поддерживает эту архитектуру. Параметр INMEMORY_SIZE устанавливается на уровне всей контейнерной базы данных, а на уровне конкретных баз данных его можно варьировать в зависимости от конкретного приложения. Например, на уровне контейнерной базы данных вы можете установить INMEMORY_SIZE равным 20 ГБ, а на уровне контейнеров — не включать кэш для ERP, для CRM установить объем кэша 4 ГБ, для хранилища данных — 16 ГБ.

    image

    Кластерная архитектура


    Да, и в кластерах Oracle Real Application Cluster это тоже работает. Можно управлять распределением объектов в кэше In-Memory между узлами в кластере. Например, можно указать опцию DUPLICATE, тогда при изменении кэша на одном из узлов кластера они будут автоматически синхронизироваться со вторым узлом, это нужно для того, чтобы всегда существовала доступная копия кэша с «разогретыми» колумнарными данными:
    SQL> ALTER TABLE cities INMEMORY
    	DUPLICATE;
    
    Table altered.
    

    Другие варианты:

    • AUTO DISTRIBUTE — синхронизацией кэша управляет СУБД (используется по умолчанию);
    • DUPLICATE ALL — на всех узлах кластера синхронизируется одинаковый кэш;
    • DISTRIBUTE BY ROWID RANGE;
    • DISTRIBUTE BY PARTITION;
    • DISTRIBUTE BY SUBPARTITION.

    Опция DUPLICATE и DUPLICATE ALL работает только на Oracle Exadata и Oracle SuperCluster, на обычном сервере эта опция игнорируется. Остальные варианты нужны для более гибкого управления — например, с помощью параметра DISTRIBUTE BY ROWID RANGE можно указать, что часть секций должна находиться в колоночном виде на одном узле, а остальные — на другом узле.

    Резюме


    Не могу больше скрывать от вас полный синтаксис команды ALTER TABLE INMEMORY. Вот он:
    SQL> ALTER TABLE cities 
    	INMEMORY
    	 	PRIORITY CRITICAL</b>
    	 	DUPLICATE</b>
    	 	INMEMORY MEMCOMPRESS FOR CAPACITY HIGH (Country_Id, Time_Zone)
    	 	INMEMORY MEMCOMPRESS NO (Id, Name, Name_Eng)
    	 	NO INMEMORY (Created, Modified, State);
    
    Table altered.
    

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

    Для настоящих инженеров есть возможность тонко настраивать свои запросы с помощью хинтов SQL-оптимизатора: INMEMORY,NO_INMEMORY, INMEMORY_PRUNING и NO_INMEMORY_PRUNING.
    NO_INMEMORY, как видите, является здесь простейшим хинтом. Например, можно в явном виде дать оптимизатору указание не использовать технологию In-Memory — если вы уверены в том, что это попросту не нужно, потому что у вас хороший запрос, построены индексы и т.д. Еще два интересных хинта — INMEMORY_PRUNING и NO_INMEMORY_PRUNING, они управляют использованием storage-индексов. Storage-индекс хранит минимальное и максимальное значение столбца в каждом экстенте памяти кэша и прозрачно исключает ненужные сканирования столбцов, например: WHERE prod_id> 14 AND prod_id < 29.

    В файле инициализации INIT.ORA появились новые параметры, дарю их вам безвозмездно, то есть даром:

    • INMEMORY_SIZE
    • INMEMORY_FORCE= { DEFAULT | OFF }
    • INMEMORY_CLAUSE_DEFAULT= [INMEMORY] [NO INMEMORY] [compression-clauses][priority-clauses]
    • INMEMORY_QUERY={ENABLE | DISABLE}
    • INMEMORY_MAX_POPULATE_SERVERS
    • OPTIMIZER_INMEMORY_AWARE
    • INMEMORY TRICKLE REPOPULATE SERVERS PERCENT

    INMEMORY_SIZE позволяет указать размер области памяти для колумнарных данных, по умолчанию равен нулю. Например, INMEMORY_MAX_POPULATE_SERVERS — это количество фоновых процессов, которые считывают данные с диска в кэш, по умолчанию он равен количеству процессоров, которые «видит» Oracle Database. Еще один интересный параметр — OPTIMIZER_INMEMORY_AWARE, при его помощи можно указать, видит или не видит оптимизатор In-Memory-кэш. Например, это нужно, если вы нужно оценить накладные расходы. Подробности предлагаю вам найти в документации.

    Oracle Database In-Memory больше всего подходит для приложений, в которых много запросов, сканирующих много строк с такими фильтрами как: «=», «<», «>», «IN». Технология очень эффективна, когда приложение запрашивает всего лишь несколько столбцов из большого числа (типично для SAP), соединяет большие факторные таблицы с таблицами измерений, с фильтрами по таблицам измерений. Соответственно, это такие приложения, как хранилища данных, информационно-аналитические системы, включая OLTP-приложения. Кстати, есть полезный дополнительный продукт — Oracle Database In-Memory Advisor, он помогает оценить применимость технологии Oracle Database In-Memory к конкретным приложениям. Oracle Database In-Memory Adviser анализирует статистику работы базы данных и выдает рекомендации по размеру памяти, по типу таблиц, которые необходимо кэшировать в In-Memory-кэше.

    Важно понимать, что в отличие от конкурентов, Oracle Database In-Memory не требует переписывания приложений. Нет ограничений на SQL, не нужна миграция данных, технология готова для облака.

    Не надо путать Oracle Database In-Memory и Oracle TimesTen In-Memory Database, это разные технологии. TimesTen — это встраиваемая база данных для приложений, она предназначена для чистых, а не смешанных OLTP-систем, для тех случаев, когда приложение должно работать в режиме реального времени, и время ответа должно составлять буквально секунды, а не секунды и не миллисекунды. TimesTen полностью загружает все данные в оперативную память. В отличие от нее Oracle Database In-Memory является расширением классической СУБД Oracle, находится внутри ее ядра и расширяет ее возможности с точки зрения ускорения аналитических запросов за счет колоночного представления.

    Мне кажется, я написал достаточно, чтобы пробудить в вас неудержимое желание прочитать документацию к Oracle Database In-Memory. Но тут дело такое — технология новая, экспертизы по ней мало, один я на все ваши вопросы ответить не смогу. Поэтому, друзья, записывайтесь на наши онлайн-тренинги. Мы их будем прямо здесь будем обязательно анонсировать. А у меня пока все.
    Oracle
    Company
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 28

      +1
      Технология не новая: http://en.wikipedia.org/wiki/K_(programming_language)
      ей уже лет 20+, успешно используется в особенности банках.
        0
        Подтверждаю, в банках в полный рост используют чтобы time series хранить. Вот только язык Q — это для настоящих сварщиков.
        0
        и время ответа должно составлять буквально секунды, а не секунды
          +1
          «В любом OLTP-приложении есть поддержка отчетности» — с чего Вы взяли?
            0
            Наверное, неточно выразился. Имелось в виду, что чистых OLTP систем, то есть приложений, в которых присутствуют только транзакции на вставку/удаление.модификацию, практически не бывает.
            Большинтсво бизнес-приложений являются системами смешанного типа: в них есть и транзакционная нагрузка, и аналитическая нагрузка. Аналитическая нагрузка может быть как в виде отчетности, а может быть и в неявном виде — в виде аналитических запросов, например: проверка балансов при закрытии операционного дня в АБС, выгрузка сводных данных в внешние системы и т.д.
            0
            К сожалению, не догоняю про колонки: являются ли они по сути индексом? Или они как раз наоборот, представляют собой список без какого-либо индекса? Почему join на них гораздо эффективнее?
              0
              Это обычные столбцы, без всяких изменений. Реляционная таблица разворачивается по столбца и в таком виде записывается в память. При этом каждый блок памяти (MEmory Compession Unit) хранит значение только одного столбца. Индексы никак в области колоночного хранения не сохраняются, поскольку не имеют реляционной структуры (не хранятся в виду строк и столбцов).
              Join на колоночном представлении быстрее, поскольку конвертируеится в фильтры. Фильтры быстрее работают на колоночном представленнии, поскольку используют векторные регистры процессора.

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

              Но Вам не нужно думають об этой внутренней кухне — SQL-оптимизатор автоматически сам выбирает для SQL-запроса сканированрие по столбцам или доступ через индекс
                0
                Если индекс имеет высокую селективноcть

                Высокая селективность это до скольки процентов? «говорите точно сколько вешать в граммах» (с)

                А еще подскажите про обычные индексы интересно, как то можно сделать, чтобы аналогично вышеописанному можно было выполнить их прелоад в память фоновыми процессами?
                  0
                  А если заранее снять статистику для оптимизатора dbms_stats.gather_schema_stats?
                  Это не то, что нужно?
                    0
                    Вы про прелоад индекса? Думаю это не то, что нужно… Или сбор статистики загружает индекс с диска в оперативную память?
                      0
                      ну если db_cache_size позволяет, индекс и так будет в памяти лежать
                        0
                        Сразу после старта базы или все таки после первого его использования? Все таки in-memory кэш это одно — когда сначала на диске, первое обращение, прочитанное с диска кэшируется в памяти и повторные обращения уже идут в память к закэшированному (если оно не было вытеснено чем то другим) и in-memory storage это когда еще до обращения все вычитывается в память и всегда там хранится. Я спрашиваю о втором варианте.
                          0
                          нет, я имел ввиду все же первый вариант
              –1
              Разработчики SAP HANA кусают локти от зависти
                0
                Дык в аналитических базах DML особо и не нужен — залил данные раз в день, а остальное через speed layer (я про lambda architecture)
                  0
                  Все верно, — никто не спорит. Эта технология нужна для ускорения запросов. Ведь именно из запросов состоит нагрузка в аналитической системе.
                  0
                  Почему в статье нет реальных замеров насколько увеличилась производительность, время доступа и т.д.?
                    0
                    Ну помилуйте! Технология вышле недавно. Сейчас идут тестирования у ряда крупных заказчиков. Как только тестирования завершатся и заказчик даст разрешение на публикацию, — мы обязательно сообщим конкретные результаты!
                      0
                      >> Технология вышле недавно
                      Ей же вроде почти год или я что-то перепутал?
                    0
                    В каких редакциях поддерживается? Если только в Enterprise — сколько стоит лицензия на эту опцию?
                      0
                      Да эта опция только для Enterprise. Называется Oracle Database In-Memory Option. В прайс-листе Oracle можете найти цену (без скидок!). -Он открыт.
                      Гугл в помощь. :-)
                      0
                      Надеюсь теперь транзакции в Сбербанке будут работать побыстрее
                        0
                        Очень крутая статья! Я читал про эту особенность, но не отнесся к ней серьезно, а зря! Тут она описана очень хорошо! Я как OCP понимаю мощь данной технологии!
                        Но все же я жду когда Оракл полностью реализует поколоночное хранение, а не как сейчас только в Экзадате и только сжатые!
                          0
                          А где написано, что это только в Экзадате?
                            0
                            Поколоночное хранение данных на диске? Только в Экзадате и только в сжатом виде. Разве не знали?
                              0
                              Про хранение на диске не знал, что вообще такое есть, думал поколоночное есть только для in-memory.
                                0
                                Ну вот, вы мне чему-то научили, я вас…
                            0
                            В России у кого-то есть Экзадата?

                          Only users with full accounts can post comments. Log in, please.