Технические аспекты знакоства с девушкой в Интернете

Preview:

DESCRIPTION

Обзор опыта и проблем, решенных при разработке speed-dating сайта Wannafun.ru

Citation preview

Технические аспекты знакомства с девушкой в

интернетеАлексей Найден, Алексей Носков

Evil Martians

воскресенье, 16 декабря 12 г.

воскресенье, 16 декабря 12 г.

воскресенье, 16 декабря 12 г.

Wannafun.ru• Онлайн speed dating

• Знакомство только с теми, кто в сети• Поток лиц aka «матрица»

• 3 минуты чата для принятия решения

• 48% / 52%

воскресенье, 16 декабря 12 г.

Проект в цифрах

• 500 000 пользователей

• 2000 онлайн

• 2-3 события в секунду на юзера

• Более 100 http-запросов в минуту на юзера

• Более 5 000 000 чатов

• Более 45 000 000 сообщений

воскресенье, 16 декабря 12 г.

На чем всё работает

ErlangEventMachine

PostgresRails

RedisResque

воскресенье, 16 декабря 12 г.

воскресенье, 16 декабря 12 г.

Онлайн-взаимодействия

• Знакомства: входящие/исходящие, сообщения• Контакт-лист: личные сообщения, онлайн/оффлайн

• Уведомления

воскресенье, 16 декабря 12 г.

Взаимодействие с браузеромPusher (http://pusher.com/)

Push-канал к клиенту

Нет серверной логики

Faye (http://faye.jcoglan.com/)

Pub/sub

Нет серверной логики

Socket.io (http://socket.io/)

Абстракция над WebSocket, Flash и Polling

Произвольная серверная логика

воскресенье, 16 декабря 12 г.

Серверная реализация socket.io• NodeJS

• На тестах падала VM — epic fail

• EventMachine

• Не было актуальной версии• Erlang

• Не было актуальной версии

воскресенье, 16 декабря 12 г.

Что хорошего в Erlang

• Нет коллбеков - простой последовательный код• Нет разделяемого состояния, структуры данных неизменяемы - concurrency проще

• Иерархия супервизоров - высокая устойчивость• Прозрачная распределенность• Бесшовный деплой

воскресенье, 16 декабря 12 г.

Архитектура чат-сервера

• Соединения обслуживаются Cowboy

• Каждая сессия - отдельный процесс• Вспомогательные процессы для работы с БД, Redis

воскресенье, 16 декабря 12 г.

Начало знакомства

воскресенье, 16 декабря 12 г.

Одновременный ответ

воскресенье, 16 декабря 12 г.

Синхронная реализация

воскресенье, 16 декабря 12 г.

воскресенье, 16 декабря 12 г.

Очереди задач• Resque

• Быстро работает, использует Redis

• Удобный Web UI

• Redis полезен и для других задач:

• Хранение счетчиков• Синхронизация состояния• Кэширование

воскресенье, 16 декабря 12 г.

Уникальные задачи

• Сохранение сообщений• Прогрев кэша• Расчет статистики

Можно использовать Redis для блокировки

воскресенье, 16 декабря 12 г.

Обычный код

Реализация: resque-lock (<= 1.0.0)

def perform return unless redis.setnx("lock", true)

# do task actionsensure redis.del "lock"end

воскресенье, 16 декабря 12 г.

воскресенье, 16 декабря 12 г.

Правильный код

Реализация: resque-lock (>= 1.1.0)

http://redis.io/commands/setnx

def perform now = Time.now.to_i timeout = now + 60

unless redis.setnx("lock", timeout) # Lock is active return if now <= redis.get("lock").to_i # Lock is not expired return if now <= redis.getset("lock", timeout).to_i end

# do task actions 11

redis.del "lock"end

воскресенье, 16 декабря 12 г.

воскресенье, 16 декабря 12 г.

Хорошие индексыХорошие = Ускоряющие необходимые запросы

create_table 'messages' do |t| t.references 'source' t.references 'destination' t.string 'body' t.timestamp 'created_at' t.timestamp 'read_at'end

# History of messages received from given userSELECT * FROM messages WHERE destination_id = ? AND source_id = ?ORDER BY created_at DESC LIMIT 10

# Unread messages of userSELECT * FROM messages WHERE destination_id = ? AND read_at IS NULLORDER BY created_at DESC LIMIT 10

воскресенье, 16 декабря 12 г.

Плохие индексы# History of messages received from given useradd_index 'messages', ['source_id', 'destination_id']

# Unread messages of useradd_index 'messages', ['destination_id']

Limit -> Sort Sort Key: created_at Sort Method: top-N heapsort Memory: 25kB -> Index Scan using messages_between_users on messages Index Cond: ((source_id = ?) AND (destination_id = ?))Total runtime: 6.451 ms

Limit -> Sort Sort Key: created_at Sort Method: quicksort Memory: 26kB -> Bitmap Heap Scan on messages Recheck Cond: (destination_id = ?) Filter: (read_at IS NULL) -> Bitmap Index Scan on messages_unread Index Cond: (destination_id = ?)Total runtime: 123.983 ms

воскресенье, 16 декабря 12 г.

Отличные индексы!# History of messages received from given useradd_index 'messages', ['source_id', 'destination_id', 'created_at'], :order => { 'created_at' => 'desc' }

# Unread messages of useradd_index 'messages', ['destination_id', 'created_at'], :order => { 'created_at' => 'desc' },:where => 'read_at IS NULL'

Limit -> Index Scan using messages_between_users on messages Index Cond: ((source_id = ?) AND (destination_id = ?))Total runtime: 0.209 ms

Limit -> Index Scan using messages_unread on messages Index Cond: (destination_id = ?)Total runtime: 0.183 ms

воскресенье, 16 декабря 12 г.

Массивы и hstore

• Как сериализация, только лучше• Могут индексироваться

воскресенье, 16 декабря 12 г.

Размер таблицcreate_table 'users_usual' do |t| t.boolean 'flag1' ... t.boolean 'flag20'end

create_table 'users_hstore' do |t| t.hstore 'flags' # gem 'activerecord-postgres-hstore'end

create_table 'users_array' do |t| t.integer_array 'flags' # gem 'activerecord-postgres-array'end

5 000 000 записей, флаги независимы, P[flag=yes] = 0.01Usual table size: 249 MBHstore table size: 219 MBArray table size: 257 MB

воскресенье, 16 декабря 12 г.

ИндексированиеПоля:

Seq Scan on users_usual Filter: (flag2 AND flag7 AND flag13)Total runtime: 799.959 ms

Hstore:Bitmap Heap Scan on users_hstore Recheck Cond: (flags @> '2=>y, 7=>y, 13=>y'::hstore) -> Bitmap Index Scan on users_hstore_flags Index Cond: (flags @> '2=>y, 7=>y, 13=>y'::hstore)Total runtime: 350.778 ms

Массив:Bitmap Heap Scan on users_array Recheck Cond: (flags @> '{2,7,13}'::integer[]) -> Bitmap Index Scan on users_array_flags Index Cond: (flags @> '{2,7,13}'::integer[])Total runtime: 48.118 ms

воскресенье, 16 декабря 12 г.

Кэширование последовательностей

• Выбираем последовательность на несколько шагов вперед

• Кэшируем идентификаторы в Redisid = redis.lpop(cache_key) # Get next value from cache

unless id # No cached value ids = connection.select_values some_heavy_scope.select('id').to_sql id = ids.shift

redis.multi do |r| ids.each{ |id| r.rpush cache_key, id } r.expire cache_key, 7200 # Expire cache after 2 hours endend

воскресенье, 16 декабря 12 г.

воскресенье, 16 декабря 12 г.

Кэширование матрицы• Проблема• Различные фильтры: мин/макс возраст

(от 16 до 70) + пол

• 3080 возможных фильтров

• (1 + 2 + … + 55) * 2 = 55 * 56

• Решение: аппроксимация фильтров

воскресенье, 16 декабря 12 г.

Кэширование матрицы• Проблема• Различные фильтры: мин/макс возраст

(от 16 до 70) + пол

• 3080 возможных фильтров

• (1 + 2 + … + 55) * 2 = 55 * 56

• Решение: аппроксимация фильтров

воскресенье, 16 декабря 12 г.

Кэширование с аппроксимацией• Возраст округляется до кратного X (= 4)

• Минимальный - вниз, максимальный - вверх• 210 фильтров

• (1 + 2 + … + 14) * 2 = 14 * 15

• Кэшируется порция заведомо большего размера• После извлечения из кэша выкидываются лишние записи

воскресенье, 16 декабря 12 г.

Обработка фотографий

• 200–300 регистраций в минуту, половина грузит JPG на 5 мегабайт

• Первым делом уменьшайте размер входящих изображений

• CarrierWave лучше отделён от модели, чем Paperclip, обратно совместим

воскресенье, 16 декабря 12 г.

Обработка фотографий

• RMagick MiniMagick не хранит в себе временного файла, использует память отдельного процесса, не поддерживает создание изображений

• GraphicsMagick — форк ImageMagick, ориентированный на стабильность и производительность

• Прирост в скорости до 2-3 раз, но это не серебрянная пуля: меньше фич, иногда производительность страдает

воскресенье, 16 декабря 12 г.

Отправка СМС

• SMPP – открытый протокол, поддерживаемый большинством SMS-шлюзов

• Бинарный, за счет чего выше скорость передачи и footprint воркеров

• github.com/raykrueger/ruby-smpp – реализация для EventMachine

воскресенье, 16 декабря 12 г.

Тестирование

• Модульное• Ruby — RSpec

• Erlang — EUnit

• Интеграционное?• Нагрузочное?

воскресенье, 16 декабря 12 г.

RSpec для Rails и Erlang• Запуск Erlang при создании сессии

• Отдельный поток с EM, обслуживающий все соединения

• Socket.io поверх em-websocket-client

• Очередь входящих сообщенийs = open_session_with_chat

# Delegates to ActionDispatch::Integration::Sessions.post "/users/sign_in", email: '123@example.com', pass: '12345'

# Wait for a message (with timeout)s.receive(:connect).should be

# Send messages.send_event :contact_message, contact_id, text: "Hi!"

воскресенье, 16 декабря 12 г.

Боты-тестеры

• Нагрузочное тестирование чата• Определение проблем с concurrency

• Помощь при ручном тестировании

• Настраиваемое поведение

воскресенье, 16 декабря 12 г.

Реализация ботов

• Акторы на основе EventMachine

• Socket.io поверх em-websocket-client

• Набор "шаблонов поведения"class Caller < Wannafun::Actor behave :get_matrix behave :accept_calls behave :call_to_users behave :talk_in_callsend

EventMachine.run do Wannafun::ActorSet.new(Caller, options).start!end

воскресенье, 16 декабря 12 г.

Как ловить JS ошибки на клиенте

• В сложных приложениях — сложные сценарии и граничные случаи

• Обратная связь пользователь - разработчик. Максимум информации собирается автоматически

• Echoes.js (github.com/kossnocorp/echoes). На клиенте собираем логи, фильтруем важные и прикладываем к запросу

воскресенье, 16 декабря 12 г.

Echoes.jsecho.log('Test', 'logging', namespace: 'app.lol_module.45')echo.log(['trololo'])

[ { "timestamp": 1341468018606, "body": ["Test", "logging"], "namespace": "app.lol_module.45" }, { "timestamp": 1341468018606, "body": [["trololo"]], "namespace": "" } ]

echo.logs.grep 'some'#=> [{ body: ['Something'] }, { body: ['I want some LSD.']}]

воскресенье, 16 декабря 12 г.

Мониторинг приложения

• Длины очередей в Resque

• Кол-во несохраненных сообщений• Кол-во знакомств в разных состояниях• Длина очереди модерации

воскресенье, 16 декабря 12 г.

Head-huntung

Wannafun: red.scorpix@gmail.com

Evil Martians: surrender@evl.ms

воскресенье, 16 декабря 12 г.

Алексей Носков@alno

github.com/alnoalexey.noskov@evl.ms

Алексей Найден@alexnayden

github.com/anaydenalexey.nayden@evl.ms

ВПРСВ НТ?

ЗБС!

Все изображения являются собственностью их автороввоскресенье, 16 декабря 12 г.