google-cloud-platformgoogle-cloud-functionsgoogle-cloud-sql

Cloud Function cannot connect to Cloud SQL with "upstream request timeout"


As the title says my Cloud Function is logging "Finished with status: 'error'" with no other logs. I am using 1st gen Cloud Functions with Python 3.10.

Below is my code which I have written based on this notebook: https://colab.research.google.com/github/GoogleCloudPlatform/cloud-sql-python-connector/blob/main/samples/notebooks/postgres_python_connector.ipynb#scrollTo=0CnzkOianTNN

import sqlalchemy
from google.cloud import secretmanager
from google.cloud.sql.connector import Connector, IPTypes

PROJECT_ID = "REDACTED"
connector = Connector()


def getconn():
    conn = connector.connect(
        "REDACTED",
        "pg8000",
        user="REDACTED",
        password="REDACTED",
        db="REDACTED",
        ip_type=IPTypes.PUBLIC,
    )
    return conn


pool = sqlalchemy.create_engine(
    "postgresql+pg8000://",
    creator=getconn,
)


def main(request):
    try:
        max_version = None
        # connect to connection pool
        with pool.connect() as db_conn:
            row = db_conn.execute(
                sqlalchemy.text(
                    "select coalesce(max(version), 0) as max_version from pricings"
                )
            ).fetchone()

            # show results
            max_version = row[0]
        return {"max_version": max_version}
    except Exception as e:
        print(e)
        return "error"

The only logs I see are "Function execution started" and "Function execution took x ms, finished with status error". Not even the exception is printed.

This function works fine on local, but once it is deployed it does not.

Edit: Once I changed the connection method to Unix Socket rather than cloud sql python connector, it now works on GCP. But obviously not on local.


Solution

  • This is due to the nature of Cloud Functions and because the Cloud SQL Python Connector runs background tasks.

    It turns out global variables that run background tasks can cause issues when run out side of the Cloud Function request context (because Cloud Functions only allocates compute during the request). So Cloud Functions recommends lazy initializing this type of global variable so that the variable is initialized within the request context.

    import sqlalchemy
    from google.cloud.sql.connector import Connector, IPTypes
    import pg8000
    
    def connect_to_instance() -> sqlalchemy.engine.base.Engine:
        connector = Connector()
    
        def getconn() -> pg8000.dbapi.Connection:
            return connector.connect(
                "...", # the PostgreSQL's instance connection name here
                "pg8000",
                user     = "xyz",
                password = 'supersecret',
                db       = "db_name",
                ip_type  = IPTypes.PUBLIC
            )
        
        return sqlalchemy.create_engine(
            "postgresql+pg8000://",
            creator      = getconn,
            pool_size    = 5,
            max_overflow = 2,
            pool_timeout = 30,
            pool_recycle = 1800
        )
    
    # lazy initialization of global db
    db = None
    
    def hello_http(request):
        # lazy init within request context
        global db
        if not db:
            db = connect_to_instance()
        with db.connect() as db_conn:
            # ... perform queries
    

    As you mentioned this is also mentioned as part of https://github.com/GoogleCloudPlatform/cloud-sql-python-connector/issues/830