Maison >développement back-end >Tutoriel Python >Pourquoi mon API multithread est-elle toujours lente ?

Pourquoi mon API multithread est-elle toujours lente ?

DDD
DDDoriginal
2024-12-07 20:49:17798parcourir

Why is My Multi-Threaded API Still Slow?

Je suis confronté à un problème avec mon API et j'espère que quelqu'un pourra m'aider. Malgré l'ajout du multi-threading, les gains de performances sont loin de ce à quoi je m'attendais. Idéalement, si un thread prend 1 seconde pour terminer une tâche, alors 10 threads exécutés simultanément devraient également prendre environ 1 seconde (c'est ce que je comprends). Cependant, mes temps de réponse API sont encore très lents.

Le problème

J'utilise FastAPI avec des bibliothèques comme Playwright, MongoDB et ThreadPoolExecutor. L'objectif était d'utiliser le threading pour les tâches liées au processeur et l'attente asynchrone pour les tâches liées aux E/S. Pourtant, mes temps de réponse ne s’améliorent pas comme prévu.

Exemple d'automatisation de livre

Une partie de mon projet consiste à automatiser les requêtes de livres à l'aide de Playwright pour interagir avec une visionneuse EPUB. La fonction suivante utilise Playwright pour ouvrir un navigateur, accéder à la page d'un livre et effectuer des recherches :

from playwright.async_api import async_playwright
import asyncio

async def search_with_playwright(search_text: str, book_id: str):
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page()
        book_id = book_id.replace("-1", "")
        book_url = f"http://localhost:8002/book/{book_id}"
        await page.goto(book_url)
        await page.fill("#searchInput", search_text)
        await page.click("#searchButton")
        await page.wait_for_selector("#searchResults")
        search_results = await page.evaluate('''
            () => {
                let results = [];
                document.querySelectorAll("#searchResults ul li").forEach(item => {
                    let excerptElement = item.querySelector("strong:nth-of-type(1)");
                    let cfiElement = item.querySelector("strong:nth-of-type(2)");

                    if (excerptElement && cfiElement) {
                        let excerpt = excerptElement.nextSibling ? excerptElement.nextSibling.nodeValue.trim() : "";
                        let cfi = cfiElement.nextSibling ? cfiElement.nextSibling.nodeValue.trim() : "";
                        results.push({ excerpt, cfi });
                    }
                });
                return results;
            }
        ''')
        await browser.close()
        return search_results

La fonction ci-dessus est censée être asynchrone pour éviter de bloquer d'autres tâches. Cependant, même avec cette configuration asynchrone, les performances ne sont toujours pas celles attendues.
Remarque : j'ai calculé que le temps nécessaire pour ouvrir un livre et exécuter une requête sur un seul livre est d'environ 0,0028 s

Exemple de refactorisation

J'ai utilisé run_in_executor() pour exécuter des fonctions dans ProcessPoolExecutor, en essayant d'éviter le GIL et de gérer correctement les charges de travail.

async def query_mongo(query: str, id: str):
    query_vector = generate_embedding(query)

    results = db[id].aggregate([
        {
            "$vectorSearch": {
                "queryVector": query_vector,
                "path": "embedding",
                "numCandidates": 2100,
                "limit": 50,
                "index": id
            }
        }
    ])

    # Helper function for processing each document
    def process_document(document):
        try:
            chunk = document["chunk"]
            chapter = document["chapter"]
            number = document["chapter_number"]
            book_id = id

            results = asyncio.run(search_with_playwright(chunk, book_id))
            return {
                "content": chunk,
                "chapter": chapter,
                "number": number,
                "results": results,
            }
        except Exception as e:
            print(f"Error processing document: {e}")
            return None

    # Using ThreadPoolExecutor for concurrency
    all_data = []
    with ThreadPoolExecutor() as executor:
        futures = {executor.submit(process_document, doc): doc for doc in results}

        for future in as_completed(futures):
            try:
                result = future.result()
                if result:  # Append result if it's not None
                    all_data.append(result)
            except Exception as e:
                print(f"Error in future processing: {e}")

    return all_data

Question

Même après ces changements, mon API est toujours lente. Qu'est-ce qui me manque ? Quelqu'un a-t-il rencontré des problèmes similaires avec les configurations GIL, threading ou asynchrone de Python ? Tout conseil serait grandement apprécié !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn