I have an API setup written in FastAPI and I am hitting 1 endpoint using multiprocessing via 5 processes. Here is the reproducible setup:
API SETUP (please note this is NOT AN ASYNC API -> nothing is being awaited)
import uvicorn
from fastapi import FastAPI
import PIL.Image as Image
import requests
from loguru import logger
import sys
log_format = "{level} {process}-{thread} {time} {name}:{line} - {message}"
logger.remove()
logger.add(sys.stderr, format=log_format, backtrace=True, diagnose=True)
logger.add("logs/" + "t_{time}.log", format=log_format, colorize=True, backtrace=True, diagnose=True)
Image.MAX_IMAGE_PIXELS = None
def get_the_image_from_net():
a = requests.get("https://eoimages.gsfc.nasa.gov/images/imagerecords/73000/73751/world.topo.bathy.200407.3x5400x2700.png", verify=False)
return True
app = FastAPI()
@app.get("/expectoPatronum")
async def get_image_of_the_new_world():
"""
Gets Image of the World
"""
logger.info("Received request for getting Image of the world")
image_bytes: bytes = get_the_image_from_net()
logger.info("Image has been downloaded and processed as bytes array")
return True
if __name__ == '__main__':
uvicorn.run("image_downloader_api:app", host="0.0.0.0", port=10009)
How I am hitting this API using multiprocessing:
import time
from multiprocessing import Pool, Manager, Process
import requests
from loguru import logger
import sys
log_format = "{level} {process}-{thread} {time} {name}:{line} - {message}"
logger.remove()
logger.add(sys.stderr, format=log_format, backtrace=True, diagnose=True, enqueue=True)
logger.add("logs/" + "t_{time}.log", format=log_format, colorize=True, backtrace=True, diagnose=True, enqueue=True)
manager = Manager()
process_dictionary = manager.dict()
process_completion_race_list = manager.list()
def test_function(index_iterator: int):
logger.info(f"STARTING REQUEST FOR {index_iterator}")
response = requests.get("http://localhost:10009/expectoPatronum")
logger.info(f"ENDING REQUEST FOR {index_iterator}")
p1 = Process(target=test_function, args=(1,))
p2 = Process(target=test_function, args=(2,))
p3 = Process(target=test_function, args=(3,))
p4 = Process(target=test_function, args=(4,))
p5 = Process(target=test_function, args=(5,))
p1.start()
p2.start()
p3.start()
p4.start()
p5.start()
p1.join()
p2.join()
p3.join()
p4.join()
p5.join()
I have also tried hitting my API with a multiprocessing POOL
pool = Pool(5)
pool.map(test_function, [i for i in range(1,6)])
pool.close()
However, in all cases the problem is the same:
Expectation: Since it is a SYNC API, the first request should be handled by API, return its response, then move on to handling 2nd request, return its response, and so on. Basically, everything should happen sequentially.
Observation and Problem: Only the first request's response takes some x
amount of time to return a response, all other requests return responses at the same time:
LOGS from HITTING API script
INFO 13607-140018755471168 2022-07-13T10:23:16.958178+0530 __main__:20 - STARTING REQUEST FOR 1
INFO 13608-140018755471168 2022-07-13T10:23:16.962500+0530 __main__:20 - STARTING REQUEST FOR 2
INFO 13609-140018755471168 2022-07-13T10:23:16.966071+0530 __main__:20 - STARTING REQUEST FOR 3
INFO 13613-140018755471168 2022-07-13T10:23:16.966112+0530 __main__:20 - STARTING REQUEST FOR 5
INFO 13611-140018755471168 2022-07-13T10:23:16.966230+0530 __main__:20 - STARTING REQUEST FOR 4
INFO 13607-140018755471168 2022-07-13T10:23:31.631877+0530 __main__:26 - ENDING REQUEST FOR 1
INFO 13613-140018755471168 2022-07-13T10:24:24.807898+0530 __main__:26 - ENDING REQUEST FOR 5
INFO 13608-140018755471168 2022-07-13T10:24:24.807911+0530 __main__:26 - ENDING REQUEST FOR 2
INFO 13611-140018755471168 2022-07-13T10:24:24.808056+0530 __main__:26 - ENDING REQUEST FOR 4
INFO 13609-140018755471168 2022-07-13T10:24:24.808055+0530 __main__:26 - ENDING REQUEST FOR 3
As you can see ENDING REQUEST FOR 1
is logged at 10:23:31
which is normal, as 1 request takes about 7-10 seconds. However, all other responses are returned at roughly the same time of 10:24:24
. How is this even possible? Ideally, each subsequent request should have taken 7-10 seconds more.
LOGS from API:
INFO 12950-140191386634048 2022-07-13T10:23:16.966509+0530 image_downloader_api:28 - Received request for getting Image of the world
INFO 12950-140191386634048 2022-07-13T10:23:31.630237+0530 image_downloader_api:30 - Image has been downloaded and processed as bytes array
INFO 12950-140191386634048 2022-07-13T10:23:31.631287+0530 image_downloader_api:28 - Received request for getting Image of the world
INFO 12950-140191386634048 2022-07-13T10:23:53.571931+0530 image_downloader_api:30 - Image has been downloaded and processed as bytes array
INFO 12950-140191386634048 2022-07-13T10:23:53.572622+0530 image_downloader_api:28 - Received request for getting Image of the world
INFO 12950-140191386634048 2022-07-13T10:24:05.753152+0530 image_downloader_api:30 - Image has been downloaded and processed as bytes array
INFO 12950-140191386634048 2022-07-13T10:24:05.753887+0530 image_downloader_api:28 - Received request for getting Image of the world
INFO 12950-140191386634048 2022-07-13T10:24:15.155202+0530 image_downloader_api:30 - Image has been downloaded and processed as bytes array
INFO 12950-140191386634048 2022-07-13T10:24:15.155954+0530 image_downloader_api:28 - Received request for getting Image of the world
INFO 12950-140191386634048 2022-07-13T10:24:24.806016+0530 image_downloader_api:30 - Image has been downloaded and processed as bytes array
These logs prove, that yes requests were being handled one-by-one and only the last request took time till 10:24:24
.