Pull to refresh
-11
0
Send message

Я почитал историю изменений, но в чем проблема, не понял. Единственное заметное, что сделали в MariaDB и MySQL в последнее время — включили по умолчанию "строгий режим". Ранее СУБД по умолчанию использовали "нестрогий" режим, который прощал многие ошибки. Например, если вы пытались вставить в строчную колонку слишком длинное значение, СУБД просто обрезала его и давала предупреждение (которое в случае PHP обычно никак не видно на стороне PHP и никак не логгируется). Теперь же в "строгом" режиме, СУБД отказывается выполнять такие некорректные запросы.


https://mariadb.com/kb/en/library/sql-mode/


Обратите внимание, что там есть изменеия в 10.2.4:


From version Default sql_mode setting
MariaDB 10.2.4 STRICT_TRANS_TABLES, ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER, NO_ENGINE_SUBSTITUTION

Strict mode. Statements with invalid or missing data are aborted and rolled back, except that for non-transactional storage engines and statements affecting multiple rows where the invalid or missing data is not the first row, MariaDB will convert the invalid value to the closest valid value, or, if a value is missing, insert the column default value. Default since MariaDB 10.2.4.

Впрочем, это можно переключить в настройках. Не в этом ли было дело?


Ну и конечно, поражает качество разработки в Битриксе. Как вам этот гениальный запрос?


INSERT INTO b_iblock_element_property (ID, IBLOCK_ELEMENT_ID, IBLOCK_PROPERTY_ID, VAL UE, VALUE_NUM) SELECT 10555 ,2201 ,P.ID ,'3607' ,3607.0000 FR OM b_iblock_property P WHERE ID = 184

Подзапрос SELECT тут делается только для того, чтобы извлечь P.ID, который явно указан и равен 184. Или это:


$DB->Query("DELETE FROM ".$strTable." WHERE ID = ".$res["ID"]);
$results = $DB->Query("SELECT * FROM ".$strTable." WHERE ID = ".$res["ID"]);”

Эти гении отечественного программирования из Битрикса про плейсхолдеры или подготовленные запросы не слышали? При их подходе легко допустить инъекцию. Ну и в поддержку MariaDB надо слать итоговые запросы, а не этот кусок PHP-кода.


Ну и плохо конечно, что вы не смогли разобраться, в чем была причина проблемы.

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

2 FessAectan: используем свои сборки iperf2/3, одно-два коммерческих решения и несколько самописных. Код наших программистов я не читал, здоровье и так подорвано.
Не то что «несбалансированной»…

Ну вот смотрите. 12 дисков должны быть поделены на две части, так как два контроллера в HA-кластере, каждый из них, должен владеть своим набором дисков, так как они не «шарятся» между контроллерами.

Соответствено в каждой из этих групп по 6 дисков нужно пару дисков отдать под parity (RAID-DP, аналог RAID-6, то есть группа с чередованием и четностью, с двумя дисками четности на RAID-группе).
И один диск на каждой группе должен быть hot spare (диски hotspare также не шарятся).

Остается при 12 дисках всего 3 + 3 диска под данные (по 3 на каждом из контроллеров). Это мало, причем не столько для объема, сколько для производительности, которая сильно зависит от количества дисков в RAID-группе.

Однако если у нас дисков 24, то тогда арифметика другая. Также диски делятся пополам, также один на spare и два на parity на каждой группе, а вот уже все оставшиеся можно целиком использовать под data. Получается 9+9 дисков, то есть эффективной емкости становится уже втрое больше, чем в варианте с 12 дисками. Величина usable от raw уже не 50%, как на 12 дисках, а 75%.

К тому же больше становится и дисковых шпинделей, выше будет и производительность на random IO.
Аналогично, и под линукс это делать куда проще, через LD_PRELOAD. Использую простую функцию:
#define UNPROTECT(addr,len) (mprotect((void*)(addr-(addr%len)),len,PROT_READ|PROT_WRITE|PROT_EXEC))

void detour_function(void * new_adr, int addr)
{
    int call = (int)(new_adr - addr - 5);
    UNPROTECT(addr, 4096);
    *((unsigned char*)(addr)) = 0xE9;
    *((int*)(addr + 1)) = call;
}

Потом достаточно написать свои функции и перенаправить вызов:
detour_function(&hasp_init_game, (int)0x080BD120);

Очень просто и быстро. А если нужна поддержка разных версий, то есть еще и код, который ищет функцию в заданном адресном промежутке по ее сигнатуре.
Вообще говоря, наша in-memory DB достаточно сильно отличается от популярных на текущий момент, таких, как Aerospike, Tarantool и т.п., и по современным понятиям назвать её DB можно с натяжкой. Основная задача была в минимизации оверхеда. Полностью написана на С, описание таблиц — это C-структуры. По этим описаниям генерируются сериализаторы/десериализаторы, чтобы можно было сбрасывать структуры на диск или в сеть. Вся работа идет непосредственно со структурами в памяти. Репликация данных идет отдельно, и вообще ее может не быть. Для отображений и связей используются интрузивные списки (элементы prev/next лежат в самой записи), а голова — в другой таблице. Их тоже нужно объявлять в заголовке структуры. Также есть интрузивные деревья поиска для более сложных структур. Принцип работы с инрузивными типами очень похож на работу в ядре ОС Linux (https://notes.shichao.io/lkd/ch6/). Есть много вспомогательных функций, макросов, автогенераторов, но всё равно это больше программирование на С, чем работа с NoSQL-базой. Сделано это было, так как корни растут еще с начала 90-х, да и на PA-RISC родной компилятор С++ оставлял желать лучшего, а g++ под PA-RISC генерировал код раза в 3-4 медленнее, чем родной, поэтому до 2011 был только С. Сейчас, конечно, boost::intrusive более предпочтительный способ :)

Непосредственно перед стартом определятся максимальный размер каждой таблицы — и именно такой размер выделяется в оперативной памяти (с mlock). Это сделано для обеспечения не только минимальной задержки, но и стабильного джиттера, т.к. если в процессе работы будет увеличение размера, это приведет к небольшим паузам. Из-за особенности торгов мы каждый день перезапускаем систему и, соответственно, корректируем эти значения. Также в ходе старта происходит загрузка всех справочных данных на текущей день из SQL-базы. Можно выделить 4 класса таблиц: вспомогательные (хеши, индексы), статические данные (загружаются в отсортированном виде при старте из SQL-базы и более не меняются), динамические и динамические с реюзингом. Последний тип достаточно интересный, ~70-80 % RAM занимает таблица заявок, количество таких заявок может быть под 100 млн за день, и каждая строчка — это около ~450 байт. Хотя серверы с 1Тб+ RAM вполне доступны сейчас, но лет 6 назад этот объем был существенен, да и серверов с учетом всех гейтвеев более 100. Но самое главное — хранить их всех нет необходимости. Большая часть заявок в ходе торгового дня становится неактивна и нужна только для истории, сразу скажу, эта задача решается online-импортом в SQL-базу. Поэтому в ходе работы с этой таблицей ведется список LRU (least recently used), и когда все доступные записи таблицы использованы, начинается алгоритм реюзинга (отвязывание записей из LRU, входящих в отображения, и перевод в новый список доступных после реюзинга). Эта операция идет блочно по ~32 записи, т.е. мы освободили место под текущую транзакцию и сделали задел на будущее. Если меньше, то операции синхронизации (чтобы безопасно отвязать) на каждой транзакции будут давать замедление (производительность падает на 20 % в этом случае, по сравнению, когда реюзинга еще нет). Если делать больше то на транзакции, в которой происходит операция, будет всплеск задержки в обработки. Поэтому было найдено оптимальное значение (падение производительности ~1 %).

Все запросы пишутся тоже с нуля. Вообще говоря, в запросах сразу заложен нативный протокол Mustang и все запросы сразу формируют Mustang-пакеты без какого-либо внутреннего промежуточного представления. Собственно, по этой причине и нет сейчас «бинарного» протокола на фондовом и валютном рынке.

Данная база модифицируется всё время, пока функционирует Биржа, начиная с австралийской версии ASTS.

База на текущий момент не распределенная и все серверы имеют ее реплики на, в теории, неограниченное число серверов. Об этом расскажем в следующей части
А зачем вы активно писали в таблицу с индексом? Я так понимаю, ваш кейс — сначала запишем миллиард записей, потом начинаем оттуда читать. Вывод — дропаем индекс, ставим COPY/BULK INSERT/что--то другое, отключаем autocommit/fsync и д.р., идем на обед или ставим на ночь. Приходим — все закачалось, возвращаем все назад, ставим индекс обратно, радуемся.
Если же вам нужно одновременно И читать, И активно писать, то это надо было делать с разных реплик, конечно же.
М… Там есть ещё одна проблема, я пока не знаю как её количественно описывать, но суть проблемы в том, что не смотря на низкую загрузку процессора при большом числе виртуальных машин на хосте (>>100) наблюдаются неприятные эффекты увеличения задержек. Насколько я понимаю, это связано во-первых с cache trashing, во-вторых с чрезмерным количеством event'ов, которые проходят в dom0. Чем больше ядер, тем меньше это заметно.

Это не "неадекватная работа сети", а вполне как раз правильная и документированная в стандартах.


В пакетах типа "станция-точка" есть ТРИ поля для MAC-адресов: адрес станции (клиента), адрес точки доступа и адрес какого-нибудь хоста "за точкой". Т.е. на точке бриждевать можно, для этого она и есть, а на станции — нет: некуда в пакет положить адрес хоста, расположенного "за станцией". Если заменить адрес станции (которая в вашем случае — хост) на адрес чего-то там за станцией (которое в данном случае — виртуалка), то точка доступа пакет не примет — у неё нет в табличке ассоциировавшихся клиентов станции с таким адресом.


(Здесь мог бы помочь четырёх-адресный формат пакета, WDS, но он в стандартах не детализован, каждый производитель оборудования реализует по-своему, поэтому, вообще говоря, если у вас в точке доступа broadcom, а на ноуте atheros, работать с четырёхадресным форматом не будет. Ну и на десятке вы это вряд ли заведёте.)


Странно, что на форумах не нашлось ни одного человека, кто вам бы про это рассказал.


Для бриджевания на станции может помочь MAC-NAT (или Layer2 NAT), когда станция уходящие пакеты отправляет со своим MACом, записывая в табличку, а для ответов угадывает, на что это были ответы и делает обратную подстановку — по аналогии с NAT, только на канальном уровне. (В Linux это настраивается через утилиту ebtables.) Если это недоступно, но транслировать IP-адреса не хочется, то можно ещё попробовать proxy-arp — маршрутизация, когда за двумя разными интерфейсами одна и та же подсеть, извне (со стороны точки доступа) это выглядит абсолютно так же, как и MAC-NAT.


Но ни того, ни другого десятка не умеет. Не знаю, умеет ли это серверная винда, возможно, proxy-arp могла бы уметь.

Эксперты говорят, сравнивать DSP и FPGA не совсем корректно. У них свои ниши. http://www.ti.com/lit/wp/spry296/spry296.pdf. Все упоминаемые решения имеют право на жизнь и в своих задачах будут иметь преимущество. Говорить категорично, что DSP, как технология is dead, не стоит. Разработчику объективно довольно сложно сейчас разобраться, когда что применять. Есть эксперты типа BDTI. А часто и экспертам сложно разобраться с учетом динамики технологий. Но будем стараться…
Суть современных методов парольной аутентификации в том, чтобы проверка правильности пароля проходила долго (SCRAM), а лучше долго и дорого (Argon2). Использование одной итерации хэша MD5 практически ничем не отличается от хранения пароля в открытом виде. Если кто-то угонит базу паролей (а он обязательно её угонит, если она будет стоить того), то он сможет за небольшое время вскрыть её всю. Плюс необходима защита от прямого перебора паролей снаружи: при отрицательной проверке пароля делать случайную задержку ответа на несколько секунд (5-15).

salt нужна для того, чтобы одинаковые пароли пользователей выглядели в базе по-разному. В идеале — это набор байт, полученный криптостойким генератором случайных чисел. Не стоит заморачиваться на какие-то околослучайные алгоритмы, можно просто прочитать в массив из /dev/random и обернуть его base64 (или чем-то другим по вкусу). Главное, чтобы этот набор байт был достаточно длинным, чтобы при подборе паролей нельзя было сгенерировать в памяти сразу все возможные варианты salt и быстро прогнать с ними хэш-функцию.

Мой опыт с k8s в районе persistent volumes показал, что оно не плохо, а катастрофически плохо (т.е. плохо во время катастроф). Без адекватного STONITH оно жить не сможет, а STONITH в k8s не завезли.


А как сделать "плохо"? Ну, например, во время установки stp-соединения не закрыть канал. Раз 8 или 16.


Вы думаете, что у вас в инсталляции нет STP? А если найду? STP находится где-то между SAS HBA и SAS Enclosure, а цифра 8 или 16 соответствует wide port в SAS.


Что происходит после того, как все 16 каналов заняты? Теоретически, контроллер должен послать bus/host reset, но тут интрига: не проходит.


С практической стороны это выглядит как TASK_UNINTERRUPTIBLE (D+) и делай что хочешь. В силу устройства контейнеров, пока все процессы не прибъёшь — pod живёт. А если процесс игнорирует ваши просьбы-9? Продолжает жить. Если pod продолжает жить, deploy не видит смысла спанить ещё один.

>В случае с неблокирующими сокетами для эффективного вычисления значения таймаута, которое надо передать в функцию select() или ее аналоги, можно применить структуру данных, которая называется «двоичная куча». Другое ее название — «пирамида».
Типичная ошибка, которую наблюдаем в таких библиотеках как libev.
Нужно разделять два типа таймеров: первые будем называть таймаутами, вторые таймерами.
Таймауты — это такие таймеры, которые почти всегда будут удалены и добавлены заново, вероятность того что нам придётся его исполнить мала. Например отслеживаем когда пришёл последний пакет и если втечении 30секунд больше не прилетало пакетов, то принудительно дисконнектим.
Таймеры же наоборот, практически всегда будут исполняться и очень редко удаляться для передобавления.

Если у нас простое сетевое приложение и все клиенты должны обрубаться по таймауту в 30секунд, то мы можем воспользоваться обычной FIFO очередью, в которую будем добавлять таймауты, а чтобы определить сколько секунд осталось до последнего таймаут, то достаточно просто посмотреть на первый элемент в очереди, а потом передать это значение в select/etc.
В случае если у нас множество различных таймаутов, то можем воспользоваться алгоритмом Timing Wheel.

Ну а для таймеров отлично подойдёт Heap, rb-tree итд.
Очень бы не рекомендовал использовать разные типы передаваемых аргументов в V8 в тех случаях, где требуется высокая нагрузка.
JIT компилятор не сможет использовать нативный код в таких функциях.

Это прекрасно. Вы используете винду как линукс. Поясню свою мысль. На линуксе можно по-быстрому нахакать какой-нибудь шел-скрипт, который будет генерить скрипт на седе, который будет генерить какой-нибудь юнит-файл для systemd, который ещё чё-нибудь куда-нибудь пропишет и так далее и тому подобное. Теперь выясняется, что в винде так тоже можно. В связи с этим у меня к вам как к эксперту по использованию винды в качестве линукса (ну и к другим хабравчанам), есть пара вопросов. Ответьте, если не сложно. Ну или ссылки дайте или направление хотя бы.


  • В линуксе при написании скриптов на баше, программ на си, всемозможных sed'овых регексов и так далее нужно знать, что и когда эскейпить, помещать в кавычки и так далее. Скажем, вот так писать нельзя: find -name *.txt, надо так: find -name '*.txt', а вот у rm, напротив, надо так: rm *.txt. Расскажите, как с этим в винде. Как работают все эти кавычки в cmd? Скажем, в баше баш сам звёздочки раскрывает. А в винде что?
  • Тесно связанный с предыдущим вопрос. В линуксе есть exec-команды и есть shell-команды (термины мои, но сами понятия, стоящие за ними, разумеется, не мои). Exec-команда — это команда, предназначенная непосредственно для исполнения ядром (т. е. для передачи системным вызовам семейства exec). Shell-команда — это команда, которая будет обработана шелом. Скажем, chroot воспринимает свои аргументы, начиная со второго, как exec-команду, т. е. сразу передаёт её ядру. Поэтому, скажем, нельзя написать chroot / cd. Т. к. cd — это внутренная команда bash'а, здесь так не прокатит. А вот, скажем, ssh принимает в качестве аргументов, начиная со второго, shell-команду. Так что писать ssh user@host cd можно (хотя ничего эта команда не даст), да и вообще вторым аргументом в ssh можно запихнуть любую башевую команду, пускай даже с ветвлениями и прочее. С chroot такое не прокатит. Как со всем этим обстоят дела в винде? Если запустить команду "cmd /q /c ..." (вижу у вас там в посте такую) каким конкретно преобразованиям будет подвергнута команда, переданная в cmd (ну там, убирания кавычек и т. д.)? Какими функциями WinAPI запускаются команды для запуска сразу ядром и какими — шелом? И вообще, в линуксе shell-команда — это строка, а exec-команда — это массив строк. А в винде? Как формируется argv[], который получает программа на си?
  • Каковы, скажем так, "правила поведения" для виндовых программ (всех, консольных и графических)? Как им себя вести, чтобы не мешать другим программам? Где, скажем, хранить настройки и прочее-прочее? В линуксах я знаю, конфиги — в /etc, логи — в /var/log и так далее. В линуксе есть хороший гайд по требования к пакетам свободного ПО: https://wiki.debian.org/UpstreamGuide. На многие мелкие темы можно найти кучу подробных ответов, скажем, где хранить временные файлы: http://0pointer.net/blog/projects/tmp.html .
  • А существуют ли специфичные "правила поведения" для консольных программ? Как лучше всего распространять консольные программы? Неужели для консольной программы писать графический инсталлятор со всякими Далее-Далее-Готово?
  • Как распространять библиотеки на си? Чтобы пользователь после установки мог сразу их заюзать в своём visual studio. Куда хедеры класть? Вот у меня есть библиотека на си: http://github.com/safinaskar/libsh. Поддерживает линукс и винду. На линуксе собирается и ставится при помощи "cmake. && make && make install". На винде собирается при помощи "cmake. && cmake --build .". А вот что делать дальше, куда ставить и главное, зачем, не понятно. Куда принято ставить библиотеки в винде и как их потом использовать?

По всем этим вопросам, я, как мне кажется, хорошо разбираюсь, если дело касается линукса. Могу написать на эти темы статью (статьи), если интересно

jpegtran отрезает лишнюю мета информацию (-copy none) и подбирает оптимальные коэффициенты для косинусного преобразования (DCT), при которых размер сжатых данных получается минимальным.

есть ещё jpegoptim, но в режиме lossless он показывает худшие результаты, хотя незначительное уменьшение качества практически незаметно в большинстве случаев, зато размер существенно сокращается.
Ну, немного о цифрах — у 100GE с обычным 1536b пакетами скорость классификации близка к 10Mpps — далеко не все современные фреймворки способны переваривать такой рейт на чем-то, кроме топовых процессоров. Если вас вдруг начнут атаковать и пакет рейт вырастет на порядок — x86 просто не будет способен это переварить, по нескольким причинам:
а) нельзя масштабироваться на соседние NUMA-ноды, то есть сокеты, слишком большие потери даже при bulk queuing пакетов для пересылки между процессором и очередями карт, сюда же записываем проблему с работой DCA, который хорошо вывозит работу с картой на сравнительно небольших объемах трафика,
б) классификация пакетов не может съедать ниже определенного числа тактов на пакет, равного примерно полусотне.

Иными словами, хотите крутой и производительный SDN — распределяйте обработку пакетов по коробкам, а там уже не имеет значения, атом ли это или топовый зеон. В сабжевом устройстве единственная задача управляющей платы — запускать линукс с программирующей сущностью, обычно это сцепленный с API матрицы виртуальный свич.
Да, увеличивает, только резко возрастает количество TCP out of order пакетов, даже при crossover подключении без свичей вовсе. Поэтому нужно задирать параметр net.ipv4.tcp_reordering.
К слову о функциях и переменных:

function assert2 {
        msg=$1
        shift
        test "$@"
        ec=$?
        if [ $ec -gt 0 ]
        then
                echo "ERROR (assertion $* failed): $msg"
                exit 2
        fi
}

function run {
        "$@"
        ec=$?
        if [ $ec -gt 0 ]
        then
                echo "failed with error code $ec:" "$@"
                exit 3
        fi
}


И всё, что можно, оформляется в таком духе:

d=`date +%F--%H-%M`
assert2 "mysqldump_console is empty" ! -z "$mysqldump_console"
run $mysqldump_console --result-file=$backup_to"${d}-database.sql"


$mysqldump_console и $backup_to берётся из настроек конкретного проекта с помощью команды source.

При таком подходе проблемы начинаются тогда, когда нужна более продвинутая обработка ошибок вроде конструкции try-catch-finally и пролетающих сквозь стэк исключений. Тогда я не жалею времени, чтобы переписывать bash-скрипт бэкапа на программу на питоне:

...
if not machine_is_on(machine_info['host']):
	turn_on_machine(machine_info['host'], machine_info['mac'])

assert not os.listdir(settings.target_mountpoint), 'mountpoint is not empty'

mount_command = ("smbmount //%(host)s/%(shared_path)s " + settings.target_mountpoint + " -o %(mount_options)s") % machine_info
if not os.system(mount_command) == 0:
	raise Exception('mount error')
try:
	# do backup
finally:
	if not os.system("smbumount %s" % settings.target_mountpoint) == 0:
		raise Exception('umount error')
...

Information

Rating
Does not participate
Registered
Activity