How I Fixed a Critical Memory Leak in My Python Audio App

від

у

Як я виправив критичну витік пам’яті у своєму Python-додатку для аудіо

Я нещодавно зіткнувся з дратівливою проблемою у своєму застосунку перевірки частоти 432 Гц. Користувачі повідомляли про збої під час завантаження більших аудіофайлів (прибл. 20 МБ та більше), а мій хостинг-провайдер (Render) продовжував надсилати попередження про перевищення лімітів пам’яті.

Ось історія того, як я розслідував витік, визначив винуватця та оптимізував бекенд, щоб обробляти великі файли з мінімальним використанням пам’яті.

Проблема: Недостатня пам’ять на великих файлах

Мій бекенд на FastAPI був розроблений для прийому аудіофайлу, аналізу його домінуючої частоти та визначення, чи налаштований він на 432 Гц.

Початкова реалізація виглядала приблизно так:

# 🚫 Проблемний код
@app.post(“/upload”)
async def analyze_audio(file: UploadFile = File(…)):
# 1. Зчитати УСІЙ файл повністю у пам’ять
audio_data = io.BytesIO(await file.read())

# 2. Декодувати УСІ аудіо у PCM-дані
audio = AudioSegment.from_file(audio_data).set_channels(1)
samples = np.array(audio.get_array_of_samples(), dtype=np.float32)

# 3. Виконати FFT…

Чому це не спрацювало

Коли ви завантажуєте MP3 розміром 20 МБ, файл стислий. Коли pydub (підтримується ffmpeg) декодує його у сирі PCM-дані (нестислі аудіосемпли) для аналізу, такий 20-МБ файл легко може перетворитися на сотні мегабайт даних у RAM.

Для сервера на безкоштовному тарифі з 512 МБ або 1 ГБ ОЗП обробка навіть одного чи двох таких запитів одночасно призводить до завершення процесу через OOM Kill.

Рішення: розумна вибірка за допомогою Librosa

Я зрозумів, що щоб знайти «тонування» пісні, не потрібно аналізувати кожну мілісекунду 5-хвилинного треку. Налаштування постійне. Аналіз 60-секундної вибірки за статистикою такий же точний, як аналіз усього годинного матеріалу.

Я перейшов з pydub на librosa, потужну бібліотеку для аудіоаналізу, яка підтримує стрімінг та часткове завантаження.

Виправлення

Ось оптимізований код:

# ✅ Оптимізований код
import librosa

@app.post(“/upload”)
async def analyze_audio(file: UploadFile = File(…)):
# … валідація …

# Завантаження аудіо за допомогою librosa
# duration=60: Завантажуйте лише перші 60 секунд!
# sr=None: Зберегти оригінальну частоту дискретизації
# mono=True: Одноканальне зведення одразу
y, sr = librosa.load(file.file, sr=None, mono=True, duration=60)

# Виконати FFT на цьому меншому фрагменті
fft_vals = rfft(y)
# …

Ключові переваги

Без await file.read(): Ми передаємо файловий об’єкт безпосередньо до librosa, уникаючи дублювання копії в пам’яті Python.

duration=60: Ми суворо обмежуємо декодовану аудіо до 60 секунд. Це обмежує використання пам’яті до фіксованого розміру (приблизно 10–20 МБ ОЗУ для масиву), незалежно від того, чи завантажено файл 20 МБ або 2 ГБ.

Швидша обробка: FFT на 60 сек даних значно швидший, ніж на 5 хвилин.

Перевірка

Я перевірив виправлення, створивши синтетичний WAV-файл тривалістю 120 секунд (приблизно 20 МБ) та надіславши його до нового кінцевого пункту.

Перед цим:

Використання RAM зросло до понад 500 МБ.

Час обробки: близько 5–10 секунд.

Після:

Використання RAM залишалося стабільним/незначним.

Час обробки: менше 1 секунди.

Результат: Все ще точний (правильно виявлено 432 Гц).

Висновок

Якщо ви виконуєте аналіз даних під завантаження користувачів, завжди ставте запитання: «Чи потрібні мені всі дані?»

Збираючи лише те, що потрібно, ми перетворили додаток, схильний до збоїв, на стабільний високопродуктивний сервіс.

Новини Hi-Fi

через DEV Community

25 січня 2026 р. об 10:27

January 25, 2026 at 10:27AM


Коментарі

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *