Tips & Tricks: PHP, Rails, Nginx, Apache, Passanger, MySQL, PgSQL, RabbitMQ, Memcached, …

Иногда возникает необходимость скрыть графическую информацию от ботов при парсинге страницы.
Был найден простой но интересный способ на страницах lifehacker.ru.

Continue Reading >>

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

Sophia.org – проект, написанный на ruby с использованием фреймворка Rails. Для организации backend-кэширования на sophia.org мы используем Memcached. Memcached – прекрасное решение разряда key-value, обеспечивающее быстрый доступ к данным и превосходную кластеризацию “из коробки”. Вместе с тем одной из его проблем является массовая инвалидация.

В Rails для организации доступа к cache имеется объект Rails.cache (являющийся сам по себе экземпляром класса ActiveSupport::Cache::Store). Он работает поверх любого cache storage. Наше решение действует на уровне Rails.cache, следовательно, будет работать не только для MemCacheStore, но и для других кэш-хранилищ, которые могут быть использованы с Rails.

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

Rails.cache.write('key-name', 'value-string', :tags => %w(user1 group2))

Каждый тэг имеет текущую, “актуальную” версию (ее мы также храним в memcached), а вместе с записью мы сохраняем набор тэгов, относящихся к записи. Вместе с тэгами мы сохраняем их текущие версии. Таким образом, сохраненная запись будет иметь следующую структуру (это, разумеется, псевдокод):

{key: 'key-name', value: 'value-string', tags: [{name: user1, version: 1}, {name: group2, version: 1}]}

Кроме того, были созданы записи:

{key: 'tag-user1',  value: 1}
{key: 'tag-group2', value: 1}

Теперь, когда мы будем доставать из кэша эту запись, мы будем извлекать из нее версии тэгов и проверять их актуальность. В случае если сохраненная версия тэга в записи и актуальная версия тэга не равны, – мы признаем запись устаревшей. Т.е. когда нам понадобится инвалидировать запись с ключом ‘key-name’, мы будем не удалять ее из кэша – мы просто увеличим версию тэга на 1.

{key: 'tag-group2', value: 2}

Все, запись ‘key-name’ уже устарела, т.к. запись ссылается на версию 1 тэга group2.

На практике в нашем проекте мы часто кэшируем фрагменты views. Эти фрагменты зависят от текущего пользователя, смотрящего на них, самого объекта, его версии, каких-то сторонних объектов. Следовательно, если мы изменяем объект – возможно, мы изменяем и право какого-то пользователя посмотреть на фрагмент. Чтобы не перечислять все названия фрагментов кэша, мы инкрементируем версию тэга, связанного с этим объектом, инвалидируя таким образом все записи, связанные с ним.

Конечно, более стандартным подходом, используемым для этих целей, является хранение значения updated_at объекта в имени ключа (это происходит в стандартном методе ActiveRecord::Base#cache_key) . Но этот подход лично для нас не работает, т.к. зачастую изменяется не сам объект, а какие-то записи, связанные с ним по ассоциациям, из-за чего какой-нибудь счетчик на странице принимает другое значение. Согласитесь, изменять timestamp объекта из-за появившейся связи в таблице связи с другим объектом не очень культурно.

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

Описанное выше решение выложено на github, и с ним можно ознакомиться и начать использовать.

P.S. Если кто-нибудь решил проблему именования, пожалуйста, сообщите нам. Спасибо.


$ irb
1.9.3-p0-perf :001 > puts a if a = 4
(irb):1: warning: found = in conditional, should be ==
NameError: undefined local variable or method `a' for main:Object
from (irb):1
from /home/saks/.rvm/rubies/ruby-1.9.3-p0-perf/bin/irb:16:in `<main>'

 

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

Еще одна неожиданная упячка в ruby:

vitaly@way:~/projects$ irb
1.9.3p0 :001 > a
NameError: undefined local variable or method `a' for main:Object
from (irb):1
from /home/vitaly/.rvm/rubies/ruby-1.9.3-p0-perf/bin/irb:16:in `<main>'
1.9.3p0 :002 > a = a
=> nil
1.9.3p0 :003 > a
=> nil

1.9.3p0 :004 > a = b
NameError: undefined local variable or method `b’ for main:Object
from (irb):4
from /home/vitaly/.rvm/rubies/ruby-1.9.3-p0-perf/bin/irb:16:in `<main>’

Wtf?

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

Continue Reading >>

В один прекрасный момент приложение на нашем production-сервере (написанное на Ruby On Rails), начало докучать нам не страшными, но противными Exception’ами. Выглядело это так:

CollectionsController# (ActionView::MissingTemplate) “Missing template collections/show, application/show with {:handlers=>[:erb, :builder, :haml], :formats=>[\"*/*;q=0.9\"], :locale=>[:en, :en]}.

Continue Reading >>

На начальном этапе разработки стартапа многие разработчики сталкиваются с проблемой создания достаточно гибкой и в будущем масштабируемой архитектуры приложения, оптимизированной для большой нагрузки. Мы хотели бы поделиться опытом в этой области. В качестве примера рассмотрим методы построения ресурса, базирующегося на Ruby On Rails. Тем не менее примеры, которые будут приведены ниже, с легкостью могут быть использованы для LAMP решений.

Серверы можно разделить на несколько типов по ролям:

  • Application
  • Database
  • Load Balancer
  • Utils
  • Tools

Application – это front-end сервер. Возможно использование как одного, так и нескольких Application серверов. В случае использования одного Application сервера надобности в балансировке нагрузки нет.

  • Application сервер выполняет следующие функции:
  • обработка запросов от пользователей (получает запросы от балансировщика нагрузки),
  • взаимодействие с базой данных (Database server),
  • отправка сообщений в очередь сообщений (Utils server),
  • взаимодействие с CDN,
  • взаимодействие с search engines (Util server),
  • инвалидация кэша,
  • работа с кэшем (Database server).

Continue Reading >>