Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Avatar for anayden anayden
December 16, 2012

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

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

Avatar for anayden

anayden

December 16, 2012
Tweet

More Decks by anayden

Other Decks in Technology

Transcript

  1. Wannafun.ru • Онлайн speed dating • Знакомство только с теми,

    кто в сети • Поток лиц aka «матрица» • 3 минуты чата для принятия решения • 48% / 52% воскресенье, 16 декабря 12 г.
  2. Проект в цифрах • 500 000 пользователей • 2000 онлайн

    • 2-3 события в секунду на юзера • Более 100 http-запросов в минуту на юзера • Более 5 000 000 чатов • Более 45 000 000 сообщений воскресенье, 16 декабря 12 г.
  3. Взаимодействие с браузером Pusher (http://pusher.com/) Push-канал к клиенту Нет серверной

    логики Faye (http://faye.jcoglan.com/) Pub/sub Нет серверной логики Socket.io (http://socket.io/) Абстракция над WebSocket, Flash и Polling Произвольная серверная логика воскресенье, 16 декабря 12 г.
  4. Серверная реализация socket.io • NodeJS • На тестах падала VM

    — epic fail • EventMachine • Не было актуальной версии • Erlang • Не было актуальной версии воскресенье, 16 декабря 12 г.
  5. Что хорошего в Erlang • Нет коллбеков - простой последовательный

    код • Нет разделяемого состояния, структуры данных неизменяемы - concurrency проще • Иерархия супервизоров - высокая устойчивость • Прозрачная распределенность • Бесшовный деплой воскресенье, 16 декабря 12 г.
  6. Архитектура чат-сервера • Соединения обслуживаются Cowboy • Каждая сессия -

    отдельный процесс • Вспомогательные процессы для работы с БД, Redis воскресенье, 16 декабря 12 г.
  7. Очереди задач • Resque • Быстро работает, использует Redis •

    Удобный Web UI • Redis полезен и для других задач: • Хранение счетчиков • Синхронизация состояния • Кэширование воскресенье, 16 декабря 12 г.
  8. Уникальные задачи • Сохранение сообщений • Прогрев кэша • Расчет

    статистики Можно использовать Redis для блокировки воскресенье, 16 декабря 12 г.
  9. Обычный код Реализация: resque-lock (<= 1.0.0) def perform return unless

    redis.setnx("lock", true) # do task actions ensure redis.del "lock" end воскресенье, 16 декабря 12 г.
  10. Правильный код Реализация: 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 г.
  11. Хорошие индексы Хорошие = Ускоряющие необходимые запросы 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 user SELECT * FROM messages WHERE destination_id = ? AND source_id = ? ORDER BY created_at DESC LIMIT 10 # Unread messages of user SELECT * FROM messages WHERE destination_id = ? AND read_at IS NULL ORDER BY created_at DESC LIMIT 10 воскресенье, 16 декабря 12 г.
  12. Плохие индексы # History of messages received from given user

    add_index 'messages', ['source_id', 'destination_id'] # Unread messages of user add_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 г.
  13. Отличные индексы! # History of messages received from given user

    add_index 'messages', ['source_id', 'destination_id', 'created_at'], :order => { 'created_at' => 'desc' } # Unread messages of user add_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 г.
  14. Массивы и hstore • Как сериализация, только лучше • Могут

    индексироваться воскресенье, 16 декабря 12 г.
  15. Размер таблиц 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.01 Usual table size: 249 MB Hstore table size: 219 MB Array table size: 257 MB воскресенье, 16 декабря 12 г.
  16. Индексирование Поля: 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 г.
  17. Кэширование последовательностей • Выбираем последовательность на несколько шагов вперед •

    Кэшируем идентификаторы в Redis id = 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 end end воскресенье, 16 декабря 12 г.
  18. Кэширование матрицы • Проблема • Различные фильтры: мин/макс возраст (от

    16 до 70) + пол • 3080 возможных фильтров • (1 + 2 + … + 55) * 2 = 55 * 56 • Решение: аппроксимация фильтров воскресенье, 16 декабря 12 г.
  19. Кэширование матрицы • Проблема • Различные фильтры: мин/макс возраст (от

    16 до 70) + пол • 3080 возможных фильтров • (1 + 2 + … + 55) * 2 = 55 * 56 • Решение: аппроксимация фильтров воскресенье, 16 декабря 12 г.
  20. Кэширование с аппроксимацией • Возраст округляется до кратного X (=

    4) • Минимальный - вниз, максимальный - вверх • 210 фильтров • (1 + 2 + … + 14) * 2 = 14 * 15 • Кэшируется порция заведомо большего размера • После извлечения из кэша выкидываются лишние записи воскресенье, 16 декабря 12 г.
  21. Обработка фотографий • 200–300 регистраций в минуту, половина грузит JPG

    на 5 мегабайт • Первым делом уменьшайте размер входящих изображений • CarrierWave лучше отделён от модели, чем Paperclip, обратно совместим воскресенье, 16 декабря 12 г.
  22. Обработка фотографий • RMagick MiniMagick не хранит в себе временного

    файла, использует память отдельного процесса, не поддерживает создание изображений • GraphicsMagick — форк ImageMagick, ориентированный на стабильность и производительность • Прирост в скорости до 2-3 раз, но это не серебрянная пуля: меньше фич, иногда производительность страдает воскресенье, 16 декабря 12 г.
  23. Отправка СМС • SMPP – открытый протокол, поддерживаемый большинством SMS-шлюзов

    • Бинарный, за счет чего выше скорость передачи и footprint воркеров • github.com/raykrueger/ruby-smpp – реализация для EventMachine воскресенье, 16 декабря 12 г.
  24. Тестирование • Модульное • Ruby — RSpec • Erlang —

    EUnit • Интеграционное? • Нагрузочное? воскресенье, 16 декабря 12 г.
  25. RSpec для Rails и Erlang • Запуск Erlang при создании

    сессии • Отдельный поток с EM, обслуживающий все соединения • Socket.io поверх em-websocket-client • Очередь входящих сообщений s = open_session_with_chat # Delegates to ActionDispatch::Integration::Session s.post "/users/sign_in", email: '[email protected]', pass: '12345' # Wait for a message (with timeout) s.receive(:connect).should be # Send message s.send_event :contact_message, contact_id, text: "Hi!" воскресенье, 16 декабря 12 г.
  26. Боты-тестеры • Нагрузочное тестирование чата • Определение проблем с concurrency

    • Помощь при ручном тестировании • Настраиваемое поведение воскресенье, 16 декабря 12 г.
  27. Реализация ботов • Акторы на основе EventMachine • Socket.io поверх

    em-websocket-client • Набор "шаблонов поведения" class Caller < Wannafun::Actor behave :get_matrix behave :accept_calls behave :call_to_users behave :talk_in_calls end EventMachine.run do Wannafun::ActorSet.new(Caller, options).start! end воскресенье, 16 декабря 12 г.
  28. Как ловить JS ошибки на клиенте • В сложных приложениях

    — сложные сценарии и граничные случаи • Обратная связь пользователь - разработчик. Максимум информации собирается автоматически • Echoes.js (github.com/kossnocorp/echoes). На клиенте собираем логи, фильтруем важные и прикладываем к запросу воскресенье, 16 декабря 12 г.
  29. Echoes.js echo.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 г.
  30. Мониторинг приложения • Длины очередей в Resque • Кол-во несохраненных

    сообщений • Кол-во знакомств в разных состояниях • Длина очереди модерации воскресенье, 16 декабря 12 г.
  31. Алексей Носков @alno github.com/alno [email protected] Алексей Найден @alexnayden github.com/anayden [email protected]

    ВПРСВ НТ? ЗБС! Все изображения являются собственностью их авторов воскресенье, 16 декабря 12 г.