по слухам проблема булинга школе стоит довольно остро
У меня двое детей, учились в двух разных школах (по причине «перебрались ближе к дому»), и кажется, что в Российских школах было на порядок больше буллинга (но сужу по себе, дети в школу пошли уже в UK).
Есть ещё специфический вариант с полной картой. Нужны два списка: список шардов и карта ID элемента -> ID шарда. При создании новой сущности, шард выбирается по любым удобным правилам (случайно, раунд-робином или по весам — it's up to you). В карту пишется какому шарду принадлежит сущность.
Плюсов два:
— перемещение единичных сущностей упропрощается донельзя
— распределение по шардам контроллируется максимально гибко
Минусы очевидны:
— размер карты равен количеству сущностей
— на каждое чтение сущности нужно прочитать запись из карты
Если хранилище карты быстрое, минусы нивелируются.
Чистое любопытсво: почему китайских? Полагаю, первая и основная проблема была с дизайном: то что можно сказать двумя иероглифами приходится писать пятью словами на не-иероглифических языках?
Почитайте комментарии, мы частично обсуждали эти же вопросы.
Использовала ли Badoo какую-то готовую систему или же это все писалось в компании с нуля?
Писали с нуля, затачивали под свои нужды.
Интересно например, при такой нагрузке — вероятно где-то заранее(на каком этапе?) формируется готовый набор языковых шаблонов, или же какие-то из описанных ситуаций в нем все-же остаются переменными?
Да и да :) Статичные лексемы (не зависящие от внешних данных, например «Здравствуйте, {{user_name}}») генерируются на этапе деплоя кода — почитайте нашу статью про разработку и деплой.
В лексемах, зависящих от числа и использующих склонения остаётся определённая доля динамики (например, «Вы понравились 3 девушкам» = «Вы понравились {{users_num}} {{users_word#Dative}}»)
Также интересно, насколько система переводов в Badoo является самостоятельной и монолитной. Или же наоборот, ее части размазаны по проекту.
Система достаточно монолитна. У нас есть два типа хранения переводов: словари и текст в шаблонах. При этом в шаблонах нет никаких указаний на переводы, а в коде присутствуют только вызовы «дай-ка мне вот эту фразу из словаря». Словари используются как для веб, так и для мобильных приложений. И словари, и шаблоны генерируются на этапе деплоя веба или на этапе сборки приложения.
Как я уже говорил, мы затачиваем свою систему переводов под себя. И поверьте, система переводов Badoo гораздо сложнее, чем простое key-value хранилище. Для геттекста пришлось бы выстраивать «наше АПИ поверх», и не факт что было бы быстрее в разработке и производительнее. Мы выбрали гибкость.
Судя по текущему состоянию голосования, следующая статья будет как раз об этом ;)
Объект-обёртка создаётся для конкретного языка, на котором отображается сайт или генерируется ответ для мобильного приложения. Ну и наши объекты умеют зависеть только от числа (передаётся из кода) и падежа (выбирается переводчиком). То есть в псевдокоде как-то так:
$current_language = 'RU'; //Берём текущий язык
$Localisation = new Localisation($current_language); //Создаём объект "локализация"
$users_num = 5; //Число пользователей
$users_word = 'girl'; //Идентификатор слово, которое будет зависеть от числа и склоняться
$Word = $Localization->getNumDependent($users_word, $users_num); //Получаем тот самый "объект-обёртку"
//Готовим параметры для шаблонизатора
$params = array(
'users_num' => $users_num,
'users_word' => $Word,
);
//Выводим "Вы понравились 5 девушкам"
echo Render('Вы понравились {{users_num}} {{users_word#Dative}}', $params);
Тема склонений действительно очень интересна, но текущая реализация нас не очень устраивает из-за того, что разработчик должен обеспечить передачу объекта-контейнера в шаблонизатор. Сейчас мы работаем над тем, чтобы переводчик мог сам, без участия разработчиков, сделать часть лексемы зависимой от числа и/или заменить слово объектом-контейнером. Самый сложный вопрос в таком решении — насколько это ударит по перфомансу.
Как правило для фраз, зависящих от пола, создаётся три варианта: Male, Female и Unknown
Unknown используется не только в тех случаях, когда пол не указан, но и когда речь идёт о пользователях разных полов. Такие лексемы перефразируются максимально нейтрально.
Шаблоны, конечно же, одни на всех. Переводчик видит лексему и переменные в ней, исходный вид "{{users_num}} {{users_word}} liked you" — никаких падежей.
Для каждого языка существует свой собственный набор падежей, и переводчик сам решает, какой падеж применить в его конкретном случае. Фразу ведь можно перефразировать как "{{users_num}} {{users_word}} заценили вас", тогда нужно использовать именительный падеж.
Пусть у нас есть две лексемы:
Вы понравились {{users_num}} {{users_word#Dative}}
Разработчики обеспечивают, что в шаблонизатор попадёт не конкретное слово «девушка», а объект-обёртка, умеющий получать правильную форму по числу и правильный падеж (указанный в переменной).
Разработчик не знает как переводчик составит фразу, он просто обеспечивает покрытие всех кейсов. А переводчики подготавливают этот универсальный объект примено так:
Согласен, числа прописью делают текст более естественным, но съедают много места, особенно при больших значениях. Давайте считать наш пример промежуточным: не идеальным, но всё же достаточно понятным и гибким (учтены и склонения, и зависимость от числа).
И даже многомесячные ветки были. Если не забывать регулярно в долгие ветки подтягивать свежий мастер — никаких проблем.
Тестируются до билда все ветки на девеле и (не ИЛИ, а именно И) в боевом окружении, в т.н. шотах. Шоты — тема для отдельной статьи, не буду отбирать у авторов этого великого творения право высказаться :)
Описание специально упрощённое. Это достаточно базовые знания о структуре таблиц и индексов, статья писалась для относительных новичков. Мне такой информации в своё время очень не хватало. Именно упрощённой и разжёванной.
Что касается «плоской» и не-совсем-такой-как-в-реальности «индексной таблицы» (каковой, строго говоря, вообще не существует) — это абстракция, упрощающая восприятие. Возможно я зря решил так упростить, но лично мне было бы проще воспринимать именно так.
mysql> EXPLAIN SELECT * FROM `t1_isam` WHERE `i1` > 200 AND `i2` < 200\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1_isam
type: ALL
possible_keys: secondary_index
key: NULL
key_len: NULL
ref: NULL
rows: 6
Extra: Using where
Оптимизатор с первой же попытки отказывается использовать ключ, фулскан быстрее. В данном примере MyISAM будет особенно быстр (все поля NOT NULL, все поля фиксированной длины — очень удобно читать).
Ограничиваем выборку полями `i1` и `i2` (они и только они находятся во вторичном ключе в MyISAM).
mysql> EXPLAIN SELECT `i1`, `i2` FROM `t1_isam` WHERE `i1` > 200 AND `i2` < 200\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1_isam
type: range
possible_keys: secondary_index
key: secondary_index
key_len: 4
ref: NULL
rows: 5
Extra: Using where; Using index
Сам первичный (кластерный) ключ хранится вместе с данными и под абстракцию «индексной таблицы» не подходит. А вот вторичные ключи все имеют скрытую часть в виде полного набора полей ключа первичного.
Пример. Есть таблица t1 (InnoDB) с полями k1, k2, i1, i2. Есть первичный ключ по полям k1, k2 (он и будет кластерным). И есть «обычный» индекс (вторичный ключ) по полям i1, i2.
Строки в индексной таблице для вторичного ключа будут представлять собой кортежи вида
(`i1`,`i2`, `k1`, `k2`)
где первая пара полей (i) используется для поиска второй (k). Когда получена вторая пара (k, образующая первичный ключ), по ней ищутся строки в таблице данных.
И посмотрим план выполнения простого запроса с фильтрацией:
mysql> EXPLAIN SELECT * FROM `t1` WHERE `i1` > 200 AND `i2` < 200\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: range
possible_keys: secondary_index
key: secondary_index
key_len: 4
ref: NULL
rows: 4
Extra: Using where; Using index
Ключ наш оптимизатором запросов признан годным и в плане выполнения используется. Кроме того, Using index в графе Extra означает, что фактического чтения данных с диска не производилось. Все данные брались прямо из индекса. В графе key указан secondary_index, значит все выбранные данные содержатся в нём.
Для доказательства и интереса ради проведём простой эксперимент. Выполним
ALTER TABLE `t1` DROP PRIMARY KEY;
ALTER TABLE `t1` ADD PRIMARY KEY (`k1`);
В результате чего поле k2 выпадает из индексов. План выполнения запроса тут же меняется:
mysql> EXPLAIN SELECT * FROM `t1` WHERE `i1` > 200 AND `i2` < 200\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: ALL
possible_keys: secondary_index
key: NULL
key_len: NULL
ref: NULL
rows: 6
Extra: Using where
Обратите внимание на «key: NULL». Поскольку так или иначе придётся читать данные, не входящие в ключ (выбираем-то мы "*"), оптимизатор MySQL принимает решение не использовать ключи вообще, а провести фулскан (type: ALL). Естественно, в Extra больше нет «Using index».
Но стоит нам исключить из выборки поле `k2`, как мы возвращаемся к использованию кластерных ключей:
mysql> EXPLAIN SELECT `k1`, `i1`, `i2` FROM `t1` WHERE `i1` > 200 AND `i2` < 200\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: range
possible_keys: secondary_index
key: secondary_index
key_len: 4
ref: NULL
rows: 4
Extra: Using where; Using index
У меня двое детей, учились в двух разных школах (по причине «перебрались ближе к дому»), и кажется, что в Российских школах было на порядок больше буллинга (но сужу по себе, дети в школу пошли уже в UK).
Всё сильно зависит от строк, имхо. Если в строке два инта — можно сильно не ограничиваться.
Плюсов два:
— перемещение единичных сущностей упропрощается донельзя
— распределение по шардам контроллируется максимально гибко
Минусы очевидны:
— размер карты равен количеству сущностей
— на каждое чтение сущности нужно прочитать запись из карты
Если хранилище карты быстрое, минусы нивелируются.
Писали с нуля, затачивали под свои нужды.
Да и да :) Статичные лексемы (не зависящие от внешних данных, например «Здравствуйте, {{user_name}}») генерируются на этапе деплоя кода — почитайте нашу статью про разработку и деплой.
В лексемах, зависящих от числа и использующих склонения остаётся определённая доля динамики (например, «Вы понравились 3 девушкам» = «Вы понравились {{users_num}} {{users_word#Dative}}»)
Система достаточно монолитна. У нас есть два типа хранения переводов: словари и текст в шаблонах. При этом в шаблонах нет никаких указаний на переводы, а в коде присутствуют только вызовы «дай-ка мне вот эту фразу из словаря». Словари используются как для веб, так и для мобильных приложений. И словари, и шаблоны генерируются на этапе деплоя веба или на этапе сборки приложения.
Судя по текущему состоянию голосования, следующая статья будет как раз об этом ;)
Объект-обёртка создаётся для конкретного языка, на котором отображается сайт или генерируется ответ для мобильного приложения. Ну и наши объекты умеют зависеть только от числа (передаётся из кода) и падежа (выбирается переводчиком). То есть в псевдокоде как-то так:
Тема склонений действительно очень интересна, но текущая реализация нас не очень устраивает из-за того, что разработчик должен обеспечить передачу объекта-контейнера в шаблонизатор. Сейчас мы работаем над тем, чтобы переводчик мог сам, без участия разработчиков, сделать часть лексемы зависимой от числа и/или заменить слово объектом-контейнером. Самый сложный вопрос в таком решении — насколько это ударит по перфомансу.
Unknown используется не только в тех случаях, когда пол не указан, но и когда речь идёт о пользователях разных полов. Такие лексемы перефразируются максимально нейтрально.
Для каждого языка существует свой собственный набор падежей, и переводчик сам решает, какой падеж применить в его конкретном случае. Фразу ведь можно перефразировать как "{{users_num}} {{users_word}} заценили вас", тогда нужно использовать именительный падеж.
Пусть у нас есть две лексемы:
Вы понравились {{users_num}} {{users_word#Dative}}
{{users_num}} {{users_word#Nominative}} заценили вас
Разработчики обеспечивают, что в шаблонизатор попадёт не конкретное слово «девушка», а объект-обёртка, умеющий получать правильную форму по числу и правильный падеж (указанный в переменной).
Разработчик не знает как переводчик составит фразу, он просто обеспечивает покрытие всех кейсов. А переводчики подготавливают этот универсальный объект примено так:
Тестируются до билда все ветки на девеле и (не ИЛИ, а именно И) в боевом окружении, в т.н. шотах. Шоты — тема для отдельной статьи, не буду отбирать у авторов этого великого творения право высказаться :)
Что касается «плоской» и не-совсем-такой-как-в-реальности «индексной таблицы» (каковой, строго говоря, вообще не существует) — это абстракция, упрощающая восприятие. Возможно я зря решил так упростить, но лично мне было бы проще воспринимать именно так.
Речь здесь именно о InnoDB, а там свои правила: dev.mysql.com/doc/refman/5.0/en/innodb-index-types.html И кластерный индекс создаётся всегда.
Оптимизатор с первой же попытки отказывается использовать ключ, фулскан быстрее. В данном примере MyISAM будет особенно быстр (все поля NOT NULL, все поля фиксированной длины — очень удобно читать).
Ограничиваем выборку полями `i1` и `i2` (они и только они находятся во вторичном ключе в MyISAM).
Вуаля!
Пример. Есть таблица t1 (InnoDB) с полями k1, k2, i1, i2. Есть первичный ключ по полям k1, k2 (он и будет кластерным). И есть «обычный» индекс (вторичный ключ) по полям i1, i2.
Строки в индексной таблице для вторичного ключа будут представлять собой кортежи вида
где первая пара полей (i) используется для поиска второй (k). Когда получена вторая пара (k, образующая первичный ключ), по ней ищутся строки в таблице данных.
Заполним таблицу случайными данными:
И посмотрим план выполнения простого запроса с фильтрацией:
Ключ наш оптимизатором запросов признан годным и в плане выполнения используется. Кроме того, Using index в графе Extra означает, что фактического чтения данных с диска не производилось. Все данные брались прямо из индекса. В графе key указан secondary_index, значит все выбранные данные содержатся в нём.
Для доказательства и интереса ради проведём простой эксперимент. Выполним
В результате чего поле k2 выпадает из индексов. План выполнения запроса тут же меняется:
Обратите внимание на «key: NULL». Поскольку так или иначе придётся читать данные, не входящие в ключ (выбираем-то мы "*"), оптимизатор MySQL принимает решение не использовать ключи вообще, а провести фулскан (type: ALL). Естественно, в Extra больше нет «Using index».
Но стоит нам исключить из выборки поле `k2`, как мы возвращаемся к использованию кластерных ключей: