Состоянии гонки (Race condition) на примере счетчика

Состоянии гонки (Race condition) на примере счетчика

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

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

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

Счетчик просмотров

Надо понимать, куда будем записывать данные. Самая большая ошибка, когда пишут в post_meta или term_meta. Это самый плохой вариант, так как счет просмотров обновляется постоянно, что лишает вас объектного кеширования данных для поста/термина.

Записывать данные в таблицу options тоже не отлично, ведь они тоже кешируются, но можно указать, чтобы данные опции не загружались автоматически.

Но лучше всего сделать простую таблицу для хранения при активации плагина:

<?phpclass Views {public static function table() {global $wpdb;return $wpdb->prefix. 'views';}public static function create_table() {require_once ABSPATH. 'wp-admin/includes/upgrade.php';global $wpdb;$sql = 'CREATE TABLE '. self::table(). '(`post_id` INT NOT NULL UNIQUE,`views` INT UNSIGNED DEFAULT 0) '. $wpdb->get_charset_collate();maybe_create_table( self::table(), $sql);}}register_activation_hook( __FILE__, [ 'Views', 'create_table' ]);

Плохой счетчик

Как обновлять данные? 99% всех счетчиков обновляют следующим образом:

<?php class Views {//...public function get_views( $post_id) {global $wpdb;return absint($wpdb->get_var($wpdb->prepare('SELECT views FROM '. self::table(). ' WHERE post_id=%d',$post_id)));}public function update( $post_id) {global $wpdb;$views = $this->get_views( $post_id);$wpdb->query($wpdb->prepare('UPDATE '. self::table(). ' SET views=%d WHERE post_id=%d', ++$views, $post_id))}}

Получили данные, увеличили на 1 и записали обратно в базу данных. Весь способ на update у нас будет уязвивым(критическим) участком кода.

Почему так делать нельзя? Запускаем нагрузочное тестирование на 10000 запросов в течении минуты, для чистоты результата, на странице кроме счетчика, нет кеширования и содержимого.

В результате вместо 10000 просмотров у нас 7240 просмотров — погрешность 27,5%.

Это произошло из-за того, что пару запросов одновременно были отправлены с одной и той же информацией. То есть с момента, когда мы данные получили, до момента, когда мы их обновили, другой запрос изменил данные данные.

Хороший счетчик

Для того, чтобы этого избежать, в месте, куда вы записываете данные должна быть блокировка до момента записи. К счастью в MySQL базах это реализовано.

<?php class Views {//...public function update( $post_id) {global $wpdb;$views = $this->get_views( $post_id);$wpdb->query($wpdb->prepare('UPDATE '. self::table(). ' SET views = views + 1 WHERE post_id=%d', $post_id))}}

Запускаем нагрузочное тестирование. В результате на 10000 запросов столько же и просмотров. В данном случае все запросы в MySQL выстроились в очередь и пока не выполнится 1 запрос остальные его ждут.

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

Это самый простой пример гонки состояния. В целом все данные, которые имеют состояние(к примеру их надо приобрести из базы, посчитать и записать в базу) лучше делать через MySQL, так как там есть блокировка состояния.

А сейчас представьте ситуацию, где банк при переводе денег с карты на карту точно так же зависит от кол-ва нагрузки и вместо 10000 грн вам будет начислено всего 7240 грн или ничего не начислено.

Всем также рекомендую посмотреть видео от Геннадия Ковшевина о состоянии гонки:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *