Простое приложение rails на Puma генерирует segfault, не может обрабатывать параллелизм
У меня довольно простое приложение на Rails. Он слушает запросы в форме
example.com/items?access_key=my_secret_key
Контроллер моего приложения просматривает секретный ключ, чтобы определить, какой пользователь выполняет вызов, ищет свои учетные данные базы данных и подключается к соответствующей базе данных, чтобы получить предметы этого человека.
Однако нам нужно, чтобы эта поддержка поддерживала несколько запросов одновременно, и Puma кажется нам любимым / самым быстрым сервером для всех. Мы начали сталкиваться с проблемами, сравнивая его с ApacheBench. К вашему сведению, Puma настроена на 3 рабочих и min=1, max=16 потоков.
Если бы я должен был бежать
ab -n 100 -c 10 127.0.0.1:3000/items?access_key=my_key
затем эта ошибка выдается с большим количеством трассировки стека после нее:
/home/user/.gem/ruby/2.0.0/gems/mysql2-0.3.16/lib/mysql2/client.rb:70: [BUG] Segmentation fault
ruby 2.0.0p353 (2013-11-22 revision 43784) [x86_64-linux]
Редактировать: эта строка также появляется в огромном количестве информации, содержащейся в ошибке:
*** glibc detected *** puma: cluster worker 1: 17088: corrupted double-linked list: 0x00007fb671ddbd60
И мне кажется, что это срабатывает несколько раз. Я не смог точно определить, когда (по каким запросам) он отключается. Бенчмаркинг, кажется, все еще заканчивается, но он кажется довольно медленным (из ab):
Concurrency Level: 10
Time taken for tests: 21.085 seconds
Complete requests: 100
Total transferred: 3620724 bytes
21 секунда за 3 мегабайта? Даже если MySQL был медленным, это... плохо. Но я думаю, что это еще хуже - объем данных недостаточно высок. Когда я запускаю параллелизм 1, ошибок сегмента нет, а объем данных для -n 10 -c 1 составляет 17 мегабайт. Таким образом, puma отвечает с некоторой страницей ошибок, которую я не вижу - запуск "curl address" дает мне ожидаемые данные, и я не могу вручную выполнить параллелизм.
Это ухудшается, когда я запускаю больше запросов или более высокий уровень параллелизма.
ab -n 1000 -c 10 127.0.0.1:3000/items?access_key=my_key
доходность
apr_socket_recv: Connection reset by peer (104)
Total of 199 requests completed
а также
ab -n 100 -c 50 127.0.0.1:3000/items?access_key=my_key
доходность
apr_socket_recv: Connection reset by peer (104)
Total of 6 requests completed
Бег top
в другом окне замазки показано, что очень часто (в большинстве случаев я пытаюсь провести сравнительный анализ) только один из трех рабочих, созданных пумой, выполняет какую-либо работу. Редко все трое делают.
Поскольку кажется, что ошибка может быть где-то здесь, я покажу вам мой application_controller. Это коротко, но основная часть приложения (что, как я уже сказал, довольно просто).
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def get_yaml_params
YAML.load(File.read("#{APP_ROOT}/config/ecommerce_config.yml"))
end
def access_key_login
access_key = params[:access_key]
unless access_key
show_error("missing access_key parameter")
return false
end
access_info = get_yaml_params
unless client_login = access_info[access_key]
show_error("invalid access_key")
return false
end
status = ActiveRecord::Base.establish_connection(
:adapter => "mysql2",
:host => client_login["host"],
:username => client_login["username"],
:password => client_login["password"],
:database => client_login["database"]
)
end
def generate_json (columns, footer)
// config/application.rb includes the line
// require 'json'
query = "select"
columns.each do |column, name|
query += " #{column}"
query += " AS #{name}" unless column == name
query += ","
end
query = query[0..-2] # trim ','
query += " #{footer}"
dbh = ActiveRecord::Base.connection
results = dbh.select_all(query).to_hash
data = results.map do |result|
columns.map {|column, name| result[name]}
end
({"fields" => columns.values, "values" => data}).to_json
end
def show_error(msg)
render(:text => "Error: #{msg}\n")
nil
end
end
И пример контроллера, который его использует
class CategoriesController < ApplicationController
def index
access_key_login or return
columns = {
"prd_type" => "prd_type",
"prd_type_description" => "description"
}
footer = "from cin_desc;"
json = generate_json(columns, footer)
render(:json => json)
end
end
Это почти все, что касается пользовательского кода. Я не могу найти ничего, что делает это не безопасным для потоков, поэтому я не знаю, в чем причина segfaults. Я не знаю, почему не все рабочие раскручиваются, когда поступают запросы. Я не знаю, какая ошибка возвращается в ApacheBench. Спасибо за помощь, я могу разместить больше информации, как вам нужно.
1 ответ
Похоже, что стабильная версия библиотеки mysql2, 0.3.17, НЕ является поточно-ориентированной. До тех пор, пока он не будет обновлен для обеспечения многопоточности, его использование с многопоточной пумой будет невозможно. Альтернативой было бы использовать Unicorn.