У меня есть довольно простое приложение Rails. Он слушает запросы в форме
example.com/items?access_key=my_secret_key
Мой контроллер приложения просматривает секретный ключ, чтобы определить, какой пользователь делает вызов, ищет его учетные данные в базе данных и подключается к соответствующей базе данных, чтобы получить элементы этого человека.
Однако нам нужно, чтобы это поддерживало несколько запросов одновременно, и Puma кажется всем любимым / самым быстрым сервером для нас. Мы начали сталкиваться с проблемами при тестировании с помощью ApacheBench. К вашему сведению, puma настроена на 3 рабочих потока и минимум = 1, максимум = 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 работал медленно, это... плохо. Но я думаю, что это еще хуже - объем данных недостаточно высок. Когда я запускаю concurrency 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
в другом окне шпатлевки показывает мне, что очень часто (чаще всего я пытаюсь провести бенчмаркинг) только один из трех созданных рабочих puma выполняет какую-либо работу. Редко, все три делают.
Поскольку кажется, что ошибка может быть где-то здесь, я покажу вам свой 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. Спасибо за помощь, я могу опубликовать больше информации, как вам это нужно.