Простое приложение rails на Puma выдает segfault, не может обрабатывать параллелизм

У меня есть довольно простое приложение 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. Спасибо за помощь, я могу опубликовать больше информации, как вам это нужно.


person Devon Parsons    schedule 24.11.2014    source источник
comment
Достаточно ли велик ваш пул базы данных Rails? Посмотрите в config/database.yml   -  person Graham Savage    schedule 24.11.2014
comment
@GrahamSavage Я не уверен. Сейчас установлено значение 5 - это максимальное количество одновременных подключений к MySQL? Я проверю это на более высоком числе. Кроме того, вы знаете, как высоко он может подняться?   -  person Devon Parsons    schedule 24.11.2014
comment
@GrahamSavage Увеличение его до 5000 (с -n 10 -c 5) все еще не удалось. Я заметил еще одну строку в огромной обратной трассе, которая может быть полезна для устранения ошибки. См. редактирование.   -  person Devon Parsons    schedule 24.11.2014
comment
Segfault означает либо ошибку в Ruby, либо расширение C. Попробуйте обновить оба до последнего доступного патча. 2.0.0 сейчас на p594.   -  person Chris Heald    schedule 24.11.2014
comment
Я второй @ChrisHeald, но я бы пошел дальше и посоветовал обновиться до Ruby 2.1.3, если в вашем приложении ничего не зависит от более старого Ruby. У меня были всевозможные проблемы с Ruby 2.0 и различными зависимостями, последней из которых был openssl после обновления до Yosemite.   -  person notaceo    schedule 24.11.2014
comment
Извиняюсь, раньше я начинал с версии 2.1.4, и это была та же ошибка. Супервайзер поручил мне попробовать версию 2.0.0, и она так и не была изменена. Сейчас перехожу на 2.1.3, буду обновлять.   -  person Devon Parsons    schedule 24.11.2014
comment
@ChrisHeald Обновление до 2.1.3 не устранило ошибку сегментации - я думаю, что есть проблема с моими библиотеками mysql2, судя по сообщению об ошибке. попробую переустановить кое-что   -  person Devon Parsons    schedule 24.11.2014


Ответы (1)


Похоже, что стабильная версия библиотеки mysql2, 0.3.17, НЕ является потокобезопасной. Пока он не будет обновлен для обеспечения многопоточности, использование его с многопоточной пумой будет невозможно. Альтернативой может быть использование Unicorn.

person Devon Parsons    schedule 25.11.2014