I am using FastAPI as a full stack with Jinja2 template.
The main problem is SQLalchemy and postgres
Here's example of main page
async def read_posts(request: Request, page: int = 1, page_size: int = 12, db: Session = Depends(get_db)):
posts = db.query(models.Post).offset(start).limit(end).all()
return templates.TemplateResponse("index.html", {"request": request, "posts": posts})
I just have a blog with a lot of posts, and speed of loading page is very slow, I think I somehow wrongly build queries to the database, but can't find what I did wrong, it is very simple app.
But the main problem is that website is not able to withstand a load, there's statistics from one of the services to check the load
LOAD STATS
here's the logs of error when there is load
sqlalchemy.exc.OperationalError: (psycopg2.OperationalError) connection to server at "localhost" (::1), port 5432 failed: FATAL: remaining connection slots are reserved for non-replication superuser connections
i find out that is connection leak, but i can't find the source of the problem. I spent 2 days to find the problem and got nothing
I found out the answer
in FastApi you connect to database like this
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./my.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
Most important part is get_db(), so you call this command wherever you access the database and think when your query is ended it will finally close, but it won't happen.
When you return posts to template, as in the example above, there is still connection to database and because of it there's connection overflow.
It won't happen if you use JsonResponse for example, but with TemplateResponse database will still connected and working
Related
From the typeorm docs:
Generally, you call initialize method of the DataSource instance on application bootstrap, and destroy it after you completely finished working with the database. In practice, if you are building a backend for your site and your backend server always stays running - you never destroy a DataSource.
But, for implementing postgres's schema based multitenancy, I'm scoping connections per request, because, each request has to be sent to a different schema. So, in my getConnection option, I'm doing this:
async function getTenantConnection(
tenantName: string,
connectionOptions?: PostgresConnectionOptions,
) {
if (!connectionOptions) {
connectionOptions = baseConnection;
}
const options: PostgresConnectionOptions = {
...connectionOptions,
schema: tenantName,
entities: [__dirname + '/../**/*.entity.js'],
synchronize: false,
};
const dataSource = new DataSource(options);
return await dataSource.initialize();
}
so on each request, I'm doing getTenantConnection which sort of initialized the database. The previously available getConnection() seems deprecated, and now doing stress tests on the app, I'm getting TCP connection issues, which I simply cannot debug:
5;3m[ExceptionsHandler] connect ETIMEDOUT 20.119.245.111:5432
2023-01-31T10:40:40.581303183Z Error: connect ETIMEDOUT 20.119.245.111:5432
2023-01-31T10:40:40.581370686Z at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1278:16)
2023-01-31T10:40:40.581381886Z at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17)
2023-01-31T10:40:40.589395960Z [Nest] 53 - 01/31/2023, 10:40:40 AM ERROR [ExceptionsHandler] connect ETIMEDOUT 20.119.245.111:5432
I'm just speculating that the database pool has something to do with this. I don't understand the code fully, but the source code for typeorm doesn't seem to contain any pooling done in the initialize() method section as well. I had tried to take reference from this article which demonstrates schema based multitenancy in postgres using typeorm , but methods available there aren't available anymore so I had to resolve to using .initialize(). Please let me know how I can go about implementing this.
I have a standard setup of flask_sqlalchemy and an AWS RDS with postgresql.
I initialize the DB adapter the standard way:
from flask_sqlalchemy import SQLAlchemy
from flask import Flask
app = Flask(__name__)
db = SQLAlchemy()
db.init_app(app)
class Example(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(256))
If I reboot the RDS server and a SELECT starts during reboot I get:
psycopg2.OperationalError: terminating connection due to administrator
command SSL connection has been closed unexpectedly
The above exception was the direct cause of the following exception:
sqlalchemy.exc.OperationalError: (psycopg2.OperationalError)
terminating connection due to administrator command SSL connection has
been closed unexpectedly
That I understand. The DB is unavailable.
BUT: After that the application cannot recover from the situation because:
sqlalchemy.exc.InvalidRequestError: Can't reconnect until invalid
transaction is rolled back
The above exception was the direct cause of the following exception: sqlalchemy.exc.StatementError:
(sqlalchemy.exc.InvalidRequestError) Can't reconnect until invalid
transaction is rolled back
I understand the application throws exceptions during reboot.
But what puzzles me that it never recovers.
There is no write transaction to the DB! I don't understand what rollback the exception is describing.
Edit:
This is how all commits are implemented:
try:
db.session.commit()
except Exception as e:
db.session.rollback()
But there is no commit triggering the exception. Only SELECTS.
Do this and it should fix it for you session.rollback().
If you want to find this error, wrap it into a try/except-block and perform a session.rollback().
EDIT:
So I test some of the stuff and I believe the reason for the error lies within the try/except block. Ideally the doc says this is how you should be implementing inserts:
Create the Python object
Add it to the session
Commit the session
e.g.
>>> db.session.add(User(name="Flask", email="example#example.com"))
>>> db.session.commit()
Likewise for deletes:
>>> db.session.delete(me)
>>> db.session.commit()
For querying you do not need to commit exclusively, the query attribute in Flask-SQLAlchemy does it for you on the Model class.
so for selecting you just do:
users = User.query.all()
Which in your case is:
something = Example.query.all()
One more thing, can you please clarify or give more info on why you are implementing commits like this:
try:
db.session.commit()
except Exception as e:
db.session.rollback()
PS: I might have misread your last edit, my bad!
When i am trying to connect to my existing database(it is realy exist) i have an ERROR occur:
Here is my code and error:
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:postgres510#localhost/height_collector'
db = SQLAlchemy(app)
class Data(db.Model):
__tablename__ = "data"
id = db.Column(db.Integer, primary_key=True)
email_ = db.Column(db.String(120), unique=True)
height_ = db.Column(db.Integer)
def __init__(self, email_, height_):
self.email_ = email_
self.heigth_ = height_
#app.route("/")
def index():
return render_template("index.html")
#app.route("/success", methods=['post'])
def success():
if request.method == 'POST':
email = request.form["email_name"]
height = request.form["height_name"]
print(email, height)
return render_template("success.html")
if __name__ == '__main__':
app.debug = True
app.run()
and then i have an error
Data base ".." doesn't exist!
Here is a picture of my database
It's hard to be sure, but this is probably related to either network names, postgres server config, or permissions.
You need to go through different possibilities step by step and eliminate them as the cause. You can connect and see the db in pgAdmin, and you can't connect in Flask. Somewhere between these two is a difference which stops it from working.
Double-check that in pgAdmin you can correctly open the database which you see pictured and look at the tables (if any). It could be that pgAdmin is showing this db open but it isn't connectable any more.
Can you make sure that in pgAdmin, you use localhost as the host name of your connection, and not the IP address of the machine or anything else. If this is the problem, you need to look at how postgres is configured, and in particular the listen key in the postgres config. If listen is set to localhost, you should be good.
I don't see where you mentioned that you are using Windows, but another answerer seems to have assumed this, is this the case? Does the command ping localhost in a shell succeed?
Connect in pgAdmin using the exact user and password that you use in your Flask code.
Try to connect in Python, not in Flask. Open a Python shell, import psycopg2 and call psycopg2.connect(host='localhost', user='postgres', ...)
I'm new to python. I have to develop a simple Flask app (in my local Ubuntu 16.4) with PostgreSQL as database.
I install pgadmin, Flask, SQLAlchemy and postgres and also this is my app code:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://dbUserName:userNamePassword#localhost/dbName'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
def __init__(self, username, email):
self.username = username
self.email = email
def __repr__(self):
return '<User %r>' % self.username
#app.route('/')
def index():
return "Hello Flask"
if __name__ == "__main__":
app.run()
Also I create a database and new user in pgAdmin (and replace them with related variable in my code), but when I try to test this code in python shell I found error.
my python code:
from app import db
result:
/home/user/point2map2/venv/lib/python3.5/site-packages/flask_sqlalchemy/__init__.py:839: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning.
'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
Then:
db.create_all()
result:
(psycopg2.OperationalError) FATAL: password authentication failed for user "dbUserName"
FATAL: password authentication failed for user "dbUserName"
after a lot of search in forum I found this guide:
in your pg_hba.conf
# IPv4 local connections:
# TYPE DATABASE USER CIDR-ADDRESS METHOD
host all all 127.0.0.1/32 trust
But its not work for me.
I was stuck with this same error.
The problem for me was that I hadn't set the password for the psql user.
See similar question with answer here:
https://askubuntu.com/questions/413585/postgres-password-authentication-fails
it got solved when I did
ALTER USER db_username PASSWORD 'new_password'
its an old question and i guess its not important to you but for people with same problem in future:
i was stuck too. i found postgres default behavior converts everything to lowercase.[1]
my problem solved when i converted my user to lowercase.
sorry for bad english :(
After some debugging of my sqlalchemy code, I saw that the url that sqlalchemy used was a decoded url-string (at least for postgres). This means that if you have substrings in your connection string such as %34, the sqlalchemy connection string will be 4, as that is the url-decoded string. The solution for this problem is simple: simply replace all occurences of % in the connection string with %25, as that is the url encoding for %. The code for this is simply:
from sqlalchemy import create_engine
connection_string_orig = "postgres://user_with_%34_in_the_string:pw#host:port/db"
connection_string = connection_string_orig.replace("%", "%25")
engine = create_engine(connection_string)
print(engine.url) # should be identical to connection_string_orig
engine.connect()
This probably doesn't solve everyone's problem, but it's nevertheless good to be aware of it.
I use flask-sqlalchemy in my application. DB is postgresql 9.3.
I have simple init of db, model and view:
from config import *
from flask import Flask, request, render_template
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://%s:%s#%s/%s' % (DB_USER, DB_PASSWORD, HOST, DB_NAME)
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
login = db.Column(db.String(255), unique=True, index=True, nullable=False)
db.create_all()
db.session.commit()
#app.route('/users/')
def users():
users = User.query.all()
return '1'
And all works fine. But when happens DB server restarting (sudo service postgresql restart), on first request to the /users/ I obtain sqlalchemy.exc.OperationalError:
OperationalError: (psycopg2.OperationalError) terminating connection due to administrator command
SSL connection has been closed unexpectedly
[SQL: ....
Is there any way to renew connection inside view, or setup flask-sqlalchemy in another way for renew connection automatically?
UPDATE.
I ended up with using clear SQLAlchemy, declaring engine, metadata and db_session for every view, where I critically need it.
It is not solution of question, just a 'hack'.
So question is open. I am sure, It will be nice to find solution for this :)
The SQLAlchemy documentation explains that the default behaviour is to handle disconnects optimistically. Did you try another request - the connection should have re-established itself ? I've just tested this with a Flask/Postgres/Windows project and it works.
In a typical web application using an ORM Session, the above condition would correspond to a single request failing with a 500 error, then the web application continuing normally beyond that. Hence the approach is “optimistic” in that frequent database restarts are not anticipated.
If you want the connection state to be checked prior to a connection attempt you need to write code that handles disconnects pessimistically. The following example code is provided at the documentation:
from sqlalchemy import exc
from sqlalchemy import event
from sqlalchemy.pool import Pool
#event.listens_for(Pool, "checkout")
def ping_connection(dbapi_connection, connection_record, connection_proxy):
cursor = dbapi_connection.cursor()
try:
cursor.execute("SELECT 1")
except:
# optional - dispose the whole pool
# instead of invalidating one at a time
# connection_proxy._pool.dispose()
# raise DisconnectionError - pool will try
# connecting again up to three times before raising.
raise exc.DisconnectionError()
cursor.close()
Here's some screenshots of the event being caught in PyCharm's debugger:
Windows 7 (Postgres 9.4, Flask 0.10.1, SQLAlchemy 1.0.11, Flask-SQLAlchemy 2.1 and psycopg 2.6.1)
On first db request
After db restart
Ubuntu 14.04 (Postgres 9.4, Flask 0.10.1, SQLAlchemy 1.0.8, Flask-SQLAlchemy 2.0 and psycopg 2.5.5)
On first db request
After db restart
In plain SQLAlchemy you can add the pool_pre_ping=True kwarg when calling the create_engine function to fix this issue.
When using Flask-SQLAlchemy you can use the same argument, but you need to pass it as a dict in the engine_options kwarg:
app.db = SQLAlchemy(app, engine_options={"pool_pre_ping": True})