0

I'm using Python script to check if user requested exists. using:

import MySQLdb 
from flask import Flask, request, abort
app = Flask(__name__)
try:
    db = MySQLdb.connect('xxx1','my_username','my_password','my_db_name')
    db1 = MySQLdb.connect('xxx2','my_username','my_password','my_db_name')
    db2=
    db3=
except MySQLdb.OperationalError as e:
    print "Caught an exception : " + str(.message)

@app.route('/')
@app.route('/<path:path>')
def page(path = ''):
user = request.args.get('user', None)
if not mac: 
    abort (403)

cursor = db.cursor()
query = 'Select ID from f_member where Name=%s'
db.commit()
cursor execute(query, (user, ))
row = cursor.fetchone()
cursor.close()

#cursor.db1 here

if row == None and row1 == None:
    abort (403)
return 'OK', 200

if __name__ == '__main__':
    app.run(host=host, port=port)

Then i have 5 nginx servers with this:

location = /auth {
proxy_pass http://xxx.xxx$request_uri;
proxy_pass_request_body off;
proxy_set_header Content_Lenght "";
proxy_set_header X-Real-IP $remote_addr;

So the thing is, this script checks if user is found in one of the databases, if true then access the page. Problem is my user list is now getting up to 5k users. and when i run the .py script it runs so fast (even with errors 403 people who are trying to connect), then broken pipe starts to show up. Seems like it is getting overloaded, is there a better way to handle my script so it runs better and more efficient?

Sami H
  • 51
  • 1
  • 8
  • Can you share the broken pipe error(s)? Everything here looks fine IMO; there are some gaps in the code, but I'm assuming those are from the translation of real code to SO-ready code. The only thing you might want to change is select the count, rather than the ID. – wholevinski May 25 '15 at 16:00
  • Traceback (most recent call last): File Socketserver.py line 295 handle_request_noblock File socketserver.py line 321 in process request File socketserver.py line 334 finish_request file socketserver.py line 710 in _finish socket.py line 303 in flush writeoffset++buffer_size error:Errno 32 Broken Pipe I use this script to handle nginx sended data to view live streams. And first minutes it runs smoothly then totally freeze. It's handling about 4k simultaneous requests 200 and 403. – Sami H May 25 '15 at 16:08
  • Ok...so that exception is caused by the other side of your pipe being closed. We're missing part of the equation here; are you load testing? Are you actually getting 4000 simultaneous requests? Whatever is on the other end making requests is closing. I need more details before I can effectively help... Here's the link explaining more about the socket write failure: http://stackoverflow.com/questions/6063416/python-basehttpserver-how-do-i-catch-trap-broken-pipe-errors What web framework are you using? Can I see more code on how you're handling the requests? – wholevinski May 25 '15 at 16:21
  • @dubhov edited to full code. and actually i have more than 2 db connected. its a total of 4 db. So row == None and - row3 == none – Sami H May 25 '15 at 16:33
  • Got it. So then, the other part of my question: How are you getting 4000 simultaneous requests? The error you're seeing is because the client side has closed its socket. Is it a browser? If so, the browser has probably been closed already, or the request has been interrupted (stop button, new request made, etc). Is it an automation tool you're making 4000 requests with? – wholevinski May 25 '15 at 17:19
  • In short, your approach isn't bad here...you can take time to over-engineer some cache, but to be honest, 5000 rows in a DB is _nothing_. Making that query repeatedly really isn't that bad IMO; you can maybe improve it by making it a `select COUNT(ID)` instead. And the exceptions, I'm pretty sure, are a symptom of your client exiting and can be ignored. – wholevinski May 25 '15 at 17:25
  • @dubhov true, i do have Browsers clients, and as i looked and searched it seemed they closed it. Anyway why the freeze happens? after about 5 minutes, there are no requests being pushed or accepted and no clients have picture(being accepted). and when i press ctrl+c multiple times to end. It starts to roll out the last of the logs and shutdown. Could it be because of ubuntu 14.04? – Sami H May 25 '15 at 17:45
  • So, without anything up front to start multiple app processes (uwsgi, etc), your web app is single threaded. To get a better idea of where the freeze is happening, put some logging of your own in there. Other than that, I don't have any suggestions as to where/why your web server is "freezing"... Also, I don't think you need the db.commit() in there. That's only for statements that update the DB. – wholevinski May 25 '15 at 17:52
  • When i only connected 1 server to the script, it seems to work just fine, this 1 server handles about 500-1000 users. – Sami H May 26 '15 at 10:05

2 Answers2

0

You may use a dictionary for username/id map in your Python program. Basically, when the program starts it will make a query for all the users and populate the map. Afterwards, every 20 seconds or so it will make a query to get the "changes" in f_member to update the dictionary. Lookup for username happens always in this map. If a user name is not found in the map, then only it makes a DB query (and if the user detail is found on DB, update the local map as well). If you don't have millions of users in the table, this approach will work. Otherwise use a LRU cache.

Nipun Talukdar
  • 4,975
  • 6
  • 30
  • 42
  • Appreciate your idea, and sounds logically that it could work, but i have absolutely no idea how to approach this, with my set of skills in python. – Sami H May 25 '15 at 16:07
  • You know how to use the dictionary in Python. Other than that, you will need to use a thread to get the "updates" in f_member table, which run in parallel and make the DB query at certain intervals. You should design your f_member table in such a way that "select username, id from f_member where mod_time > somtime" is possible. – Nipun Talukdar May 25 '15 at 16:12
0

So, after a lengthy comment thread, it appears like your flask instances might be competing for DB resources. There's also another hypothesis that saving your connections off in global scope could have some bad side effects (I could be wrong about this, but I'd be concerned about timeouts, not closing the connections, etc). Here's how I might rewrite it:

import MySQLdb 
from flask import Flask, request, abort
app = Flask(__name__)

def get_db_connection_args():
    try:
        db_args = { 'host':'xxx1', 'user':'my_username', 'passwd':'my_password', 'db':'my_db_name' }
        db1_args = { 'host':'xxx2', 'user':'my_username', 'passwd':'my_password', 'db':'my_db_name' }
        db2_args = { 'host':'xxx3', 'user':'my_username', 'passwd':'my_password', 'db':'my_db_name' }
        db3_args = { 'host':'xxx4', 'user':'my_username', 'passwd':'my_password', 'db':'my_db_name' }
    except MySQLdb.OperationalError as e:
        print "Caught an exception : " + str(.message)
    return (db_args, db1_args, db2_args, db3_args)

@app.route('/')
@app.route('/<path:path>')
def page(path = ''):
    user = request.args.get('user', None)
    #I don't know what mac is...but it was in your original code.
    if not mac:
        abort (403)

    found = False
    db_connection_args = get_db_connection_args()
    for db_connection_arg_dict in db_connection_args:
        if not found:
            db_conn = MySQLdb.connect(**db_connection_arg_dict)
            try:
                cursor = db_conn.cursor()
                cursor.execute('Select ID from f_member where Name=%s', (user, ))
                row = cursor.fetchone()
                if row:
                    found = True
            finally:
                db_conn.close()

    if found:
        return 'OK', 200

    abort (403)

if __name__ == '__main__':
    app.run(host=host, port=port)
wholevinski
  • 3,658
  • 17
  • 23
  • thanks for taking the time to give me this, and it sounds reasonable that it is Flask, causing this with multiple requests competing. But this is giving me error 500, can't find the problem causing it, also i have checked Tornado what you think about it instead of Flask? – Sami H May 26 '15 at 12:44
  • 500 is probably a coding error on my part. Please post the exception and let me know...I whipped it up real quick and posted it. Haven't actually tried the code. – wholevinski May 26 '15 at 13:48
  • Seems like it's where the connection to db is. – Sami H May 26 '15 at 13:49
  • Updated. I had changed a method name and hadn't updated the caller – wholevinski May 26 '15 at 13:51
  • TypeError: Connect() argument after ** must be a mapping, not str – Sami H May 26 '15 at 18:30
  • Oh i noticed, that i get this error only if 1 database in typed in, otherwise code works! time to load up this bad boy, btw i would really appreciate if i can make this code work with 1 database only, because as you know i have 4 databases, but one of them has different columns – Sami H May 26 '15 at 18:56