Meningkatkan performa throughput dari aplikasi Python di Azure Functions

Saat mengembangkan untuk Azure Functions menggunakan Python, Anda perlu memahami performa fungsi Anda dan bagaimana performa tersebut memengaruhi cara aplikasi fungsi Anda diskalakan. Kebutuhanini lebih penting ketika merancang aplikasi dengan performa tinggi. Faktor utama yang perlu dipertimbangkan saat merancang, menulis, dan mengonfigurasi aplikasi fungsi Anda adalah penskalaan horizontal dan konfigurasi performa throughput.

Penskalaan horizontal

Secara default, Azure Functions secara otomatis memantau beban pada aplikasi Anda dan membuat lebih banyak instans host untuk Python sesuai kebutuhan. Azure Functions menggunakan ambang bawaan untuk berbagai jenis pemicu agar dapat memutuskan kapan harus menambahkan instans, seperti usia pesan dan ukuran antrean untuk QueueTrigger. Ambang ini tidak dapat dikonfigurasi pengguna. Untuk informasi selengkapnya, lihat Penskalaan yang digerakkan kejadian di Azure Functions.

Meningkatkan performa throughput

Konfigurasi default cocok untuk sebagian besar aplikasi Azure Functions. Namun, Anda dapat meningkatkan performa throughput aplikasi dengan menggunakan konfigurasi berdasarkan profil beban kerja Anda. Langkah pertama adalah memahami jenis beban kerja yang Anda jalankan.

Tipe beban kerja Karakteristik aplikasi fungsi Contoh
Terikat I/O • Aplikasi perlu menangani banyak permintaan bersamaan.
• Aplikasi memproses sejumlah besar kejadian I/O, seperti panggilan jaringan dan diska baca/tulis.
• API Web
Terikat CPU • Aplikasi menjalakan komputasi jangka panjang, seperti mengubah ukuran gambar.
• Aplikasi melakukan transformasi data.
• Pemrosesan data
• Inferensi pembelajaran mesin

Karena beban kerja fungsi yang sesungguhnya biasanya merupakan campuran I/O dan CPU yang terikat, Anda harus membuat profil aplikasi di bawah beban produksi yang realistis.

Konfigurasi khusus Performa

Setelah Anda memahami profil beban kerja aplikasi fungsi Anda, berikut ini adalah konfigurasi yang dapat Anda gunakan untuk meningkatkan performa throughput fungsi Anda.

Asinkron

Karena Python adalah runtime rangkaian tunggal,sebuah instans host untuk Python hanya dapat memproses satu permintaan fungsi pada satu waktu secara default. Untuk aplikasi yang memproses sejumlah besar kejadian I/O dan/atau I/O yang terikat, Anda dapat melakukan peningkatan performa yang lebih baik dengan menjalankan fungsi secara asinkron.

Untuk menjalankan fungsi secara asinkron, gunakan pernyataan async def, yang menjalankan fungsi dengan asyncio secara langsung:

async def main():
    await some_nonblocking_socket_io_op()

Berikut adalah contoh fungsi dengan pemicu HTTP yang menggunakan klien http aiohttp :

import aiohttp

import azure.functions as func

async def main(req: func.HttpRequest) -> func.HttpResponse:
    async with aiohttp.ClientSession() as client:
        async with client.get("PUT_YOUR_URL_HERE") as response:
            return func.HttpResponse(await response.text())

    return func.HttpResponse(body='NotFound', status_code=404)

Fungsi tanpa async kata kunci dijalankan secara otomatis di kumpulan utas ThreadPoolExecutor:

# Runs in a ThreadPoolExecutor threadpool. Number of threads is defined by PYTHON_THREADPOOL_THREAD_COUNT. 
# The example is intended to show how default synchronous functions are handled.

def main():
    some_blocking_socket_io()

Untuk mencapai manfaat penuh dari berjalannya fungsi secara asinkron, operasi/pustaka I/O/ yang digunakan dalam kode Anda perlu memiliki asinkron yang diimplementasikan juga. Menggunakan operasi I/O sinkron dalam fungsi yang didefinisikan sebagai asinkron dapat menganggu keseluruhan performa. Jika pustaka yang Anda gunakan tidak memiliki versi asinkron yang diterapkan, Anda mungkin masih mendapat manfaat dari menjalankan kode secara asinkron dengan mengelola perulangan peristiwa di aplikasi Anda.

Berikut adalah beberapa contoh pustaka klien yang telah menerapkan pola asinkron:

  • aiohttp - Klien http/server untuk asyncio
  • Stream API - Primitif asinkron tingkat tinggi/siap menunggu untuk bekerja dengan koneksi jaringan
  • Antrean Janus - Rangkaian antrean asyncio yang aman untuk Python
  • pyzmq - Pengikatan data Python untuk ZeroMQ
Memahami asinkron pada pekerja Python

Saat Anda menentukan async di depan tanda tangan fungsi, Python menandai fungsi sebagai coroutine. Saat Anda memanggil coroutine, coroutine dapat dijadwalkan sebagai tugas ke dalam perulangan peristiwa. Ketika Anda memanggil await dalam fungsi asinkron, ia mendaftarkan kelanjutan ke dalam perulangan peristiwa, yang memungkinkan perulangan peristiwa untuk memproses tugas berikutnya selama waktu tunggu.

Dalam Python Worker kami, pekerja berbagi perulangan peristiwa dengan fungsi pelanggan async dan mampu menangani beberapa permintaan secara bersamaan. Kami sangat mendorong pelanggan kami untuk menggunakan pustaka yang kompatibel dengan asinkron, seperti aiohttp dan pyzmq. Mengikuti rekomendasi ini meningkatkan throughput fungsi Anda dibandingkan dengan pustaka tersebut saat diimplementasikan secara sinkron.

Catatan

Jika fungsi Anda dinyatakan sebagai async tanpa apa pun await di dalam implementasinya, performa fungsi Anda akan sangat terpengaruh karena perulangan peristiwa akan diblokir yang melarang pekerja Python menangani permintaan bersamaan.

Gunakan proses pekerja multibahasa pemrogram

Secara default, setiap instans host Functions memiliki proses pekerja bahasa tunggal. Anda dapat meningkatkan jumlah proses pekerja per host (hingga 10) dengan menggunakan FUNCTIONS_WORKER_PROCESS_COUNT pengaturan aplikasi. Azure Functions kemudian mencoba mendistribusikan permintaan fungsi simultan secara merata ke seluruh pekerja.

Untuk aplikasi terikat CPU, Anda harus mengatur jumlah pekerja bahasa agar sama dengan atau lebih tinggi dari jumlah inti yang tersedia per aplikasi fungsi. Untuk mempelajari lebih lanjut, lihat SKU instans yang tersedia.

Aplikasi terikat I/O juga dapat memperoleh manfaat dari proses peningkatan jumlah pekerja di luar jumlah inti yang tersedia. Perlu diingat bahwa mengatur jumlah pekerja yang terlalu tinggi dapat memengaruhi performa keseluruhan karena peningkatan jumlah pengalihan konteks yang diperlukan.

Berlaku FUNCTIONS_WORKER_PROCESS_COUNT untuk setiap host yang dibuat Azure Functions saat menskalakan aplikasi Anda untuk memenuhi permintaan.

Siapkan para pekerja dalam proses pekerja bahasa pemrogram secara maksimal

Seperti telah disebutkan di bagian asinkron, pekerja bahasa pemrogram Python memperlakukan fungsi dan coroutine secara berbeda. Coroutine dijalankan dalam perulangan kejadian yang sama dengan yang dijalankan pekerja bahasa pemrogram. Di sisi lain, pemanggilan fungsi dijalankan dalam ThreadPoolExecutor, yang dikelola oleh pekerja bahasa sebagai utas.

Anda dapat mengatur nilai pekerja maksimum yang diizinkan untuk menjalankan fungsi sinkronisasi menggunakan pengaturan aplikasi PYTHON_THREADPOOL_THREAD_COUNT. Nilai ini menetapkan argumen max_worker objek ThreadPoolExecutor, yang memungkinkan Python menggunakan kumpulan rangkaian max_worker terbanyak untuk mengeksekusi panggilan secara asinkron. PYTHON_THREADPOOL_THREAD_COUNT berlaku untuk setiap pekerja yang dibuat host Functions, dan Python memutuskan kapan harus membuat rangkaian baru atau menggunakan kembali rangkaian diam yang ada. Untuk versi Python yang lebih lama (yaitu, 3.8, 3.7, dan 3.6), max_worker nilai disetel ke 1. Untuk Python versi 3.9, max_worker diatur ke None.

Untuk aplikasi terikat CPU, Anda harus mempertahankan pengaturan ke angka rendah, mulai dari 1 dan meningkat saat Anda bereksperimen dengan beban kerja Anda. Saran ini adalah untuk mengurangi waktu yang dihabiskan untuk pengalihan konteks dan memungkinkan tugas terikat CPU selesai.

Untuk aplikasi terikat I/O, Anda akan melihat keuntungan besar dengan meningkatkan jumlah rangkaian yang bekerja pada setiap permintaan. Rekomendasinya adalah memulai dengan default Python (jumlah inti) + 4 lalu mengubah berdasarkan nilai throughput yang Anda lihat.

Untuk aplikasi beban kerja campuran, Anda harus menyeimbangkan konfigurasi FUNCTIONS_WORKER_PROCESS_COUNT dan PYTHON_THREADPOOL_THREAD_COUNT untuk memaksimalkan throughput. Untuk memahami apa yang paling sering dihabiskan oleh aplikasi fungsi Anda, sebaiknya profilkan dan atur nilai sesuai dengan perilakunya. Untuk mempelajari tentang pengaturan aplikasi ini, lihat Menggunakan beberapa proses pekerja.

Catatan

Meskipun rekomendasi ini berlaku untuk fungsi yang dipicu http dan non-HTTP, Anda mungkin perlu menyesuaikan konfigurasi pemicu khusus lainnya untuk fungsi yang dipicu non-HTTP guna mendapatkan performa yang diharapkan dari aplikasi fungsi Anda. Untuk informasi selengkapnya tentang ini, silakan lihat Praktik terbaik ini untuk Azure Functions yang andal.

Mengelola perulangan kejadian

Anda harus menggunakan pustaka pihak ketiga yang kompatibel dengan asyncio. Jika tidak ada pustaka pihak ketiga yang memenuhi kebutuhan Anda, Anda juga dapat mengelola perulangan kejadian di Azure Functions. Mengelola perulangan kejadian memberi Anda lebih banyak fleksibilitas dalam manajemen sumber daya komputasi, dan juga memungkinkan untuk menggabungkan pustaka I/O yang sinkron ke dalam coroutine.

Ada banyak dokumen resmi Python yang berguna yang membahas Coroutines dan Tasks dan Event Loop dengan menggunakan pustaka asinkron bawaan.

Lihat pustaka permintaan berikut sebagai contoh, cuplikan kode ini menggunakan pustakaasyncio untuk mengemas metode requests.get() ke dalam coroutine, menjalankan beberapa permintaan web untuk SAMPLE_URL secara serentak.

import asyncio
import json
import logging

import azure.functions as func
from time import time
from requests import get, Response


async def invoke_get_request(eventloop: asyncio.AbstractEventLoop) -> Response:
    # Wrap requests.get function into a coroutine
    single_result = await eventloop.run_in_executor(
        None,  # using the default executor
        get,  # each task call invoke_get_request
        'SAMPLE_URL'  # the url to be passed into the requests.get function
    )
    return single_result

async def main(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    eventloop = asyncio.get_event_loop()

    # Create 10 tasks for requests.get synchronous call
    tasks = [
        asyncio.create_task(
            invoke_get_request(eventloop)
        ) for _ in range(10)
    ]

    done_tasks, _ = await asyncio.wait(tasks)
    status_codes = [d.result().status_code for d in done_tasks]

    return func.HttpResponse(body=json.dumps(status_codes),
                             mimetype='application/json')

Penskalaan Vertikal

Anda mungkin bisa mendapatkan lebih banyak unit pemrosesan, terutama dalam operasi terikat CPU, dengan meningkatkan ke paket premium dengan spesifikasi yang lebih tinggi. Dengan unit pemrosesan yang lebih tinggi, Anda dapat menyesuaikan jumlah jumlah proses pekerja sesuai dengan jumlah inti yang tersedia dan mencapai tingkat paralelisme yang lebih tinggi.

Langkah berikutnya

Untuk informasi selengkapnya tentang pengembangan Azure Functions Phyton, lihat referensi berikut ini: