What happens to an App Engine request if autoscaling can't create a new instance? - google-app-engine

Because of the instance limit. So there is a request, it sits in the queue long enough, but App Engine autoscaling can't start a new instance.
What happens to this request? Is it kept in the queue indefinitely or is it aborted after some time?

It returns a message "Rate exceeded." to the user and the following error in the logs "Request was aborted after waiting too long to attempt to service your request."
Here's how I tested it:
I created a class to count the time elapsed to make sure that I am indeed executing multiple concurrent requests. And a basic Python app that has a sleep function for 20 seconds.
Then in app.yaml I set the max-instances to 1, and max-concurrent requests to 1.
Then by simply opening 5 tabs with the app URL and running them at the same time, at least one of them will fail with the errors mentioned above.
Tested on GAE Standard
timer.py:
import time
class TimerError(Exception):
"""A custom exception used to report errors in use of Timer class"""
class Timer:
def __init__(self):
self._start_time = None
def start(self):
"""Start a new timer"""
if self._start_time is not None:
raise TimerError(f"Timer is running. Use .stop() to stop it")
self._start_time = time.perf_counter()
def stop(self):
"""Stop the timer, and report the elapsed time"""
if self._start_time is None:
raise TimerError(f"Timer is not running. Use .start() to start it")
elapsed_time = time.perf_counter() - self._start_time
self._start_time = None
print(f"Elapsed time: {elapsed_time:0.4f} seconds")
main.py:
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello():
import time
from timer import Timer
t = Timer()
t.start()
print('Started')
time.sleep(20)
t.stop()
return 'Hello World!'
if __name__ == '__main__':
requirements.txt:
Flask==1.1.2
codetiming
app.yaml:
service: scaling
runtime: python37
instance_class: F1
automatic_scaling:
target_cpu_utilization: 0.65
min_instances: 1
max_instances: 1
min_pending_latency: 30ms # default value
max_pending_latency: automatic
max_concurrent_requests: 1
Deploy:
gcloud app deploy
Then: Open 5 tabs with the link of the deployed app at the same time.
Results:
User gets: "Rate exceeded."
GAE logs show: ERROR "Request was aborted after waiting too long to attempt to service your request."

Related

FastAPI with MQTT client deployed to GAE not receive any message after running for a while

I built a FastAPI app to forward MQTT message using paho-mqtt lib from broker to another REST api service.
After deploying the app to google app engine, everything works fine (can normally send/receive message) at the beginning.
But about hours later, my app can still publish messages but not receiving any message.
app.yaml
runtime: python310
entrypoint: gunicorn -b :$PORT -w 1 -k uvicorn.workers.UvicornWorker main:app
...
instance_class: F1
inbound_services:
- warmup
automatic_scaling:
min_instances: 1
max_instances: 1
min_idle_instances: 1
mqtt client wrapper
class MqttClient:
_instance = None
_lock = threading.Lock()
_on_msg_callback: Set[Callable] = set()
_subscribe_topics: Set[str] = set()
_mqtt_client = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def init(self, config: MqttConfig):
broker_host = config.broker_host
random_id = ''.join(random.choices(string.digits, k=6))
client_id = f'mqtt-forwarder-{random_id}'
port = config.port
keepalive = config.keepalive
client = mqtt.Client(client_id=client_id, userdata=self)
client.username_pw_set(
username=config.username,
password=config.password
)
context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2)
client.tls_set_context(context)
# set callbacks
client.on_log = self._on_log
client.on_connect = self._on_connect
client.on_disconnect = self._on_disconnect
client.on_publish = self._on_publish
client.on_subscribe = self._on_subscribe
client.on_message = self._on_message
client.connect(broker_host, port=port, keepalive=keepalive)
client.loop_start()
self._mqtt_client = client
main.py
import...
app = FastAPI()
mqtt_client = MqttClient()
mqtt_client.init(
MqttConfig(
broker_host='...',
port=8883,
keepalive=60,
username=...,
password=...
))
mqtt_client.subscribe('...')
mqtt_client.subscribe('...')
mqtt_client.register_on_message_callback(mqtt_cb.mqtt_on_msg_callback)
#app.get('/_ah/warmup')
def warmup():
return Response('', status_code=status.HTTP_200_OK)
After checking the GAE logging, I found a process is terminated (pid1, 16:23:44.397).
I am not sure if this caused MQTT client receiving no message, and curious about why the process was terminated.
GAE log
My main Question:
How can I fix this issue and keep receiving MQTT messages?
Other Question:
Is it a good idea to deploy a GAE FastAPI to forward MQTT messages to my other services? Any suggestion is appreciated.
Thank you.

Why does dev_appserver.py exit without throwing errors?

I am trying to run a simple python 2 server code with AppEngine and Datastore. When I run dev_appserver.py app.yaml, the program immediately exits (without an error) after the following outputs:
/home/username/google-cloud-sdk/lib/third_party/google/auth/crypt/_cryptography_rsa.py:22: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.
import cryptography.exceptions
INFO 2022-12-20 11:59:41,931 devappserver2.py:239] Using Cloud Datastore Emulator.
We are gradually rolling out the emulator as the default datastore implementation of dev_appserver.
If broken, you can temporarily disable it by --support_datastore_emulator=False
Read the documentation: https://cloud.google.com/appengine/docs/standard/python/tools/migrate-cloud-datastore-emulator
INFO 2022-12-20 11:59:41,936 devappserver2.py:316] Skipping SDK update check.
INFO 2022-12-20 11:59:42,332 datastore_emulator.py:156] Starting Cloud Datastore emulator at: http://localhost:22325
INFO 2022-12-20 11:59:42,981 datastore_emulator.py:162] Cloud Datastore emulator responded after 0.648865 seconds
INFO 2022-12-20 11:59:42,982 <string>:384] Starting API server at: http://localhost:38915
Ideally, it should have continued by runnning the server on port 8000. Also, it works with option --support_datastore_emulator=False.
This is the code:
import webapp2
import datetime
from google.appengine.ext import db, deferred, ndb
import uuid
from base64 import b64decode, b64encode
import logging
class Email(ndb.Model):
email = ndb.StringProperty()
class DB(webapp2.RequestHandler):
def post(self):
try:
mail = Email()
mail.email = 'Test'
mail.put()
except Exception as e:
print(e)
self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
return self.response.out.write(e)
def get(self):
try:
e1 = Email.query()
logging.critical('count is: %s' % e1.count)
e1k = e1.get(keys_only=True)
logging.critical('count 2 is: %s' % e1k.count)
e1 = e1.get()
key = unicode(e1.key.urlsafe())
logging.critical('This is a critical message: %s' % key)
logging.critical('This is a critical message: %s' % e1k)
e2 = ndb.Key(urlsafe=key).get()
self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
return self.response.out.write(str(e2.email))
except Exception as e:
print(e)
self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
return self.response.out.write(e)
app = webapp2.WSGIApplication([
('/', DB)
], debug=True)
How can I find the reason this is not working?
Edit: I figured that the dev server works and writes to a datastore even with support_datastore_emulator=False option. I am confused by this option. I also don't know where the database is stored currently.
It should be count() and not count i.e.
logging.critical('count is: %s' % e1.count())
A get returns only 1 record and so it doesn't make sense to do a count after calling a get. Besides, the count operation is a method of the query instance not the results. This means the following code is incorrect
e1k = e1.get(keys_only=True)
logging.critical('count 2 is: %s' % e1k.count)
You should replace it with
elk = e1.fetch(keys_only=True) # fetch gives an array
logging.critical('count 2 is: %s' % len(e1k))
When you first run your App, it will execute the GET part of your code and because this is the first time your App is being run, you have no record in Datastore. This means e1 = e1.get() will return None and key = unicode(e1.key.urlsafe()) will lead to an error.
You have to modify your code to first check you have a value for e1 or e2 before you attempt to use the keys.
I ran your code with dev_appserver.py and it displayed these errors for me in the logs. But I ran it with an older version of gcloud SDK (Google Cloud SDK 367.0.0). I don't know why yours exited without displaying any errors. Maybe it's due to the version...??
Separately - Don't know why you're importing db. Google moved on to ndb long ago and you don't use db in your code
The default datastore (for the older generation runtimes like Python 2) is in .config (hidden folder) > gcloud > emulators > datastore
You can also specify your own location by using the flag --datastore_path. See documentation

AppEngine Flexible instances constantly respawning

I am deploying a Go application using AppEngine flexible. Below is my app.yaml. Sometimes after I deploy it stabilizes at 1 instance (it's a very low load application), but most of the time it constantly respawns upwards of 6 instances. My logs are filled with messages showing the new instances being created. There is nearly zero load on this application, why is AppEngine flexible constantly destroying and respawning instances?
Log showing constant respawning:
app.yaml
runtime: go
api_version: go1
env: flex
handlers:
- url: /.*
script: _go_app
health_check:
enable_health_check: True
check_interval_sec: 10
timeout_sec: 4
unhealthy_threshold: 2
healthy_threshold: 2
automatic_scaling:
min_num_instances: 1
max_num_instances: 10
cool_down_period_sec: 120 # default value
cpu_utilization:
target_utilization: 0.5
The problem was with my health check function. It originally looked like this:
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
return
}
I then discovered this sentence in the documentation on how instances are managed:
You can write your own custom health-checking code. It should reply to /_ah/health requests with a HTTP status code 200. The response must include a message body, however, the value of the body is ignored (it can be empty).
So I changed the health check function to write a simple "ok" in response:
func healthCheckHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
return
}
The instances now behave according to my autoscale settings! The respawning is gone.
I obviously should have read the documentation closer, but there was zero indication of a problem in the health check logs. All health checks looked like they were passing. Hopefully this info is helpful to others.

AppEngine python's send_email not working anymore

My appengine app has a cron job that calls a url endpoint whose handler uses mail.send_mail from google.appengine.api. This has been working fine for several months so far.
Today, the email never arrived. I wrote some test code to invoke send_mail, but the email does not get sent. I have adhered to the necesarry requirements like sending from a email address of the form anything#appname.appspotmail.com.
The function is not throwing any exception either. The appengine logs note that the url is invoked, but there is no error or exception.
What might be the problem? Thanks.
Editing to add some code as suggested. Note that to actually test this code one'd need an AppEngine App. In that case you'd need to change myApp etc. in the code below to the actual app name that is used.
Looking forward to any help/insights.
from google.appengine.api import mail
class TestEmailHandler(webapp2.RequestHandler):
def get(self):
mySender = "mySender <mySender#myApp.appspotmail.com>"
myTo = "myToAddress#example.com"
mySubject = "Test Subject"
myBody = "Test Body Text"
myHtml = "<html><body>Test body</body></html>"
try:
mail.send_mail(sender=mySender,
to=myTo,
subject=mySubject,
body=myBody,
html=myHtml)
self.response.headers['Content-Type'] = 'text/plain'
self.response.write("Sent email. Body: " + myBody)
except:
self.response.write("Exception. " + sys.exc_info()[0])
application = webapp2.WSGIApplication([
('/', MainPage),
('/test_email', TestEmailHandler)
], debug=True)
My app.yaml looks like this:
application: myApp
version: 2
runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
script: myApp.application
inbound_services:
- mail
I think the problem's now fixed. I associated my credit card by enabling billing and now an email got sent when I tested it. Who would've thought...

Google App Engine: Cron handler is called by cron, but no code is run

I have a following problem. I have defined a cron job in Google App Engine, but my get method is not called (or to be precise it is called every other time - if I run it manually it doesn't do anything at the first time, but at the second it works flawlessly). This is output from logging for the call made by cron:
2011-07-04 11:39:08.500 /suggestions/ 200 489ms 70cpu_ms 0kb AppEngine-Google; (+http://code.google.com/appengine)
0.1.0.1 - - [04/Jul/2011:11:39:08 -0700] "GET /suggestions/ HTTP/1.1" 200 0 - "AppEngine-Google; (+http://code.google.com/appengine)" "bazinga-match.appspot.com" ms=489 cpu_ms=70 api_cpu_ms=0 cpm_usd=0.001975 queue_name=__cron task_name=a449e27ff383de24ff8fc5d5f05f2aae
As you can see it makes GET request on /suggestions/, but nothing happens, including my log messages (they are printed, when I run it second time manually). Do you have any idea, why this might be happening?
My handler:
class SuggestionsHandler(RequestHandler):
def get(self):
logging.debug('Creating suggestions')
for key in db.Query(User, keys_only=True).order('last_suggestion'):
make_suggestion(key)
logging.debug('Done creating suggestions')
print
print('Done creating suggestions')
This is my cron.yaml:
cron:
- description: daily suggestion creation
url: /suggestions/
schedule: every 6 hours
and proper section of my app.yaml:
- url: /suggestions/
script: cron.py
login: admin
You're missing this declaration from the bottom of your handler file, after main:
if __name__ == '__main__':
main()
The first time a handler script is run, App Engine simply imports it, and this snippet runs your main in that situation. On subsequent requests, App Engine runs the main function, if you defined one.

Resources