0

I have implemented a cloud function that accesses a postgres DB per the documentation like this...

    import java.util.Properties
    import javax.sql.DataSource
    import com.zaxxer.hikari.HikariConfig
    import com.zaxxer.hikari.HikariDataSource
    import io.github.cdimascio.dotenv.Dotenv
    
    import java.sql.Connection
    
    class CoreDataSource {
      def getConnection = {
        println("Getting the connection")
        CoreDataSource.getConnection
      }
    }
    object CoreDataSource {
      var pool : Option[DataSource] = None
      def getConnection: Option[Connection] = {
        if(pool.isEmpty) {
          println("Getting the datasource")
          pool = getDataSource
        }
        if(pool.isEmpty){
          None
        } else {
          println("Reusing the connection")
          Some(pool.get.getConnection)
        }
      }
      def getDataSource: Option[DataSource] = {
        Class.forName("org.postgresql.Driver")
        var dbName,dbUser,dbPassword,dbUseIAM,ssoMode, instanceConnectionName = ""
        val dotenv = Dotenv
          .configure()
          .ignoreIfMissing()
          .load()
        dbName = dotenv.get("DB_NAME")
        println("DB Name "+ dbName)
        dbUser= dotenv.get("DB_USER")
        println("DB User "+ dbUser)
        dbPassword = Option(
          dotenv.get("DB_PASS")
        ).getOrElse("ignored")
    
        dbUseIAM = Option(
          dotenv.get("DB_IAM")
        ).getOrElse("true")
        println("dbUseIAM "+ dbUseIAM)
        ssoMode = Option(
          dotenv.get("DB_SSL")
        ).getOrElse("disable") // TODO: Should this be enabled by default?
        println("ssoMode "+ ssoMode)
        instanceConnectionName = dotenv.get("DB_INSTANCE")
        println("instanceConnectionName "+ instanceConnectionName)
        val jdbcURL: String = String.format("jdbc:postgresql:///%s", dbName)
        val connProps = new Properties
        connProps.setProperty("user", dbUser)
        // Note: a non-empty string value for the password property must be set. While this property will be ignored when connecting with the Cloud SQL Connector using IAM auth, leaving it empty will cause driver-level validations to fail.
        if( dbUseIAM.equals("true") ){
          println("Using IAM password is ignored")
          connProps.setProperty("password", "ignored")
        } else {
          println("Using manual, password must be provided")
          connProps.setProperty("password", dbPassword)
        }
        connProps.setProperty("sslmode", ssoMode)
        connProps.setProperty("socketFactory", "com.google.cloud.sql.postgres.SocketFactory")
        connProps.setProperty("cloudSqlInstance", instanceConnectionName)
        connProps.setProperty("enableIamAuth", dbUseIAM)
        // Initialize connection pool
        val config = new HikariConfig
        config.setJdbcUrl(jdbcURL)
        config.setDataSourceProperties(connProps)
        config.setMaximumPoolSize(10)
        config.setMinimumIdle(4)
        config.addDataSourceProperty("ipTypes", "PUBLIC,PRIVATE") // TODO: Make configureable
        println("Config created")
        val pool : DataSource = new HikariDataSource(config) // Do we really need Hikari here if it doesn't need pooling?
        println("Returning the datasource")
        Some(pool)
      }
    }

    class DoSomething() {
      val ds = new CoreDataSource
      def getUserInformation(): String = {
        println("Getting user information")
        connOpt = ds.getConnection
        if(connOpt.isEmpty) throw new Error("No Connection Found")
        ...
      }
    }

    class SomeClass extends HttpFunction {
      override def service(httpRequest: HttpRequest, httpResponse: HttpResponse): Unit = {
        httpResponse.setContentType("application/json")
          httpResponse.getWriter.write(
            GetCorporateInformation.corp.getUserInformation( )
          )
      }
    }
    object GetCorporateInformation {
      val corp = new CorporateInformation()
    }

And I deploy like this...

gcloud functions deploy identity-corporate --entry-point ... --min-instances 2 --runtime java17 --trigger-http --no-allow-unauthenticated --set-secrets '...'

But when first deployed (and after sitting idle for a while) the function takes 25 secs to return causing all kinds of issues with SLAs. After the "cold start" it returns quickly but at least in dev I can't really make sure someone is always hitting it.

Is there a way to mitigate this or do I need to use a VM to make sure it isn't destroyed? Or is there a way to do this without the overhead of pooling?

Jackie
  • 21,969
  • 32
  • 147
  • 289
  • Short answer: you don't have 100% cold start behavior control. Slightly longer answer: you can mitigate it with minInstances, but that still won't eliminate it. The language/runtime doesn't really matter, though JVM instances are going to feel it worse.. This is just the way that serveless systems work. If this isn't acceptable, use dedicated servers instead. – Doug Stevenson Jul 21 '22 at 03:47

1 Answers1

0

Since functions are stateless, your function sometimes initializes the execution environment from scratch, which is called a cold start. However, you can minimize the impact of cold start by setting a minimum number of instances (Note that this can help reduce but not eliminate) or you could create a scheduled function warmer that runs every few minutes and calls your high priority function ensuring they are kept warm.

Catherine O
  • 943
  • 1
  • 9