Ламер с 20 летнем стажем
0,0
рейтинг
10 мая 2013 в 02:49

Разработка → Из говнокода в Highload. Используем ТАРАНtool. 5 рецептов повышения производительности

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

Код проекта… В общем у меня осталось впечатление, что писал его недоученный студент… И это, немотря на то, что уже был сделан частичный рефакторинг другим программистом. Единственное, что радовало, то это то, что не использовался какой-либо фреймворк. Конечно, это вечно флеймовый вопрос: Иисус или Магомед? Быть или не Быть? Unix или Windows? Использовать или не Использовать? ИМХО, Моё мнение: фреймворки заточены под узкий круг типовых задач. Социальный проект — задача, как правило, не типовая… Но, в целом, мне проект показался интересным и я решил взяться за улучшение. На этом вступление можно закончить…

Наверно, про повышение производительности и тему highload не писал только ленивый WEB разработчик, знающий хоть что-то в этой области. Принципиально, что-то нового, в данной статье вы не найдёте. Основные идеи разработки highload проектов, были мною изложены в цикле статей HighLoad. Три кита.. Если вам интересно, как я увеличил производительность PHP проекта, используя NoSQL хранилище tarantool, то Добро пожаловать под кат.

Хотя, принципиально можно использовать другое, подходящее под данный круг задач, key/value хранилище, и реализация серверной логики может быть на любом другом скриптовом языке.

Рецепт 1. Анализируем код

Всё до ужаса банально. Про это писали сотни раз до меня, и будет написано еще сотни статей… Однако, «мы самые умные» и упорно наступаем на одни и теже грабли. Я не открою Америки, если скажу, что самое узкое место в 99% всех WEB проектов — это БД. А какой из этого следует сделать вывод?
Правильно, — необходимо минимизировать количество запросов.. А как это сделать, если по ходу логики пять раз встречается код:

$user = new User();
$userData = $user->getById($uid);

При профилировании запросов вылезает, что мы выполнили пять одинаковых «селектов»: SELECT * FROM users WHERE id=$uid;
А реализуется это довольно просто: используем внутреннee (private) статическое поле или свойство объекта User:
    class User {
 
        private static $userData = NULL;
 
        private function getById_loc($uid) {
        // некоторый код обращения к БД. 
        }
 
        public function getById($uid) {
           if (self::$userData) return self::$userData;
           self::$userData = $this->getById_loc($uid); 
           return self::$userData;
        }
    }


Второе, что сразу бросается в глаза. это когда рядом стоят два метода:
$User->updateBalance($sum);
$User->updateRating($rating);
 

что приводит к выполнению двух запросов к одной таблице подряд, один за другим:
UPDATE users  SET balance = balance + $sum WHERE id = $uid;
UPDATE users  SET rating = $rating  WHERE id = $uid;
хотя, если чуть-чуть пошевелить мозгами, то мы вполне могли бы сформировать один запрос:
UPDATE users SET 
  balance = balance + $sum,
  rating = $rating  
WHERE id = $uid;
 

Это можно сделать двумя способами: либо мы пишем еще один метод $user->updateBalanceAndRating($sum, $rating), либо мы реализуем нечто универсальное, как говорится, на все случае жизни:
$user->updateFieldAdd('balance', $sum);        // запоминаем поле, операцию add - сложение, операнд
$user->updateFieldAssign('rating', $rating);   // запоминаем поле, операцию assign - присваивание, операнд
$user->execUpdate();                           // формируем и выполняемзапрос 


Только внедрение этих двух простых методов позволило уменьшить количество запросов к БД с 10-12 до 3-5. Но если честно, существующий код предстоит еще рефакторить и рефакторить. Конечно, большинство Хаброюзеров далеко не лузеры, но профилирование и анализ всегда «Бог нам в помощь».

Рецепт 2. Кеширование

Что такое Кеширование, надеюсь разъеснять не нужно. Как писал в своей статье про разработку highload проекта, Дима Котеров: «надо кешировать всё, что можно». В данном проекте, были робкие попытки какого-то кеширования. Однако, все вышло по Черномырдину: хотели как лучше, а закешировали, не то, что наиболее часто используется ;).

Как было отмеченоно выше, чтоб снизить нагрузку на БД, надо снизить количество запросов. А как их снизить? Да очень просто — часть неизменных данных (справочники по юнитам, трибутам и оружию ) просто вынести из БД, например в конфиги. Конфиги могут быть либо в XML, и правится девочками из команды геймплея в любом XML редакторе, либо в уже готовом виде: в РНР массиве — если разработчик геймплея и кода — один человек. ПарсингXML на лету — вещь тяжелая, по этому я делаю предварительное XSLT преобразование непосредственно в PHP код (в ввиде ассоциативного массива, который и грузится вместе с основным кодом). Однако, после каждого изменения в XML конфиг-файле, необходимо запустить скрипт XSLT- преобразования или консольную утилиту. Да, это не кеширование, это небольшое улучшение, и его не стоит выделять в отдельный рецепт, но и забывать про него не стоит.

Таким образом, запихнув все справочники в конфиги, мы освобождаемся еще от пары-тройки запросов. Ну, что — стало легче?.. По крайней мере, после применения рецептов 1 и 2 база перестала падать. Ну хоть какой-то результат…

Рецепт 3. Анализ данных

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

В нашем проекте, наиболее часто запрашиваемой информацией был игровой профиль пользователя, подарки и награды. Все эти данные были помещены в NoSQL хранилище, а все прочие данные, особенно связанные с платежами, остались в MySQL. В качестве NoSQL хранилища был выбран tarantool.

А все же — почему ТАРАНtool ?

На Конференции Highload++ 2011 Руководителя разработки Tarantool Костю Осипова спросили:
— А почему у Вас название такое ядовитое?
— Ну, можете рассматривать название, как таран и tools, т.е. как средство (тулзу) тарана для ваших проектов.

Итак, факторами, влияющими на выбор NoSQL хранилища были:
— Моё личное знакомство с тимлидом проекта Костей Осиповым, который обещал поддержку и консультацию
— Опыт внедрения данного хранилища в предыдущем проекте. К сожалению проект не взлетел :(, но было интересно.
— Изучение новых возможностей tarantool, прошло почти два года с момента его предыдущего использования
— Высокая производительность данного NoSQL хранилища и высокая доступность данных.
— Персистентность данных, при падении на диске остается актуальная копия, которую всегда можно поднять.
— ну, и если быть не очень скромным, то сам я являюсь автором первой версии PHP расширения для Tarantool, так что при необходимости смогу что-то пропатчить или исправить багу.

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

Анализ данных (продолжение)

Рассмотрим профиль пользователя, таблица users. В ней есть изменяемые и не изменяемые данные. К изменяемым данным относится: баланс, рейтинг, pvp-очки, юниты, шаги туториала и пр.
К не изменяемым данным относятся social_id, логин, url аватара, личные коды и пр… Среди изменяемых данных есть часто меняемые и редко меняемые. Однако, не изменяемые данные могут запрашиваться часто.

Выделяем часто запрашиваемые данные. Именно их мы и будем кешировать в tarantool. Теперь немного про само NoSQL хранилище…

ТАРАНtool. Краткий обзор

Tarantool — это, как уже было выше сказано, высокопроизводительное key/value NoSQL хранилище. Все данные находятся в оперативной памяти, и представлены в виде кортежей, по этому их извлечение по скорости не уступает redis или чуть медленнее (на 6-7 милисекунд на 1000 операций) memcached.

И все-таки, отметим, что Tarantool — это хранилище данных, а не система кеширования в памяти, типа memcache. Все данные находятся в оперативной памяти, но постоянно сохраняются (синкуются от системного вызова sync) в файлы (снапах 0000..01.snap ). В отличие от традиционного memcached & redis, tarantool имеет дополнительные возможности:
— возможноссть наложения вторичных индексов на данные
— индексы могут быть составными
— индексы могут иметь тип HASH, TREE или BITSET. Планируется внедрение GEO индекса.
— осуществление выборок по одному или нескольким индексам одновременно.
— изъятие данных частями (аналог LIMIT/OFFSET).

Tarantool оперирует данными, которые объединены в пространства (space). Пространство — это аналог таблицы в MySQL. В tarantool используется цифровая нумерация пространств (0, 1, 2, 3 ...). В обозримом будущем планируется использовать именные пространства (аналог имен таблиц в MySQL.).

На каждое пространство может накладываеться индекс. Индексы могут накладываться, как на числовое (int32 или int64), так и на символьное поле. Так же, как и с пространствами, в tarantool определена цифровая нумерация индексов.

Обмен между клиентом и сервером происходит кортежами. Кортеж- это аналог строки в таблице MySQL. В математике понятие кортеж — это упорядоченный конечный набор длины n. Каждый элемент кортежа, представляет собой элемент данных или поле. Принципиально, тарантул не различает типы данных полей. Для него — это набор байт. Но если мы используем индекс, т.е. накладываем индекс на это поле, то его тип должен соответствовать типу поля. Существует еще другое наименование кортежа: тапл (tuple).

Все индексы прописываются в конфиге, который человеко- восприним: YAML. Пример части конфига:
space[0].enabled = 1 
space[0].index[0].type = "HASH"
space[0].index[0].unique = 1
space[0].index[0].key_field[0].fieldno = 0
space[0].index[0].key_field[0].type = "NUM"
В конфиге описываем первичный и вторичные индексы для каждого пространства. Если мы делаем выборку только по PRIMARY KEY, то вполне достаточно описания только первичного индекса (см. пример выше). Если мы хотим среди наших пользователей выбрать лучших по рейтингу или pvp-боям, то на эти поля накладываем вторичный индекс. Пусть индексируется второе поле (fieldno = 1, отсчет от ноля) int32_t — рейтинг:

space[0].index[1].type = "TREE"                 // делает тип TREE, что позволяет делать выборки на операции больше и меньше
space[0].index[1].unique = 0                       // убираем иникальность
space[0].index[1].key_field[0].fieldno = 1   // указываем номер индексируемого поля
space[0].index[1].key_field[0].type = "NUM" // тип int32_t

Так как у нас проект Социальной игры, то первичный ключ будет соответствовать social_id. Для большинства соцсетей — это 64-ключ. Тип индекса будет HASH, а тип данных STR. В идеале, хотелось бы NUM64, но к сожалению, PHP плохо работает с типом long long. Драйвер не распознает тип и размер первичного ключа используемого пространства. В настоящий момент, если использовать 64-х битный ключ, пока по нему нельзя искать используя 32хбитное значение. Его надо запаковать в пакет как 64-битный ключ. Сейчас драйвер это делает только если значение превосходит 32-хбитный диапазон. По этому, надежнее работать с типом STRING.

Расчет памяти

Необходимо помнить, что tarantool — решение memory only, по этому важно рассчитать предполагаемый объем занимаемой оперативной памяти. Расчет производится слудующим образом:

Перед каждым кортежом будет храниться переменная типа varint (аналог perl'ового 'w' в pack) и 12 байт из заголовка на каждую tuple. Конкретно, про за данные, можно ознакомиться, изучив протокол или прочитав статью Tarantool Данные и Протокол.

Дополнительно около 15 процентов занимает данные для аллокаторов. Если мы, например, имеем 10 полей и размер данных пользователя укладываются в 256 байт, то для 1.5M приблизительно будет следующий расчет:
( 10 * 1 + 256 + 12 ) * 1.15 * 1 500 000 = 921150000 ~= 440 Мб на днные

Так же, все индексы находятся в памяти, которая занимает:
— для одной ноды в дереве хранит 68 байт служебной информации
— для одной ноды в хеше хранит 56 байт служебной информации

Для хранения индекса на 1.5M пользователей достаточно чуть более 80Mb, итого вместе, для хранения 1,5 M профилей потребуется чуть более половины гигабайта. Если мы добавим еще один ключ (тип TREE), то это еще дополнительно 90М оперативной памяти.

Кому как, но по нынешним меркам — не совсем уж много.

Рецепт 4. Избавляемся от MySQL в foreground

Как мы уже говорили, перенеся данные пользовательского профиля в tarantool, мы хотим иметь их актуальные копии в MySQL. По этому, все операции, связанные с UPDATE, приходится выполнять. В результате, сделав кеширование, мы достигли не много. Но, основного эффекта все же достигли: MySQL перестал падать. Так, как же всё-таки ускорить работу скрипта в несколько раз? Это возможно, если избавиться от MySQL запросов вообще. А как это возможно? Нужно передать информацию об изменении в БД в какой-то другой, бэграунд скрипт, который будет осуществлять операции INSERT/UPDATE.

Данная информация передаётся через очередь. Есть несколько промышленных решений, которые позволяют запускать удаленные задачи: Gaerman, Celery, а так же можно приспособить RabbitMQ, ZMQ, redis и прочие сервера очередей. Но зачем вводить в проект какую-то новую сущность, если в качестве сервера очередей можно использовать tarantool.

В тарантул есть реализация очереди github.com/mailru/tntlua/blob/master/queue.lua и рекомендую её к использованию.
Однако, было реализовано чуть-чуть проще. Создаем новое пространство:

space[1].enabled = 1 
space[1].index[0].type = "TREE"
space[1].index[0].unique = 1
space[1].index[0].key_field[0].fieldno = 0
space[1].index[0].key_field[0].type = "NUM"
 

В данное пространство будем писать следующие поля:
— id, автоинкрементное поле. Должно быть проиндексировано. Накладывается первичный индекс типа TREE.
— type — тип операции, некоторая числовая константа, по которой определяется номер шаблона SQL оператора.
— data — некоторые данные для вставки / обновления.

В foreground скрипте будет следующий код:
define( 'UPDATE_SPIN_COUNT',   1);
define( 'UPDATE_USER_BALANCE'2);

$res = $tnt->call( 'box.auto_increment' , array( (string)TBL_QUEUESUPDATE_SPIN_COUNT, $spinCount, $uid ));
Хранимая процедура box.auto_increment является встроенной, она вставляет данные tuple значение первичного ключа — max + 1. Параметры:
— номер пространства, куда будет осуществлена вставка данных
— сами данные
— необязательный параметр флаг, по умолчанию выставлен «возвращать новый ключ»
Необходимо отметить, что тип переменной, номер пространства (константа TBL_QUEUES) должен быть приведен к типу STRING. Данный скрипт, вызывает lua процедуру, которая записывает данные в FIFO очередь ( автоинкрементный номер, тип выполняемой задачи и сами данные).

Далее, background скрипт, который может выполняться даже на другой удаленной машине, вынимает из очереди данные и выполняет SQL:

define( 'UPDATE_SPIN_COUNT',   1);
define( 'UPDATE_USER_BALANCE'2);

$res = $this->callProc('my_pop', array((string)TBL_QUEUES));
 
/*
если пусто то вернет: 
array(2) {
  ["count"]=>  int(0)
  ["tuples_list"]=>  array(0) {}
}
 
*/
if (!$res['count']) return;
 
$tuple = $res['tuples_list'][0];
switch ($tuple[1]) {
case UPDATE_SPIN_COUNT:
$sql = "UPDATE users SET spinCount ={$tuple[2]}  WHERE uid ={$tuple[3]}";
break;
 
case UPDATE_USER_BALANCE:
$sql = "UPDATE users SET money = money + {$tuple[2]}  WHERE uid ={$tuple[3]}";
break;
 
default:
throw new Exception ('unknow task type');
break;
}
 
$this->execSQL($sql);


В результате, наш фронт скрипт работает только с быстрым тарантулом, а бэдграунд-скрипт, висит в качестве демона или запускается по крону и сохраняет данные в MySQL на отдельном сервере, не тратя ресурсов WEB сервера. В результате, можно выиграть в производительности более 30%. Тема бэкграундовских скриптов достойна отдельной статьи.

Однако, это не всё. Чтоб запустить lua процедуру my_pop, её надо проинициализировать. Для этого, нижеследующий код необходимо поместить в файл init.lua, который должен находиться в work_dir или script_dir.

function my_pop(spaceno)
    spaceno = tonumber(spaceno)
    local min_tuple = box.space[spaceno].index[0].idx:min()
    local min = 0
    if min_tuple ~= nil then
        min = box.unpack('i', min_tuple[0])
    else
        return 
    end
 
    local ret = box.select(spaceno, 0,min)
    box.delete(spaceno, min)
 
    return ret
 
end


Значение work_dir указывается в tarantool.conf.

Рецепт 5. Кеширование только тех профилей, кто активно играет

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

А что, если наш проект набрал не 1.5 млн, а три или 5 млн пользователей? Пользователь зашел, игра не понравилась — ушел. А данные в тарантул остались, занимают память и не используются… По этому, для более эффективного использования, да и просто для более быстрого извлечения данных — имеет смысл хранить только тех пользователей, которые постоянно играют.

Иными словами, тех пользователей, которые не играют, т.е. не заходили в игру, например более недели, можно удалить из оперативного кеша. Так как у нас есть актуальная копия в БД, то мы всегда её можем восстановить в оперативном кеше. Код классов, использующих оперативный кеш построен по стандартному типу кеширования:

class User extends DbModel {
 
  public function getByUid($uid) {
 
        $result = this->getFromCache($uid);
 
        if (!is_null($result)) {
            return $result;
        }
 
        $result = $this->execSQL( "SELECT * FROM users WHERE uid=$uid");
        $this->setToCache($result);
 
        return $result;
  }
....
}


Чистку можно выполнить несколькими способами:
— выбрать крон скриптом список всех «просроченных» записей из БД и удалить их в тарантуле
— настроить в тарантуле брокера чистки (сам этого не делал) github.com/mailru/tarantool/wiki/Brokers-ru
— написать хранимую процедуру на lua по удалению всех «просроченных» записей и запускать ее вызов по крону.

С нетерпением ждем от группы разработчиков нового типа хранилища, чтоб не все данные (кортежи) поднимались с диска в оперативную память, а только наиболее востребованные. Приблизительно, как в Монго ДБ. Тогда рецепт 5 отпадает сам собой.

Вместо заключения

Всё вышеописанное можно реализовать на любом из языков, на котором реализован ваш социальный проект (PHP, Perl, Python, Ruby, Java).
В качестве NoSQL хранилища данных оперативного кеша может подойти любое key/value хранилище из следующего многообразия:
— memcached, отсутствует персистентность и придется немного поломать голову над реализацией очередей, но это можно решить исполдьзуя операцию APPEND
— membase, не очень удачная разработка и вроде как уже перестал поддерживаться своими создателями
— связка memcacheDb & memcacheQ
— редис, принципиально есть все, чтоб реализовать данный функционал
— TokyoTyrant, KyotoTyrant — принципиально очереди можно реализовать на lua процедурах
— LevelDb Daemon, достойная разработка ребят из мамбы. Небольшой допил и очереди у вас уже в кармане.
— предлагаем в комментах что-то своё

Ну и в заключении немного о грушах или чуть-чуть пиара.
Про демоны, а конкретно их возможные задачи, взаимодействие и тонкости их реализации, я планирую рассказать на DevConf 2013 "РНР-демоны в социальных играх". А так же освещу некоторые фичи, которые мне позволили поднять производительность реализованных проектов. Если интересно, то затрону тему тарантула (для этого я воспользовался голосованием) Так-что, до встречи наDevConf 2013.

PS. уже третий час ночи, возможны ачапотки… плиииз в приват — сразу поправлю. Заранее благодарен.
Данная тема актуальна?

Проголосовало 316 человек. Воздержался 81 человек.

собираюсь ли я использовать тарантул в свои проектах

Проголосовало 323 человека. Воздержалось 95 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Александр Календарев @akalend
карма
71,5
рейтинг 0,0
Ламер с 20 летнем стажем
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (52)

  • –7
    Классная статья, спасибо!
  • +6
    Рекомендую поглядеть на MongoDB. Она не так сильно упирается в размер доступной RAM и имеет некоторые другие полезности по сравнению с традиционными key-value хранилищами. Взять хотя бы aggregation framework или caped collections с ttl (это ваш кэш в виде одной опции).

    Не стоит так сильно акцентировать внимание в статье на чужом говнокоде, так как ваш потом окажется в точности таком же положении.
    • +2
      на счет говнокода — ты конечно прав. Я сам смотрю на свой код, написанный пару лет назад и ужасаюсь… Но, в статье больше про архитектуру кода, а не его запахи.

      Что касается MongoDb — есть опыт внедрения пары проектов. Очень требовательна к ресурсам и много жрет дискового пространства. На виртуальной машине в РАМ 2Гб — на 1М профилях начинает тупить. Для определенного круга задач — идеальное решение. В данном случае — она (монга) значительно проигрывает по скорости.
      • –1
        Расскажите, как мерили разницу по скорости Тарантул/МонгоДБ и каков порядок у «значительно»?

        Не понятно, что значит «требовательна к ресурсам»?
        Запускать такую бд на виртуалке с 1Г памяти это все-равно, что на телефоне.

        Опять же странно читать про «жрет дисковое пространство» в сравнении с «memory only database».

        Монга по умолчанию при старте большие файлики создает, но это как бы не проблема, вот названию ключей не надо сильно раскидистые делать — это да.
        • +1
          Расскажите, как мерили разницу по скорости Тарантул/МонгоДБ и каков порядок у «значительно»?
          мерил Си клиентом, засылал 100 000 запросов на несколько потоков (от 8- 16)
          значительно, это на 30-40%

          Запускать такую бд на виртуалке с 1Г памяти это все-равно, что на телефоне.
          Это верно, и пытались это вразумить инвестору, что нужен выделенный сервер…

          Монга по умолчанию при старте большие файлики создает, но это как бы не проблема,
          да там вообще облако, так что настройка монги — это вообще проблема хостера.
          • НЛО прилетело и опубликовало эту надпись здесь
        • +3
          С годик полтора назад тестировали производительность различных key/value в том числе и монго. Оказался самым медленным.
          У нас получилось
          mysql+handlersocket
          memcahed+mysql (с различными костыликами)
          mysql in memory db
          redis
          mongo

          handlersocket > 600Kqps выше не хватило мощности клиентов
          на memcahe получили порядка 450Kqps
          redis/mongo на порядки медленее были (меньше 100Kqps. с лагами и падениями. при том, что за mongo были девелоперы и они отчаяно пытались её натюнить)

          • +3
            конечно все зависит от локального железа и настроек, но по моим тестам редис был быстрее чем mysql+hs
            тут вообще еще очень многое зависит от методике измерения:
            — в одном ли коннекте на поток, или на каждый запрос новый коннект,
            — во сколько потоков шли запросы
            — делали инсерты или уже изменяли зааллоцированные области.
            — первоночальный размер карзины хештейбла или таблицы
            — Каким клиентом мерили. Желательно, чтоб это был родной сишный клиент.

            Но в целом корреляция существует…
            • 0
              Сервер с базой
              2 или 3 web (php, python, perl) как fcgi с разными клиентскими либами
              siege или ab

              На каждый клиентский запрос надо было получить от 100 до 500 значений из хранилища.
              Только чтение. Запись в один поток и только инсерты (достаточно медленые)
              Все данные по которым делается селект помещаются в память.

              Для нас было актуально такое тестирование.

              Нагрузка спецефичная, получилось так. Пока ещё не жалеем о выборе
              • +1
                Меня интересовала производительность самого хранилища, по этому делалось 10К рандомных запросов (GET/SET) по num PK ключу в n потоков( каждый поток 10K / n ) сишными клиентами, писался специальныйтестировочный софт. Что бы по минимуму исклюить сетевое взаимодействие, то все происходило в одном коннекте. Ели на каждый запрос делать свой коннект, то общее время увеличится на 20-30%.
                Все тестирование происходило на локалхосте, что не совсем правильно.
                Пример тулзы для memcached протокола github.com/akalend/mcstrass
                аналогичные тулзы писались и для redis, для HandlerSocket и для mongo…
  • +14
    От названия статьи аж мороз по коже. Все известные мне говнокодеры мнят себя хайлоадерами.
    • 0
      что верно — то верно
      • +3
        ну, спасибо за минус… и меня к говнокедерам причислили :)
        • +2
          Не я ))

          Мой комментарий был направлен на название статьи.

          Говнокодинг — это уже целая культура. Списать просто на глупость людей, судя по их количеству — нельзя. И как-то заметил, что у большинства из них есть общая черта. Они не стремятся писать чистый код. Они стремятся оптимизировать, причем заранее. Причем всегда. Мозги у людей так повернуты, что они бесконечно думают только о том, чтобы код «летал». Т.е. смещена картина мира — они говнокодом называют то, что не быстро работает. Даже если не требуется. А то, что их код — говно, они не замечают совершенно.

          В итоге, их код не читается, нельзя менять. И еще в добавок и не летает, а ползает, как недобитое животное. Предварительная оптимизация — такая предварительная.

          У Вас вроде всё верно — написали на реляционной базе — работает, деньги приносит. А потом, уже когда код работал, дали оптимизировать.
          • +2
            а я оценил твой юмор плюсом :)
            У Вас вроде всё верно — написали на реляционной базе — работает, деньги приносит. А потом, уже когда код работал, дали оптимизировать.

            писал код не я, довел до прибыли не я, так что это лавры хозяина проекта…
            моя задача — чтобы все это взлетело, когда стало тормозить… и мои изменения были минимальны, просто другой программист стал делать рефакторинг под моим руководством
          • +1
            что-то в коде менял я… но общий рефакторинг сейчас производит другой програмист. Мной лишь привнесены эти 5-ть рецептов.
  • –1
    Да, да, давайте дальше кричать «говнокодеры», «говнокодеры» и писать статьи с советами а-ля первый урок php (извините, дальше части про php не смог читать), от этого карма (не хабровская) должна обязательно улучшиться!
    • +2
      чтоб не писать статьи а-ля первый урок php, я специально завел свой блог. Многих переубедить что и как делать в определенных условиях практически не возможно. А бороться с ветряными мельницами — нет времени
  • +3
    фреймворки заточены под узкий круг типовых задач

    Фреймворк (англ. framework — каркас, структура) — структура программной системы; программное обеспечение, облегчающее разработку и объединение разных компонентов большого программного проекта. Употребляется также слово «каркас», а некоторые авторы используют его в качестве основного, в том числе не базируясь вообще на англоязычном аналоге. Можно также говорить о каркасном подходе как о подходе к построению программ, где любая конфигурация программы строится из двух частей: первая, постоянная часть — каркас, не меняющийся от конфигурации к конфигурации и несущий в себе гнезда, в которых размещается вторая, переменная часть — сменные модули (или точки расширения).


    Я тут хотел начать кому-то что-то обьяснять, но перехотел. Пустое это.
    • +2
      это верно, иначе бы Баду, Мамба и Контакт были бы реализованы на симфони или джомула.
      • +3
        Нужно было просто уточнить, что речь о публичных фреймворках. Ибо любой нетривиальный проект содержит в себе фреймворк облегчающий команде его поддержку и/или модификацию.
        • +2
          Леша, согласен с тобой — полностью.
          У каждого программиста, проработавшее определенное кол-во времени над определенным кругом задач появляется свой наработанный инструментарий в широком смысле этого слова, который принципиально можно назвать фреймворком. У меня есть собственный фреймворк, который я использую в большинстве задач. По этому, в тексте статьи и было написано, что это вечно флеймовый вопрос…: Быть или не Быть?

          Конечно, было быпросто замечательно, если бы группа интузиастов выпустила фреймворк, заточеный специально под соцсети, нагрузку и бинарным обменом с флешью, а не адаптировало бы ZF или симфони
        • +2
          да, еще вспомнил случай реализации одного проекта. Взял свой фреймворк, для реализации социгры, далее решили прикрутить PHP-AMF, прикрутили, оказалось 75% функфиональности в корзину… потом отказались от AMF и заменили на протобуф, ну и вообще 90 процентов кода в никуда…
          так что решаем спец задачи
        • +1
          ну и чем поможет какой-то фреймворк, общий или свой… если задача специфична…
  • +4
    MySQL пробовали настроить перед тем, как NoSQL запихивать? Какой там объём данных и рейт запросов хоть?
    • +2
      хз… это облако… так что для нас, смертных — это черный ящик
  • 0
    Ваш код
    $tuple = $res['tuples_list'][0];
    switch ($tuple[1]) {
    case UPDATE_SPIN_COUNT:
    $sql = "UPDATE users SET spinCount ={$tuple[2]}  WHERE uid ={$tuple[3]}";
    break;
     
    case 2:
    $sql = "UPDATE users SET money = money + {$tuple[2]}  WHERE uid ={$tuple[3]}";
    break;
     
    default:
    throw new Exception ('unknow task type');
    break;
    }
    


    Почему не так:
    switch ($res['tuples_list'][0][1]) {
        case UPDATE_SPIN_COUNT:
        case 2:
            $sql = "UPDATE users SET spinCount ={$tuple[2]}  WHERE uid ={$tuple[3]}";
            break;
        default:
            throw new Exception ('unknow task type');
            break;
    }
    

    Впрочем и так и так следующий разработчик посмотрев на этот код запросто сможет написать подобную статью, удивляясь откуда и почему берутся магические цифры в этом коде
    • +1
      потому-что везде в статье псевдокод, для наглядного понимания, реального кода реально больше (больше проверок, больше методов, больше диапозон выбора данных)
      ну а если по логике, то на UPDATE_SPIN_COUNT (=1) формируем один запрос, а на UPDATE_USER_BALANCE (=2) выполняем другой,
      а судя вашей логике, на 1 и 2 я выполняю один и тот же запрос
      • +1
        Да, но у вас 2 — магическое число. Как и индексы у $res
        • 0
          очепятка, просил же дать инфу в приват, писал уже ночью
          сейчас магик моменто исправлю
  • +1
    Про тарантул действительно интересно, спасибо.
    Но за такие отрывки кода нужно наказывать:

    Скрытый текст
    class User extends DbModel {
     
      public function getByUid($uid) {
     
            $result = this->getFromCache($uid);
     
            if (!is_null($result)) {
                return $result;
            }
     
            $result = $this->execSQL( "SELECT * FRAO users WHERE uid=$uid"); // Тут SQL Injection
            $this->setToCache($result);
     
            return $result;
      }
    ....
    }
    


    И да, фреймворк бы здесь не помешал. К слову, в Yii это выглядело бы примерно так:

    $user = User::model()->cache(1000)->findByPk(111);
    
    • –2
      >$this->execSQL( «SELECT * FRAO users WHERE uid=$uid»); // Тут SQL Injection
      принципиально ты прав, и молодец что заменил… Думаю, другие это оценят.
      про псевдокод — мой коммент выше, а что касается $uid — то проверка на SQL Injection осуществлена еще в контроллере, а не в модели.
      в данном случае это было приведение к инту и проверка на ноль. Ну и скажи — какой смысл пускать ноль в модель если можно дать отлуп на более высоком уровне?
      И кто тебе сказал, что клиент и сервер общаются по HTTP? Может клиент и сервер общаются по AMF, protobuf или иному бинарному протоколу?
      кстати, если вы разрабатываете социальные игры — то советую присмотреться к протобуфу
      • +1
        Ну и скажи — какой смысл пускать ноль в модель если можно дать отлуп на более высоком уровне?


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

        Всё же перед тем как творить что-то своё стоит обратить свое внимание на реализацию тех или иных вещей в современных открытых системах. Хоть в том же yii или Kohana, Меньше лапши в голове будет.
        • +1
          Очевидно у нас разные взгляды на архитектуру и понятия… Это не типовой WEB проект, точка входа одна, парсинга урла нет, один контролер который работает с моделями…
          • +1
            Расскажите про вашу архитектуру с одной точкой входа. Чем это обусловлено, неужели у Flash игр такая специфика? Или у вас что-то вроде постоянного соединения клиента с сервером?
            Я писал много разных API.
            • +2
              В данном проекте нет, так как там моего кода — кот наплакал, рефакторинг еще предстоит
              но в предыдущем проекте архитектура системы была следующая:
              — клиент стучится по сокетному соединению, если оно не проходит, то тогда стучится по HTTP
              — сервер запущен как демон, которые принимает сокетные соединения
              — все HTTP запросы прокидываются через nginx на демон скрипта
              — протокол обмена бинарный

              статистика показывала, что из 1000 одновременных подключений, где-то только 30-65 идут по HTTP

              преимущество бинарного протокола:
              — компактность
              — защищенность, хотя это сомнительно, но через протокол ни кто не влез, а флеш взламывали

              преимущество сокетного соединения — скорость
            • +2
              преимущество использование демонов:
              — инициализация справочниками проходит один раз, во время загрузки
              — соединение с БД, кешами и прочими сервисами проходит один раз

              возможны проблемы масштабирования, но они просто решаются, да и нагрузок пока таких, чтоб требовалось масштабирование демона — не возникало.
              • 0
                Я курсе про демонов, сам пишу сервер на golang сейчас для одного приложения. И проблем с масштабированием быть не должно — шарлинг, реплики, синхронизация между серверами.
                Но я всё равно не понимаю — у вас вся обработка происходит в череде if-else и switch-case в одной(грубо говоря) процедуре?

                Ну нет у вас парсинга урла, но операций ведь больше чем одна? Даже несмотря на то, что соединение постоянное, есть ведь разные action`ы, есть контроллеры.
                • +1
                  > у вас вся обработка происходит в череде if-else и switch-case в одной(грубо говоря) процедуре?
                  да, есть switch в котором есть вызывается соответствующий метод (action)
                  Каждый метод делает некоторые действия. Как правило, инстанцирует свой класс модели и вызывает её определенный метод. Результат выполнения присваивается переменной $result, из которой формируетя ответ.
                • +1
                  как вариант можно сделать как-то так…
                  $actionName= $action;
                  $this->$actionName($args);
                  

                  но в данном проекте это совсем не катит, пока что реализовано на switch
              • 0
                вот про демоны статья была бы интересна, особенно меня интересуют простые сишные вещи, beanstalkd и аналоги
                • 0
                  если буду писать статью про демоны, то будет про принципы их написание на Си,
                  а не про конкретные демоны. Если данный вопрос интересен, то могу написать.
  • 0
    Автору советую быть проще, и никогда себя не переоценивать, даже если вы очень хороши в проектировании высоконагруженных систем — готовые проекты покажут что вы стоите на самом деле. Ребята из андев делали сайт выборы 2012, вот это реальных хайлоад, одного разработчика я знаю лично. Скромнейший человек кстати. Это делает ему честь. А у вас в статье столько ляпов и дыр в знаниях. Может лучше больше читать и заниматься практикой и не писать такие сложные статьи с горяча?
    • +1
      Архитектор из автора очень даже не плох, а вот о вычитке нужно с ним договориться ;)
      Если у вашего знакомого есть желание поделиться своими знаниями и при этом у него нет обременяющих обязательств перед работодателем — то ждем его статьи/заметки/твита об архитектуре его проекта! Будьте добрее!
      • 0
        просто те кто работает в очень крутых проектах всегда очень и очень заняты и не всегда хотят делится

        обосрасть всегда легче, чем написать хорошую статью
        • +1
          Не всем дано писать статьи, вы об этом не думали? Обычно хороший спортсмен не всегда умеет учить, так как ему не дано внятно излагать информацию ученикам. А тренер же напротив, не всегда хороший спортсмен, но может быть при этом отличным учителем. Про «обосраться» увы не понял вашей мысли.
  • –6
    и почему я не удивлён?
    image
  • +1
    Скажите, о какой нагрузке идет речь, по подробнее статистику.
    • +1
      Нагрузка была небольшая: при нагрузке на WEB сервер на 50-100 одновременных тестирующих потоках был возврат 35% ошибок (MySQL server has gone away).

      после некоторого архитектурного улучшения (рефакторингом это не назовешь, его нужно еще проводить следующим шагом)
      сейчас 320-350 rps, Все работает стабильно и MySQL уже точно не падает.
  • 0
    Код проекта… В общем у меня осталось впечатление, что писал его недоученный студент… И это, немотря на то, что уже был сделан частичный рефакторинг другим программистом. Единственное, что радовало, то это то, что не использовался какой-либо фреймворк. Конечно, это вечно флеймовый вопрос: Иисус или Магомед? Быть или не Быть?

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

  • 0
    Почему не Mysql+Handlersocket, чтобы избавиться от дублирования памяти и не изобретать велосипед с синхронизацией и очередями?
    • 0
      во первых, ни какого велосипеда с очередями нет — это устоявшийся паттерн.
      во вторых — это виртуальный хостинг, и хостер предоставляет только доступ к БД, без её администрирования. Да, в какой-то степени это большой минус…

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.