Pull to refresh

Comments 19

Не перехожу на критику, поскольку не знаю внутренней кухни, однако всё же спрошу — что мешало сделать на Unicode изначально? Думается, было бы проще все сделать по-человечески с самого начала. Или же для этого были свои причины?
Причина, думаю, у большинства проектов, использующих морально устаревшие кодировки, одна: рождение проекта (кодовой базы) в те времена, когда использование многобайтовых кодировок требовало слишком много усилий, а эффект от этого в пределах Рунета бал минимальный. У нас в коде до сих пор встречаются строки, датированные 2000 годом :)
Поправьте меня, если я ошибаюсь, но мне казалось, что МойМир зародился где-то в районе 2005-2006 годов — по меньшей мере, я не помню, чтобы он существовал сильно раньше этих дат. Или же он просто разрабатывался всё это время, начиная с самого начала нулевых?
Не ошибаетесь, проекту Мой Мир и правда 7 лет. Но изначально он вылился из Почты в виде проектов Блоги, Фото и Видео, который использовали кодовую базу почты (и часть БД тоже). Потом из Блогов образовался Мир, а также произошло его слияние с Фото и Видео.
Невозможно по причине того, что максимальная длина ключа индекса в MySQL 767 байт, и индексы (особенно многоколоночные) перестают помещаться.

С версии 5.5.37 (кажется) можно указать в конфигурации:
innodb_large_prefix = ON
innodb_file_per_table = ON
innodb_file_format = Barracuda

Это позволяет хранить в индексе до 3072 байт

Кодировка utf8 в MySQL не умеет хранить 4-х байтные символы unicode, а, например, стандратные смайлы на стандартной клавиатуре в андроиде, успешно их отправляют пачками. Выход — перейти на кодировку utf8mb4 (поддерживается с версии 5.5.3)

Подробнее о проблеме можно почитать тут code.djangoproject.com/ticket/18392#comment:16
Насчет увеличения длины ключа индекса думали, но решили, что не хотим увеличивать без лишней необходимости: все-таки индекс всегда съедает память по максимальному варианту. Пришлось, конечно, повозиться и убедиться в том, что наши строки уникальны на выбранную длину, и конфликтов на уникальных индексах не возникает.
Что касается utf8mb4, согласен, это неприятная проблема. Когда выбирали между utf8mb3 и utf8mb4, мы посчитали, что можем пожертвовать редкими языками в пользу экономии памяти в индексах. Но, к сожалению, не подумали про emoji. Хорошо, что в MySQL у нас почти что нет данных, в которых применение emoji было бы особенно критично (диалоги, лента, комментарии хранятся в специализированных хранилищах). Спасибо, что обратили на это внимание, мы проведем исследование на тему того, насколько это может быть критично для нас, и, возможно, придется принять меры.
что в MySQL у нас почти что нет данных, в которых применение emoji было бы особенно критично

Да да, а потом оно там внезапно появляется и это в зависимости от архитектуры приложения может привести к чему угодно
В MySQL есть возможность указывать используемую кодировку для каждой конкретной колонки, поэтому для совсем новых данных это решается очень просто:
alter table `my_table` add column `my_column` varchar(n) character set utf8mb4;

(и, разумеется, set names utf8mb4 вместо set names utf8)
Если вдруг символы с четырехбайтовым представлением в UTF-8 появляются в колонке, которая определена как utf8mb3, то такой символ просто заменяется на знак вопроса (при условии set names utf8mb4, если используется utf8mb3, то строка внезапно обрежется по такому символу).
Теперь понятно почему в аське от многих контактов не приходят смайлы а рисуются квадратики… конечно, оф.клиент их поддерживает а все остальные в очередной раз выброшены заборт.
Это один из вариантов. Второй, вероятный вариант, использование смайликов, которые появились в последних версиях Unicode (7.0.0, например, добавляет приличное их количество) и которых нету в установленных на системе шрифтах. У меня, например, половина таблиц на странице en.wikipedia.org/wiki/Emoji в квадратиках. Скорее всего это решается установкой/обновлением пакетов с шрифтами.
Но тогда это будут черно-белые смайлы-символы, причем такие какими их нарисовал разработчик шрифта. Куда там анимированные или специфические кастомные смайлы…

Печально. Перешел по ссылке, только 3 таблицы в смайлах и те не до конца…
Красочные смайлики технически тоже шрифты. Я так понимаю, что у этих шрифтов больше вес для диапазонов с пиктограммками, как результат они используются вместо черно-белых.
Квадратики рисуются скорее всего от того, что в шрифте, которым они отображаются, нет нужных символов.
Это как раз тот случай, когда несколько первых фраз вызывают сильнейшее желание немедленно прикрыть ладонью лицо.
Не понял я про Perl часть, как всёже всё произошло.

вот есть у вас кусок кода, который зависит от того, в какой кодировке данные. Например, по бизнес-логике пишет отчёт на диск, в кодировке UTF-8

Было в CP1251:
open my $f, ">", "report.file";
print $f from_to($data, 'cp1251', 'utf8');
close $f;


Нужно после перехода на Unicode:

open my $f, ">:encoding(UTF-8)", "report.file";
print $f $data;
close $f;


в какокй момент один исходник поменялся на другой?
(это просто пример с записью в файл, может быть любая другая операция, md5 от строки с русскими буквами, вызов ord, сериализация в какой-нибудь формат)

Совсем ничего не делать в таком случае не получилось. Мы завели функцию, которая в зависимости от конфигурации сервера (cp1251 или UTF-8) принимала решение о том, как именно перекодировать. Заменили все такие явные преобразования в коде (к счастью, их было не так много, в пределах 200, — это посильная задача), и только после этого стали переключать.
open my $f, ">", "report.file";
my::utf8::encode_any($data, 1);
print $f $data;
close $f;

И ниже, собственно как эти функции реализованы (на самом деле их десяток на все случаи жизни). Сразу оговорюсь по стилю: отсутствие копирования входных параметров и оператора return — принесено в жертву производительности — это разумно для однострочных частовызываемых функций; модификация аргумента in-place и возвращаемый значения — для совместимости с utf8::encode/decode, в будущем это позволит удалить костылики почти не задумываясь.
sub enabled () { $utf8_enabled }

my $UTF8_ENC = Encode::find_encoding('UTF-8'); # Use strict version of UTF-8 to prevent invalid data
my $CP1251_ENC = Encode::find_encoding('cp1251');

if (enabled) {
    *encode_any = sub ($$) { $_[0] = Encode::encode($_[1] ? $UTF8_ENC : $CP1251_ENC, $_[0]); () };
    *decode_any = sub ($$) { $_[0] = Encode::decode($_[1] ? $UTF8_ENC : $CP1251_ENC, $_[0]); 1 };
} else {
    *encode_any = sub ($$) { Encode::from_to($_[0], $CP1251_ENC, $UTF8_ENC) if $_[1]; () };
    *decode_any = sub ($$) { Encode::from_to($_[0], $UTF8_ENC, $CP1251_ENC) if $_[1]; 1 };
}

Очень интересно. Мы тоже переходим на unicode, и у нас Perl часть, похоже, точно такая же как у вас
(начиная с sub enabled () { $utf8_enabled }). Есть моё выступление на YAPC Russia про этот переход youtu.be/43vDtaKl71c ( и это единственное место
где описание процесса лежит в паблике, больше дать нечего)

У нас как раз таких явных преобразований (которых у вас в перделах 200) много. Каждый исходник отдельно переводится «на юникод»
т.е. добавляются эти преобразования (при этом сам код конвертируется в UTF-8, ставится use utf8, и любую строковую константу
тоже нужно обработать такими преобразованиями, перед любым не-ASCII регэкспом их тоже нужно выполнить над исходными данными).

Получается перед любым вызовом внешнего модуля нужно решать нужно ли такие преобразование или нет.
К ним относятся JSON/YAML/итд модули, md5/sha хэши и пр, модули ввода-вывода (т.е. там где текст превращается в бинарные данные):
LWP/другие http(s) библиотеки, любые библиотеки по работе с протоколами, либые print, say, syswrite, pack и пр.

Хранилища впринципе тоже — memcached,redis.
Нам тоже пришлось разделить данные на бинарные и текстовые перед работой с ними, т.к. используем больше Redis, и там нет такой фичи, как в Memcached.

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

Ещё, если не секрет, сколько строчек кода и сколько человек в течение какого срока занимались Perl частью?
Каждый исходник отдельно переводится «на юникод» т.е. добавляются эти преобразования (при этом сам код конвертируется в UTF-8, ставится use utf8, и любую строковую константу тоже нужно обработать такими преобразованиями, перед любым не-ASCII регэкспом их тоже нужно выполнить над исходными данными).

Именно чтобы избежать этого мы и использовали перловые фильтры. Более того, забыл про это написать, мы вместо use utf8 использовали use my::utf8. Особенность нашей прагмы как раз в том, что она применяется условно, в зависимости от той самой константы enabled. Чтобы не быть голословным, приведу еще кусочек кода из этого модуля на эту тему (source_in_utf8 меняется с 0 на 1 при конвертации git-репо, это делается уже после переключения на utf8):
package my::utf8;

use Filter::Util::Call;

sub source_in_utf8 () { 0 }
sub enabled () { $utf8_enabled }

my %FILTERING;
my $UTF8_ENC = Encode::find_encoding('UTF-8'); # Use strict version of UTF-8 to prevent invalid data
my $CP1251_ENC = Encode::find_encoding('cp1251');

sub import {
    my ($class, %args) = @_;
    my $filename = ($args{level} ? caller($args{level}) : caller())[1];
    if (source_in_utf8 && enabled) {
        goto &utf8::import;
    } elsif (source_in_utf8) {
        unless ($FILTERING{$filename}) {
            filter_add(sub {
                my $status = filter_read();
                Encode::from_to($_, $UTF8_ENC, $CP1251_ENC) if $status > 0;
                return $status;
            });
            $FILTERING{$filename} = 1;
        }
    } elsif (enabled) {
        unless ($FILTERING{$filename}) {
            filter_add(sub {
                my $status = filter_read();
                Encode::from_to($_, $CP1251_ENC, $UTF8_ENC) if $status > 0;
                return $status;
            });
            $FILTERING{$filename} = 1;
        }
        goto &utf8::import;
    }
    return;
}

sub unimport {
    my ($class, %args) = @_;
    my $filename = ($args{level} ? caller($args{level}) : caller())[1];
    if (source_in_utf8 && enabled) {
        goto &utf8::unimport;
    } elsif (source_in_utf8) {
        filter_del();
        delete $FILTERING{$filename};
    } elsif (enabled) {
        filter_del();
        delete $FILTERING{$filename};
        goto &utf8::unimport;
    }
    return;
}


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

К счастью, у нас весь ввод/вывод большей частью сосредоточен в нескольких местах: ввод/вывод веб-сервера (разбор get/post параметров, шаблонизатор, аяксы, апишка), клиентики к хранилищам, взаимодействие с внешними системами.

сколько человек в течение какого срока занимались Perl частью?

Собственно perl-часть заняла не так много времени, если посчитать время всех задействованных разработчиков, то вряд ли больше шести-восьми человеконедель. Точно сложно сказать, так как чисто перловых задач было всего несколько, а большинство правок было связано с необходимостью обеспечить плавное и постепенное переключение хранилищ в другую кодировку.
Sign up to leave a comment.