2012-03-28 2 views
6

Мы замечаем, что в нашей базе данных создается множество дублирующих записей в разных таблицах, но они не понимают, почему это происходит. Интересно, что в то время как записи дублируются (вплоть до отметки), в нашей таблице пользователей соль и хэш по-разному различаются для каждой записи, что приводит меня к мысли, что Rails каким-то образом запускает транзакции/операции сохранения дважды. Очевидно, что мы не вызываем save или create несколько раз в код приложения.Что может привести к созданию дубликатов записей Rails?

Это дублирование, похоже, не происходит с каждой записью, сохраненной в базе данных, и мы пока не можем представить шаблон. Существует также проверка validates_uniqueness_of в модели User (хотя это еще не уникальный ключ в таблице, нам нужно очистить все дубликаты, чтобы это было возможно) - поэтому Rails должен остановиться, если запись уже существует, но если запросы одновременно срабатывают, это состояние гонки.

В настоящее время мы запускаем Rails 3.2.2 за Passenger 3.0.11/nginx на наших серверах приложений (в настоящее время 2 из них) и имеем один центральный веб-сервер nginx, который отправляет запросы на сервер приложений. Может ли эта настройка каким-то образом вызвать дублирование процессов или что-то еще? Имеет ли значение, что запросы не блокируются одним сервером восходящего потока (т. Е. Если один пользователь запрашивает страницу, содержащую статический контент, такой как изображения, можно использовать один или оба сервера приложений)? (Я чувствую, что это хватается за соломинку, но я хочу охватить все возможности)

Что еще может привести к тому, что это произойдет?

Update: В качестве примера, пользователь был создан сегодня, который получил дубликаты записей. Оба имеют марку created_at2012-03-28 16:48:11, и все столбцы, за исключением hashed_password и salt, идентичны. Из журнала запроса, я могу увидеть следующее:

App Сервер 1:

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:47:19 -0400 
[2012-03-28 12:47:19] INFO : Processing by ApplyController#create_user as HTML 
[2012-03-28 12:47:20] INFO : Rendered apply/new_user.html.erb within layouts/template (192.8ms) 

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:48:10 -0400 
[2012-03-28 12:48:10] INFO : Processing by ApplyController#create_user as HTML 
[2012-03-28 12:48:11] INFO : Redirected to apply/initialize_job_application/3517 
[2012-03-28 12:48:11] INFO : /app/controllers/apply_controller.rb:263:in `block (2 levels) in create_user' 

App Server 2:

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:48:10 -0400 
[2012-03-28 12:48:10] INFO : Processing by ApplyController#create_user as HTML 

Веб-сервер:

1.2.3.4 - - [28/Mar/2012:12:48:10 -0400] "POST /en/apply/create_user HTTP/1.1" 499 0 "en/apply/create_user" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)" "-" 
1.2.3.4 - - [28/Mar/2012:12:48:11 -0400] "POST /en/apply/create_user HTTP/1.1" 302 147 "en/apply/create_user" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)" "-" 

Так создать действие было удалено три раза (возврат к форме в первый раз из-за ошибки, вероятно), и по крайней мере один раз на каждом сервере. Последние два оба регистрируются веб-сервером в виде отдельных запросов, но первый получает статусный код 499 Client Closed Request (расширение nginx согласно wikipedia), а второе получает 302, как и ожидалось. Может ли 499 быть причиной проблем здесь?

+0

Вы просматривали файлы журналов приложений? Когда дублирующий пользователь существует в db, вы можете найти 2 запроса в вашем файле журнала, соответствующем этому точка или только один? –

+0

У нас нет полных журналов запросов на рельсы в процессе производства, а также только в журнале запросов. Вставки присутствуют в лог-файле mysql, однако действие создания появляется в журналах запросов более одного раза по крайней мере для одного примера le (хотя это вряд ли приведет к многократным * точным дублирующим * записям, нет?) –

+0

Но можете ли вы видеть пары запросов, которые соответствуют повторяющимся записям или только одиночным запросам? –

ответ

5

Две возможности приходят на ум.

Первый - это нечетное (и против RFC) поведение Nginx при использовании в качестве балансировки нагрузки. Он будет повторять любые неудачные запросы против следующего бэкэнд. RFC допускает это только для безопасных методов (например, GET или HEAD). Результатом этого является то, что если ваш nginx считает, что запрос по какой-то причине не прошел, возможно, он будет повторно отправлен на следующий сервер.Если оба сервера завершают транзакцию, у вас есть дубликат записи. Судя по вашему журналу webservers (и код статуса 499, который Nginx использует для обозначения пользователя, отменяющего прерывание в своем браузере), это выглядит как наиболее вероятная причина.

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

Чтобы убедиться, что ваши записи пользователя действительно уникальны, вы должны создать уникальные индексы в своей базе данных. Они фактически гарантированы (хотя и с худшим сообщением об ошибке по сравнению с проверкой ActiveRecord. Из-за этого вы всегда должны определять свое ограничение уникальности как для схемы базы данных, так и для ваших моделей.

Кроме того, вы можете изучить возможность замены frontend nginx с более согласованным loadbalancer.Я бы порекомендовал haproxy.

+0

Допустимо ли, что запрос уже может сделать это выше по течению от веб-сервера до сервера приложения до того, как пользователь отменит запрос, а веб-сервер nginx отмечает запрос как «499»? В таком случае, будет ли запрос жить на сервере приложений, даже если веб-сервер знает, что он завершен? –

+0

Думаю, что да. Как только запрос отправлен на стойку, он больше не прерывается. Поэтому, несмотря на то, что ваш app-server-nginx может обнаружить прерывание соединения, ваш стек rails не знает этого и ничего не может с этим поделать. Он завершает транзакцию, а nginx отбрасывает ответ.Все, пока ваш frontend loadbalancer уже ошибочно перенаправил запрос. –

+0

См. Также http://www.ruby-forum.com/topic/1674379 –

0

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

+0

Что бы я блокировал? Я верю (исправьте меня, если я ошибаюсь), что Rails и/или mysql блокируют таблицы, которые используются в транзакции, - мы столкнулись с ошибками блокировки mysql раньше, потому что таблица была заблокирована слишком долго. –

+0

Да, но я не говорю о транзакциях. Я говорю о запросах, которые отправляются на серверы. Если ваш менеджер запросов неправильно назначает запросы внешним серверам, может быть очень вероятно, что вы получите условие гонки, таким образом дублируя записи. Это зависит от того, как вы настроили менеджера, но я думаю, что это сильная возможность. – Spyros