Search code examples
pythonflaskherokuscikit-learn

Python Flask Web API on Heroku, code works locally but is timing out when deployed


I am working on a Flutter mobile app that is using a ML model in the app. I am sending my files to a Flask API on a Heroku server to extract the features through python and send them back to the app. My model used the StandardScaler library to scale data, so I recently made changes to export the scaler for use in my API as well so the features can be scaled the same way as my dataset. I had a previous version of this API that was working fine, and when I demoed the new version on my local machine it also worked. But for some reason when I make requests from the mobile app to the server it always times out.

The web app itself is running, and there are no errors in the logs either. I post the request to the url https://tunetracer-featureextraction-d0dee5876f1e.herokuapp.com:5000/extract_features'

API Directory

app/
  |-features.py
  |-Procfile
  |-requirements.txt
  |-scaler

Procfile

web: gunicorn --bind 0.0.0.0:$PORT features:app

features.py

from flask import Flask, request, jsonify
import librosa
import numpy as np
import joblib
from sklearn.preprocessing import StandardScaler
import os

app = Flask(__name__)

@app.route('/extract_features', methods=['POST'])
def extract_features():
    # Check if the POST request has the file part
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'})

    file = request.files['file']

    # If user does not select file, browser also
    # submit an empty part without filename
    if file.filename == '':
        return jsonify({'error': 'No selected file'})

    if file:

        try:
            scaler = joblib.load('scaler')
            y, sr = librosa.load(file, mono=True, duration=30)
            chroma_stft = librosa.feature.chroma_stft(y=y, sr=sr)
            rmse = librosa.feature.rms(y=y)
            spec_cent = librosa.feature.spectral_centroid(y=y, sr=sr)
            spec_bw = librosa.feature.spectral_bandwidth(y=y, sr=sr)
            rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)
            zcr = librosa.feature.zero_crossing_rate(y)
            mfcc = librosa.feature.mfcc(y=y, sr=sr)
            list = [[]]
            
            #read features into a list
            list[0] = [np.mean(chroma_stft), np.mean(rmse), np.mean(spec_cent), np.mean(spec_bw), np.mean(rolloff), np.mean(zcr)]
            list[0] += [np.mean(e) for e in mfcc]
            
            #scale the list
            list[0] = np.array(scaler.transform(list), dtype = float)
            
            #change floats to strings
            feature_list = [str(f) for f in list[0][0]]
            
            #send list back to app
            return jsonify({'features': feature_list})
        except Exception as e:
            return jsonify({'error': str(e)})

port = int(os.environ.get("PORT", 5000))
if __name__ == '__main__':
    app.run(debug=True, port=port, host='0.0.0.0')

requirements.txt

Flask==3.0.2
gunicorn==21.2.0
joblib==1.3.2
librosa==0.10.1
numpy==1.26.4
sklearn-preprocessing==0.1.0
StandardScaler==0.5

logs

2024-04-11T18:34:09.432238+00:00 heroku[web.1]: State changed from crashed to starting
2024-04-11T18:34:25.336192+00:00 heroku[web.1]: Starting process with command `gunicorn --bind 0.0.0.0:39275 features:app`
2024-04-11T18:34:26.050154+00:00 app[web.1]: Python buildpack: Detected 512 MB available memory and 8 CPU cores.
2024-04-11T18:34:26.050248+00:00 app[web.1]: Python buildpack: Defaulting WEB_CONCURRENCY to 2 based on the available memory.
2024-04-11T18:34:26.267579+00:00 app[web.1]: [2024-04-11 18:34:26 +0000] [2] [INFO] Starting gunicorn 21.2.0
2024-04-11T18:34:26.267900+00:00 app[web.1]: [2024-04-11 18:34:26 +0000] [2] [INFO] Listening at: http://0.0.0.0:39275 (2)
2024-04-11T18:34:26.267933+00:00 app[web.1]: [2024-04-11 18:34:26 +0000] [2] [INFO] Using worker: sync
2024-04-11T18:34:26.270092+00:00 app[web.1]: [2024-04-11 18:34:26 +0000] [9] [INFO] Booting worker with pid: 9
2024-04-11T18:34:26.352229+00:00 app[web.1]: [2024-04-11 18:34:26 +0000] [10] [INFO] Booting worker with pid: 10
2024-04-11T18:34:26.644297+00:00 heroku[web.1]: State changed from starting to up
2024-04-11T18:34:40.000000+00:00 app[api]: Build succeeded

Solution

  • Your heroku app should be reached without using a port number. Use the URL https://tunetracer-featureextraction-d0dee5876f1e.herokuapp.com/extract_features (without :5000). Heroku does the reverse proxying to the dynamic port it assigns to your app ($PORT). So you can just reach it on standard http(s) ports.