Инвалидация

К вопросу об инвалидации кеша

Инвалидация

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

Ну а чтобы не писать обо всём и ни о чём, сконцентрируемся на кешировании результатов SQL-запросов построенных с помощью ORM, которые в наше время встречаются нередко.

Начнём всё же с общих соображений не специфичных ни для SQL-запросов, ни для ORM. Упомянутые полноту и избыточность я определяю следующим образом.

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

Рассмотрим для примера распространённый способ инвалидации по времени. С одной стороны, он практически гарантирует, что сразу после изменения данных кеш грязен. С другой стороны, время которое кеш остаётся грязным, мы можем легко ограничить уменьшив время жизни (что в свою очередь сократит процент попаданий). Т.е.

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

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

Инвалидация по событию

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

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

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

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

Автоматическая инвалидация ORM-запросов

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

Допустим мы выполняем запрос:select * from post where category_id=2 and published
и кешируем его. Очевидно, нам нужно сбросить запрос если при добавлении/обновлении/удалении поста для его старой или новой версии выполняется условие category_id=2 and published=true.

Через некоторое время для каждой модели образуются списки инвалидаторов, каждый из которых хранит список запросов, которые должен сбрасывать:
post:
    category_id=2 and published=true:
        select * from post where category_id=2 and published
        select count(*) from post where category_id=2 and published
        select * from post where category_id=2 and published limit 20
    category_id=3 and published=true:
        select * from post where category_id=3 and published limit 20 offset 20
    category_id=3 and published=false:
        select count(*) from post where category_id=3 and not published  foo:

    a=1 or b=10: 

        or_sql

    a in (2,3) and b=10:

        in_sql

    a>1 and b=10: 

        gt_sqlи т.д. В реальности в инвалидаторах удобнее хранить списки ключей кеша, а не тексты запросов, тексты здесь для наглядности. Посмотрим, что будет происходить при добавлении объекта. Мы должны пройти по всему списку инвалидаторов и стереть ключи кеша для условий, выполняющихся для добавленного объекта. Но инвалидаторов может быть много, и храниться они должны там же где сам кеш, т.е. скорее всего не в памяти процесса и загружать их все каждый раз не хотелось бы, да и последовательная проверка всех условий больно долга. Очевидно, нужно как-то группировать и отсеивать инвалидаторы без их полной проверки. Заметим, что картина когда условия различаются только значениями. Например, инвалидаторы в модели post все имеют вид category_id=? and published=?.. Сгруппируем инвалидаторы из примера по схемам:post:
    category_id=? and published=?:
        2, true:
            select * from post where category_id=2 and published
            select count(*) from post where category_id=2 and published
            select * from post where category_id=2 and published limit 20
        3, true:
            select * from post where category_id=3 and published limit 20 offset 20
        3, false:
            select count(*) from post where category_id=3 and not published  foo:

    a=? or b=?:

        1, 10:             or_sql

    a in ? and b=?:

        (2,3), 10:             in_sql

    a > ? and b=?:

        1, 10:             gt_sql Обратим внимание на условие category_id=? and published=?, зная значения полей добавляемого поста, мы можем однозначно заполнить метки «?». Если объект:{id: 42, title: «…», content: «…», category_id: 2, published: true}
, то единственный подходящий инвалидатор из семейства будет category_id=2 and published=true и, следовательно нужно стереть соответствующие ему 3 ключа кеша. Т.е. не требуется последовательная проверка условий мы сразу получаем нужный инвалидатор по схеме и данным объекта. Однако, что делать с более сложными условиями? В отдельных случаях кое-что можно сделать: or разложить на два инвалидатора, in развернуть в or. В остальных случаях либо придётся всё усложнить, либо сделать инвалидацию избыточной, отбросив такие условия. Приведём то, какими будут инвалидаторы для foo после таких преобразований:foo:
    a = ?:
        1: or_sql
    b = ?:
        10: or_sql, gt_sql
    a = ? and b = ?:
        2, 10: in_sql
        3, 10: in_sql
Таким образом, нам нужно для каждой модели только хранить схемы (просто списки полей), по которым при надобности мы строим инвалидаторы и запрашиваем списки ключей, которые следует стереть.

Приведу пример процедуры инвалидации для foo. Пусть мы запросили из базы объект {id: 42, a: 1, b: 10}
сменили значение a на 2 и записали обратно. При обновлении процедуру инвалидации следует прогонять и для старого, и для нового состояния объекта.

Итак, инвалидаторы для старого состояния: a=1, b=10, a=1 and b=10, соответствующие ключи or_sql и gt_sql (последний инвалидатор отсутсвует, можно считать пустым). Для нового состояния получаем инвалидаторы a=2, b=10, a=2 and b=10, что добавляет ключ in_sql.

В итоге стираются все 3 запроса.

Реализация

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

Источник: https://habr.com/ru/post/120471/

Инвалидация

Инвалидация

Для начала предлагаю определиться с терминами: под инвалидацией будем понимать такое сообщение в коммуникации, которое реципиент (получатель) с высокой вероятностью сможет трактовать как «ты не испытываешь тех чувств, о которых ты говоришь» или как «чувства, о которых ты говоришь как о важных, на самом деле не важны» (вариант — «ты испытываешь не те чувства / не в той степени интенсивности, которую предполагает ситуация»).

Проще всего, как это часто бывает показать на примере: ребёнок потерял игрушку, пришёл к матери, плачет, жалуется. А та ему отвечает: «Подумаешь, ерунда какая! Игрушку потерял… Вот у меня: муж ушёл, ипотека не выплачена и тебя, троглодита, кормить нечем!»

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

Почему это плохо

Есть разные подходы к объяснению инвалидации: сюда можно зайти через теорию двойного посыла (выбирайте Бейтсона или хоть самого Фрейда — по вкусу), можно подтянуть Линехан, которая чуть ли ни выводит из неё формирование ПРЛ, можно даже НЛП-шников вспомнить. Да хоть бихевиористов, на самом деле (противоречивая система наказаний и подкреплений и там рассматривается).

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

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

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

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

Если вспомнить теорию объектных отношений, то инвалидация делает привязанность небезопасной и способствует расщеплению репрезентаций Эго и объектов. «Ну-не-может моя хорошая мама такое сказать», — думает ребёнок, — и создаёт у себя в голове две модели мамы: Хорошую Маму и Плохую Маму (а зачастую ещё и такую же пару для себя самого).

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

А уж если она сопровождается расщеплением — то тут и до эмоциональной нестабильности (необязательно ПРЛ / БАР, тут всё-таки генетика нужна соответствующая) недалеко.

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

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

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

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

Отдельно хочется сказать об «инвалидации по факту наличия психического заболевания». Являясь одной из разновидностей стигматизации, она заслуживает отдельного рассмотрения.

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

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

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

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

Почему это сложно

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

Дело в том, что зачастую люди действительно неадекватно реагируют на что-то (например, в результате той самой инвалидации, полученной в детстве, но не только). Ну, или нам может так казаться (я не верю во всякие Объективные Реальности и прочие На-Самом-Деле), но казаться крайне убедительно.

И вот тут начинаются проблемы. Кем бы вы ни были — врачом, психологом или просто человеком, состоящим с субъектом в близких отношениях, вам вряд ли захочется на своё проявление заботы или поддержки получить что-то вроде «ты меня мучаешь» / «ты надо мной издеваешься» или вообще услышать о том, что «то, что ты делаешь — аморально».

И, тем более, вам вряд ли захочется валидировать (действие, противоположное по смыслу инвалидации) подобные построения — будь они простой обидой, манипуляцией или проявлением продуктивной симптоматики.

И вы попадёте в ловушку: валидировать подобное означает дать положительное подкрепление и умножить проявление таких реакций в будущем (а вам с этим ещё жить / работать), не валидировать — [в субъективной реальности особо чувствительных травматиков] — инвалидировать со всеми вытекающими.

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

Самое внятное предложение по выходу из этой ситуации мне попалось у Янга, который называл это «эмпатической конфронтацией».

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

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

или

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

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

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

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

Вопросы

Хорошего решения у меня, как обычно, нет.

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

Но отсутствие хорошего решения не означает отсутствия решения вообще. Есть плохое, и заключается оно в том, чтобы задавать друг другу вопросы. Любые уточняющие контекст вопросы вроде:

«Ок, я над тобой издеваюсь, а какими способами? А почему именно этими способами? А почему именно над тобой? А почему именно я?».

Разумеется, вопросы должны быть сообразны контексту, иначе толку не будет. Цель — привести контрагента к состоянию, когда он заново и более детально обдумает ситуацию, начнёт строить новую её модель у себя в голове.

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

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

И то, что я много раз был «с другой стороны» — плохо помогает.

Что делать?

А ничего. По крайней мере, ничего специфического.

Стараться не манипулировать, прокачивать собственную осознанность, чтобы понимать истинные причины своих слов и поступков, работать над распознаванием чужих и собственных эмоций, использовать несовершенные, но иногда работающие методы вроде вопросов и эмпатический конфронтации (не только в работе, но и в обычной жизни, ИМХО, это коммуникационные навыки, нужные далеко не только психологам).

И главное — стараться помнить о том, что нет никакого «на самом деле», а Другие — настолько другие, что их мир может быть весьма и весьма отдалённо похож (если вообще) на ваш собственный.

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

, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Источник: https://bootandpencil.ru/blog/%D0%B8%D0%BD%D0%B2%D0%B0%D0%BB%D0%B8%D0%B4%D0%B0%D1%86%D0%B8%D1%8F/

AEM Dispatcher. Часть 4: Инвалидация кэша

Инвалидация

Предыдущие части:

Многие из нас скорее всего сталкивались с ситуацией, когда диспетчер использует старую версию кода (CSS, HTML, JS). В этой статье я расскажу о правильной конфигурации инвалидации страниц на диспетчере чтобы таких проблем не возникало.

Инвалидация — это механизм для указания устаревших кэшированных ресурсов. Существуют несколько инструментов для автоматической инвалидации и инвалидации вручную.

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

Начальные настройки секции инвалидации

Внутри секции /cache находится блок /invalidate, который устанавливает кэшированные файлы, доступные для автоматической инвалидации при обновлении контента. Например, следующие настройки устанавливают инвалидацию всех HTML страниц:

/cache{ /invalidate { /0000 { /glob «*» /type «deny» } /0001 { /glob «*.html» /type «allow» } }}

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

/cache{ /invalidate { /0000 { /glob «*» /type «allow» } }}

Перезапустите httpd-сервер после обновления секции /invalidate для применения новых изменений.

Инвалидация в подробностях

На низком уровне диспетчер использует специальные пустые стат-файлы с именем по умолчанию «.stat».

По умолчанию установлен параметр /statfileslevel «0», что подразумевает использование только одного стат-файла, располагающегося в корне папки htdocs.

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

Например, пусть у нас есть следующие кэшированные ресурсы после запроса страницы http://localhost/content/geometrixx/en/products.html :

Давайте инвалидируем их, применяя низкоуровневый механизм стат-файлов. Создайте пустой файл с именем «.stat» в корне вашей папки htdocs:

Обратите внимание, что время изменения стат-файла более позднее, чем кэшированных ресурсов. Для диспетчера это означает, что все ресурсы устаревшие. Это и есть низкоуровневый механизм инвалидации. Если мы после создания такого стат-файла снова посетим страницу http://localhost/content/geometrixx/en/products.html, то запрашиваемые кэшированные ресурсы будут обновлены:

Этот пример иллюстрирует схему инвалидации с параметром по умолчанию /statfileslevel «0». Давайте разберёмся, как мы можем настроить инвалидацию более тонко и детально при помощи параметра /statfileslevel.

Настройка параметра /statfileslevel

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

  • Диспетчер создаёт .stat файлы в каждой папке начиная от docroot и до уровня, который вы указываете. Уровень папки docroot равен 0.
  • При обновлении файла диспетчер по пути файла находит папку, лежащую на уровне statfileslevel и инвалидирует все файлы этой папки и все файлы, лежащие ниже внутри этой папки.
  • Если уровень обновлённого файла меньше уровня statfileslevel, тогда инвалидируются только файлы папки, содержащей обновлённый файл, но файлы, лежащие ниже внутри этой папки остаются валидными.
  • При обновления файла все файлы соответствующей папки и выше вплоть до корневого уровня включительно станут невалидными.

Для лучшего понимания правил свойства /statfileslevel давайте рассмотрим парочку примеров. Наш демонстрационный случай по умолчанию, в котором /statfileslevel «0», выглядит следующим образом:

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

Если мы установим /statfileslevel «4», тогда инвалидация работает уже вот так:

Стат-файлы есть на всех уровнях начиная от 0 (корень) до 4 включительно.

Стат-файл на уровне меньше 4 обладает областью ответственности или зоной охвата только содержащей его папки. Это означает, что если стат-файл внутри папки content/geometrixx/en новее, чем какой-то файл из этой папки, то такой файл является невалидным, но валидация всех файлов из остальных папок определяется другими стат-файлами.

Только стат-файлы, лежащие на уровне со значением statfileslevel (в нашем случае уровень равен 4), обладают областью ответственности или зоной охвата всего нижележащего дерева, начинающего от папки, содержащей такой стат-файл и простирающегося вниз до более низких уровней файлового дерева.

Это означает, что если у стат-файла внутри папки content/geometrixx/en/products время изменения новее, чем у какого-то файла из нижележащего файлового дерева, включающего папку products, тогда диспетчер считает такой файл невалидным.

Валидация всех файлов, которые не находятся в этом дереве, определяется другими стат-файлами.

Автоматическая инвалидация и flush-агенты

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

  • Диспетчер должен быть доступен для авторского AEM-сервера. Если ваша сеть (например, из-за файрвола) настроена так, что доступ между обоими закрыт, тогда автоматическая инвалидация работать не будет.
  • Публикация и инвалидация кэша происходит одновременно. Пользователь может запросить страницу сразу после того, как она была удалена из кэша, но ещё до того, как страница была опубликована. В такой ситуации AEM возвращает старую страницу, а диспетчер эту старую страницу кэширует снова и считает её теперь валидной. Эта проблема больше касается крупных сайтов.

Публичный flush-агент находится по адресу http://localhost:4503/etc/replication/agents.publish/flush.html

Для включения публичного flush-агента нажмите кнопку «Edit» и установите галочку в чекбоксе «Enabled»:

Также обновите URI порт на вкладке Transport и установите его значение равным 80:

Сохраните ваши изменения, и вы увидите, что публичный flush-агент активировался:

Запросы для инвалидации вручную

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

  • для удаления кэшированных файловPOST /dispatcher/invalidate.cache HTTP/1.1CQ-Action: ActivateCQ-Handle: path-patternContent-Length: 0
  • для удаления и перекэширования файловPOST /dispatcher/invalidate.cache HTTP/1.1CQ-Action: Activate Content-Type: text/plainCQ-Handle: path-patternContent-Length: numchars in bodypage_path0Page_path1…Page_pathn

Резюме

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

Подробная и полезная документация доступна на следующих страницах:

Виталий Киселев, AEM Разработчик

Источник: https://www.axamit.com/ru/blog/adobe/dispatcher-4

Все термины
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: