Eigenartiges Mongrel Cluster Problem

Hallo Leute,

ich habe ein eigenartiges Problem mit einem Projekt, dass noch auf
Rails 1.1.6 läuft, Ruby 1.8.5, Mongrel 1.1.5 nutzt. Apache dient als
Balancer und ist so konfiguriert, dass er bis zu 8 Mongrelinstanzen
versorgen kann. Der Mongrel Cluster ist auch korrekt konfiguriert, von
8000 bis 8007 lauschen die Jungs tatsächlich.

Starte ich ‘ab’ um zu testen: ab -n 1000 -c 8

und schaue mit top die Prozessliste auf dem Server an, rennen da
erwartungsgemäß 8 Instanzen von mongrel_rails und fackeln die 1000
Requests erstaunlich schnell in 54 Sekunden ab. So weit, so gut.

Jetzt habe ich im Backend des Projekts eine Action eingebaut, die,
wenn sie aufgerufen wird, ein net/http Object instanziert, dieses
für einige Abfragen im Hintergrund nutzt, und erst dann returniert, wenn
alle Requests abgeschlossen und das http Object destroyed wurden. Ein
Aufruf dieser Action kann bis zu 90 Sekunden dauern.

Hier kommt jetzt das Problem ins Spiel. Rufe ich diese Action von
meinem eigenen Host auf, können andere von aussen keine Seiten des
Projektes mehr abrufen. Es wird auch keine weitere Mongrel Instanz
hervorgeholt, solange mein Request läuft. Rufe ich den Indianer
Benchmarker wie oben auf, gibt dieser auch nach einigen
Requestversuchen mit Timeout auf.

Ich habe dafür i.A. keine Erklärung. Vorallem weil die besagte Action
wirklich nichts besonderes tut, HTTP:NET instanzieren, ein paar GET
Requests absetzten, Daten mit RegEx’es parsen und ausspucken.

Hat jemand von Euch eine Erklärung dafür? Vielleicht habe ich auch nur
einen Denkfehler.

GrüßeJan
jan [ a t ] roesner [d o t]
it_______________________________________________
rubyonrails-ug mailing list
[email protected]
http://mailman.headflash.com/mailman/listinfo/rubyonrails-ug

Hi Jan,
vielleicht kannst Du den net/http-Aufruf einmal darstellen.

Du schreibst, dass das net/http Object EINIGE Anfragen im Hintergrund
absetzt. Sind dies mehr als acht Anfragen? Setze doch mal jeweis eine
Anfrage ab und schließe den request dann einmal korrekt. Vielleicht
bleibt
der geöffnete request “offen” bis alle Anfragen beendet sind und
blockiert
solange auch den mongrel. Meine Vermutung ist, dass sehr schnelle alle
mongrel-instanzen belegt sind bis das net/http-Objekt geschlossen wird.

Gruß

Jan

Am 24. August 2008 12:39 schrieb Jan R. [email protected]:

und schaue mit top die Prozessliste auf dem Server an, rennen da
eigenen Host auf, können andere von aussen keine Seiten des Projektes mehr

Grüße
Jan
jan [ a t ] roesner [d o t]
it_______________________________________________
rubyonrails-ug mailing list
[email protected]
http://mailman.headflash.com/mailman/listinfo/rubyonrails-ug


Jan P.
Rechtsanwalt

Babendiekstraße 60 B
22587 Hamburg
Tel +49 (0)40 41265809 Fax +49 (0)40 380178-73022
Mobil +49 (0)171 3516667

Ok. Wegen meiner vorherigen Vermutung noch eine Frage: Ist es so, dass
der
Webservice, den Du ansprichst, ebenfalls auf Deinem localhost läuft? So
hatte ich Deine erste Schilderung vestanden. Dass Du mittels eines
net/http-objektes ebenfalls Deine eigenen mongrels ansprichst. Sicher,
dass
macht auf den ersten Blick keinen Sinn, aber - das wissen wir alle -
manchmal geht man seltsame Wege. Dann hätte es nämlich so sein können,
dass
Deine requests aus dem net/http-Objekt Deine eigenen mongrels
blockieren.
Was passiert denn im Log? Wird da wirklich nur einer der acht mongrels
von
dem net/http-Kram blockiert? Wenn das so ist kann der Fehler ja
eigentlich
nur im Load-Balancing liegen, ansonsten müssten die anderen mongrels
requests beantworten.

Gruß

Jan

Am 25. August 2008 14:05 schrieb Jan R. [email protected]:

der geöffnete request “offen” bis alle Anfragen beendet sind und

Benutzername und Passwort wird nun nach dem Aufruf samt Service ID

end
Daten zum anzusprechenden WebService enthält
# dann die eigentliche Action am WebService
logger.warn(“!!! do_stuff_with returned with errors”)
Und ein einfaches Object sieht in etwa so aus, auch dirty, aber die
@PASSWORD = password
@success = true
“username=#{ERB::Util.url_encode(@USERNAME)}&password=#{ERB::Util.url_encode(@PASSWORD)}”
data.each_line do |line|
return(@success)
‘Cookie’ => @COOKIE,
end
Vorallem weil es ja server side kreiert wird. ps zeigt ja auch keine


rubyonrails-ug mailing list
[email protected]
http://mailman.headflash.com/mailman/listinfo/rubyonrails-ug


Jan P.
Rechtsanwalt

Babendiekstraße 60 B
22587 Hamburg
Tel +49 (0)40 41265809 Fax +49 (0)40 380178-73022
Mobil +49 (0)171 3516667

Hallo Jan,

Jan P. schrieb:

Hi Jan,
vielleicht kannst Du den net/http-Aufruf einmal darstellen.

Du schreibst, dass das net/http Object EINIGE Anfragen im Hintergrund
absetzt. Sind dies mehr als acht Anfragen? Setze doch mal jeweis eine
Anfrage ab und schließe den request dann einmal korrekt. Vielleicht bleibt
der geöffnete request “offen” bis alle Anfragen beendet sind und blockiert
solange auch den mongrel. Meine Vermutung ist, dass sehr schnelle alle
mongrel-instanzen belegt sind bis das net/http-Objekt geschlossen wird.

Also wenn ich die Action aufrufe, wird tatsächlich immer nur ein
net/http Object instanziert. Die Funktionen dazu sehen ungefähr so aus:

es gibt Users mit Benutzername und Passwort für je einen WebService

weiterhin gibt es den Table UserConditions mit User has many

UserConditions

Benutzername und Passwort wird nun nach dem Aufruf samt Service ID

an eine protected Action übergeben

def do_my_very_important_stuff
@user_condition = UserCondition.find(params[:id])

@user_condition.status = get_query_result(@user_condition.user.name,

@user_condition.user.password, @user_condition.service_id)
@user_condition.save!
respond_to do |format|
format.js
end
end

protected Action

def get_query_result(username, password, service_id)
@service = Service.find(service_id)
@item_list = get_item_list(12,18) # returniert eine Liste mit 12 bis
18 items

# holt sich dynamisch eine Instanz vom Service Object, das genauere

Daten zum anzusprechenden WebService enthält
# nicht schön, ich weiss …
eval("@service_runner = #{@service.object.camelize}.new(username,
password, service_id)")
status = nil

# erst der Login am Service
if @service_runner.login
  logger.warn("Successfully logged in")
  @error = false
  for item in @item_list
    # dann die eigentliche Action am WebService
    if not @service_runner.do_stuff_with(item)
      logger.warn("!!! runner for item #{item} returned with an

error, aborting")
@error = true
break
end
end
if not @error
status = ‘do_stuff_with returned successfully’
else
logger.warn("!!! do_stuff_with returned with errors")
status = ‘do_stuff_with failed’
end
else
logger.warn(“Error while logging in”)
status = ‘login failed’
end
return status
end

Und ein einfaches Object sieht in etwa so aus, auch dirty, aber die
Services bieten leider alle kein REST Interface, da wäre das einfacher:

require “erb”
require “net/http”

class ServiceXYZ < ActiveRecord::Base

def initialize(username, password, service_id)
@USERNAME = username
@PASSWORD = password
@SERVICE = Service.find(service_id) # Hier finden sich die ganzen
Daten des eigentlichen WebService, den ich ansprechen will
@USERAGENT = ‘Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE;
rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1’
end

der Login läuft indem ich meisst erst ein GET absetze, dann Cookie

setze und die Benutzerdaten danach POST’e

def login
@success = true
unless @HEADERS.nil?
logger.warn(“You should already be logged in!”)
end
@http = Net::HTTP.new(@SERVICE.domain, 80)
resp, data = @http.get(@SERVICE.login_path_1, {‘User-Agent’ =>
@USERAGENT})
if resp.code == “200”
@COOKIE = resp.response[‘set-cookie’]
data =
“username=#{ERB::Util.url_encode(@USERNAME)}&password=#{ERB::Util.url_encode(@PASSWORD)}”
@HEADERS = {
‘Cookie’ => @COOKIE,
‘Referer’ => 'http://'[email protected][email protected]_path_1,
‘Content-Type’ => ‘application/x-www-form-urlencoded’,
‘User-Agent’ => @USERAGENT
}
resp, data = @http.post(@SERVICE.login_path_1, data, @HEADERS)
if resp.code == “302”
@COOKIE = resp.response[‘set-cookie’]
@logged_in = false
data.each_line do |line|
@logged_in = true if line.match(/my/archive/) # Text Check,
der nur nach positivem Login ok ist
end
@success = false if not @logged_in
else
@success = false
end
else
@success = false
end
return(@success)
end

hier nun die eigentliche Schulball Veranstaltung

def do_stuff_with(content_id)
@success = false
@content = Content.find(content_id)
data = “item%5Bbla%5D=#{ERB::Util.url_encode(@content.bla)}&item%
5Bblu%5D=#{ERB::Util.url_encode(@content.blu)”
@HEADERS = {
‘Cookie’ => @COOKIE,
‘Content-Type’ => ‘application/x-www-form-urlencoded’,
‘User-Agent’ => @USERAGENT
}
resp, data = @http.post(@SERVICE.save_path, data, @HEADERS)
if resp.code == “200”
data.each_line do |line|
@success = true if line.match(/Your\sitem\shas\sbeen\ssaved/)
end
else
@success = false
end
return(@success)
end

end

Was ich nicht tue, ist ein explizites Close auf die Connection. Ich
dachte auch nicht, dass das bei HTTP notwendig sei, vielleicht ein
Fehler. Da man hier aber sieht, dass pro Aufruf der Action wirklich nur
ein Object instanziert wird, kann dieses keine Mongrelinstanzen belegen.
Vorallem weil es ja server side kreiert wird. ps zeigt ja auch keine
weiteren Instanzen, wenn ich diese Action aufrufe.

Bitte nicht spekulieren, was ich hier tue, es ist nicht, wonach es
aussieht ;o)
Und bitte keine bösen Worte, ich weiss, dass der Code 'ne Katastrophe
ist, ist auch nur eine schnelle Idee, pragmatisch umgesetzt.

Grüße
Jan

Hallo Jan!

Jan R. schrieb:

Lustig ist jetzt folgendes. Ich starte einen Request auf meine “böse
Action”. Diese läuft jetzt ca. 90 Sekunden. In diesen 90 Sekunden starte
ich den “ab” nochmal mit den gleichen Parametern. Ergebnis: In der
Prozessliste des Servers ist nur ein mongrel_rails zu sehen - scheinbar
der, der meine böse Action bearbeitet - und “ab” gibt nach kurzem mit
einem Timeout auf.

Blockiert die lang laufende Action eventuell Ressourcen, die von den
anderen Rails-Prozessen benötigt werden?
Ich könnte mir vorstellen, dass die anderen Prozesse auf die DB, eine
Datei (Log?) o.ä. warten, und daher nicht in der Liste der aktiven
Prozesse auftauchen.

Viele
Grüße,Sebastian

Hi Jan,

Ok. Wegen meiner vorherigen Vermutung noch eine Frage: Ist es so, dass
der Webservice, den Du ansprichst, ebenfalls auf Deinem localhost
läuft?

Sorry, hatte ich wahrscheinlich ein wenig umständlich ausgedrückt. Ich
spreche aber tatsächlich andere Dienste draussen an. Nichts auf meinem
eigenen Host.

So hatte ich Deine erste Schilderung vestanden. Dass Du mittels eines
net/http-objektes ebenfalls Deine eigenen mongrels ansprichst. Sicher,
dass macht auf den ersten Blick keinen Sinn, aber - das wissen wir
alle - manchmal geht man seltsame Wege.

Ja :o) … in dem Fall aber wirklich nicht.

Dann hätte es nämlich so sein können, dass Deine requests aus dem
net/http-Objekt Deine eigenen mongrels blockieren.

Dann wäre das aber ziemlich durchsichtig gewesen ;o) …

Was passiert denn im Log? Wird da wirklich nur einer der acht mongrels
von dem net/http-Kram blockiert?

Ja, genau das ist ja auch ok. Natürlich soll dieser eine Request den ich
mit meinem Browser an den Host absetze, dort nur genau einen der
Mongrels beanspruchen. Das tut er auch. Ein mongrel_rails läuft dort,
wenn ich den Request abfeuere, und dies auch solange, bis die
eigentliche Action, die die WebServices anspricht, returniert.

Wenn das so ist kann der Fehler ja eigentlich nur im Load-Balancing
liegen, ansonsten müssten die anderen mongrels requests beantworten.

Genau das habe ich auch gedacht. Ist aber (zumindest) scheinbar nicht
der Fall. Deswegen habe ich ja mal mit dem Apache Benchmarker getestet
und eben 1000 Requests mit 8 gleichzeitigen connections auf die
Frontpage des Projektes abgesetzt.

Wenn ich das tue, sehe ich in der Prozessliste des Servers auch wie
erwartet 8 Instanzen von mongrel_rails. Und die Requests werden auch
einwandfrei und parallel abgearbeitet.

Lustig ist jetzt folgendes. Ich starte einen Request auf meine “böse
Action”. Diese läuft jetzt ca. 90 Sekunden. In diesen 90 Sekunden starte
ich den “ab” nochmal mit den gleichen Parametern. Ergebnis: In der
Prozessliste des Servers ist nur ein mongrel_rails zu sehen - scheinbar
der, der meine böse Action bearbeitet - und “ab” gibt nach kurzem mit
einem Timeout auf.

Noch lustiger ist folgendes. Starte ich kurz vor Ablauf der 90 Sekunden
den “ab”, so bekommt der vom Server keine Responses. Exakt ab dem
Augenblick, in dem meine “böse Action” zurückkommt, rennen auch bei “ab”
die Responses rein, und in der Prozessliste des Servers starten die
restlichen 7 mongrel_rails Instanzen.

Irgendwie scheint es also so zu sein, dass diese “böse Action” dafür
sorgt, dass kein weiterer mongrel_rails starten kann. An der Balancer
Config scheint es also nicht zu liegen.

Nettes Ding oder :o) …

Grüße
Jan R.
jan [ a t ] roesner [ d o t ] it

Hallo Sebastian,

Danke für den Hinweis.

Blockiert die lang laufende Action eventuell Ressourcen, die von den
anderen Rails-Prozessen benötigt werden?

Keine, die mir da einfallen würden.

Ich könnte mir vorstellen, dass die anderen Prozesse auf die DB, eine
Datei (Log?) o.ä. warten, und daher nicht in der Liste der aktiven
Prozesse auftauchen.

Also Files werden nicht geöffnet, abgesehen vom production log, aber das
sollte ja kein Problem darstellen. Die DB ist ein guter Ansatz, aber ich
locke nichts, und die “böse” Action macht auch keine wirren Dinge in der
DB, der Code den ich gepostet habe, ist fast 1:1 der reale Code. Die
Tables, welche hier in dieser Action betroffen sind (und das sind 4
Stück) sind für jedwede andere Actions auch irrelevant, werden sonst
nirgends benutzt.

Ich hatte auch erst daran gedacht, dass die mysql vielleicht irgendwie
nicht mit so vielen mongrels gleichzeitig klarkommt. Aber beim Aufruf
der Frontpage mit dem Apache Benchmarker “ab” 8-fach parallel wird die
DB auch massiv belastet, und da funktioniert es wunderbar.

ich mache ja auch nichts wirklich Dummes. Ich hole mir mein Condition
Object, das ist ein einfaches SELECT. Dann hole ich mir mein net/http
Object, mach an dem meine Aufrufe, und wenn die fertig sind, wird das
Condition Object einfach modifiziert und gespeichert. Da sollte es
keinen Lock geben.

Noch ne Idee?

Grüße
Jan R.
jan [ a t ] roesner [ d o t ] it

Also ich würde da als nächstes zum Debugging mal sehr viel Komplexität
wegnehmen. Also zB das Ableiten des ServiceXYZ vom ActiveRecord::Base
aufheben um ein klareres Bild und weniger potentielle Fehlerquellen zu
bekommen. Das Persistieren des ServiceObjekts in der Datenbank sollte
sicherlich funktionieren aber ich würde zunächst die Abhängigkeiten des
net/http-Objektes zum Rails-Framework soweit wie möglich aufheben
wollen,
wenn ich das zu Debuggen hätte.

Gruß

Jan

Am 25. August 2008 16:25 schrieb Jan R. [email protected]:

Datei (Log?) o.ä. warten, und daher nicht in der Liste der aktiven
Ich hatte auch erst daran gedacht, dass die mysql vielleicht irgendwie

http://mailman.headflash.com/mailman/listinfo/rubyonrails-ug


Jan P.
Rechtsanwalt

Babendiekstraße 60 B
22587 Hamburg
Tel +49 (0)40 41265809 Fax +49 (0)40 380178-73022
Mobil +49 (0)171 3516667