Kami menerbitkan bagian pertama dari terjemahan artikel berikutnya dalam seri tentang cara kerja Instagram dengan Python. Artikel
pertama dalam seri ini berbicara tentang fitur kode server Instagram, bahwa itu adalah monolit yang sering berubah, dan bagaimana alat pengecekan tipe statis membantu mengelola monolit ini.
Materi kedua adalah tentang mengetik API HTTP. Di sini kita akan berbicara tentang pendekatan untuk memecahkan beberapa masalah yang dihadapi Instagram menggunakan Python dalam proyeknya. Penulis materi berharap bahwa pengalaman Instagram akan bermanfaat bagi mereka yang mungkin mengalami masalah serupa.

Tinjauan situasi
Mari kita lihat modul berikut, yang, pada pandangan pertama, terlihat benar-benar tidak bersalah:
import re from mywebframework import db, route VALID_NAME_RE = re.compile("^[a-zA-Z0-9]+$") @route('/') def home(): return "Hello World!" class Person(db.Model): name: str
Kode apa yang akan dieksekusi jika seseorang mengimpor modul ini?
- Pertama, kode yang terkait dengan ekspresi reguler yang mengkompilasi string ke objek templat akan dieksekusi.
- Kemudian dekorator
@route
akan dieksekusi. Jika kita mengandalkan apa yang kita lihat, maka kita dapat mengasumsikan bahwa di sini, mungkin, representasi yang sesuai terdaftar dalam sistem pemetaan URL. Ini berarti bahwa impor biasa dari modul ini mengarah pada fakta bahwa di tempat lain keadaan global aplikasi berubah. - Sekarang kita akan menjalankan semua kode tubuh dari kelas
Person
. Itu bisa mengandung apa saja. Model
kelas dasar mungkin memiliki metaclass atau metode __init_subclass__
, yang, pada gilirannya, dapat berisi beberapa kode lain yang dieksekusi ketika mengimpor modul kami.
Masalah # 1: startup server lambat dan restart
Satu-satunya baris kode untuk modul ini yang (mungkin) tidak dieksekusi ketika diimpor adalah
return "Hello World!"
. Benar, dengan pasti kita tidak bisa mengatakan ini! Akibatnya, ternyata dengan mengimpor modul sederhana ini yang terdiri dari delapan baris (dan masih belum menggunakannya dalam program kami), kami dapat menyebabkan ratusan atau bahkan ribuan baris kode Python diluncurkan. Dan ini belum lagi bahwa impor modul ini menyebabkan modifikasi pemetaan URL global yang terletak di beberapa tempat lain dalam program ini.
Apa yang harus dilakukan Sebelum kita adalah bagian dari konsekuensi dari kenyataan bahwa Python adalah bahasa yang ditafsirkan dinamis. Ini memungkinkan kita untuk berhasil menyelesaikan berbagai masalah menggunakan metode
metaprogramming . Namun, apa yang salah dengan kode ini?
Sebenarnya, kode ini dalam urutan sempurna. Ini berlaku selama seseorang menggunakannya dalam basis kode yang relatif kecil, di mana tim kecil programmer bekerja. Kode ini tidak menimbulkan masalah selama orang yang menggunakannya dijamin untuk mempertahankan tingkat kedisiplinan tertentu tentang bagaimana tepatnya fitur Python digunakan. Tetapi beberapa aspek dari dinamisme ini dapat menjadi masalah jika ada jutaan baris kode dalam sebuah proyek yang sedang dikerjakan oleh ratusan programmer, banyak di antaranya tidak memiliki pengetahuan Python yang mendalam.
Misalnya, salah satu fitur hebat Python adalah kecepatan langkah-langkah yang terlibat dalam pengembangan bertahap. Yaitu, hasil dari perubahan kode dapat dilihat secara harfiah segera setelah melakukan perubahan tersebut, tanpa perlu mengkompilasi kode. Tetapi jika kita berbicara tentang proyek beberapa juta baris (dan diagram dependensi yang agak membingungkan dari proyek ini), maka plus dari Python ini mulai berubah menjadi minus.
Diperlukan lebih dari 20 detik untuk memulai server kami. Dan terkadang, ketika kita tidak memperhatikan optimasi, waktu ini meningkat menjadi sekitar satu menit. Ini berarti bahwa pengembang perlu 20-60 detik untuk melihat hasil perubahan yang dilakukan pada kode. Ini berlaku untuk apa yang dapat Anda lihat di browser, dan bahkan pada kecepatan menjalankan tes unit. Sayangnya, kali ini cukup bagi seseorang untuk terganggu oleh sesuatu dan melupakan apa yang telah ia lakukan sebelumnya. Sebagian besar waktu ini, secara harfiah, dihabiskan untuk mengimpor modul dan membuat fungsi dan kelas.
Di satu sisi, ini sama dengan menunggu hasil kompilasi program yang ditulis dalam bahasa lain. Tetapi biasanya kompilasi dapat dilakukan
secara bertahap . Intinya adalah Anda hanya dapat mengkompilasi ulang apa yang telah berubah, dan apa yang secara langsung tergantung pada kode yang diubah. Akibatnya, biasanya kompilasi proyek, dilakukan setelah melakukan perubahan kecil pada mereka, cepat. Tetapi ketika bekerja dengan Python, karena perintah impor dapat memiliki segala jenis efek samping, tidak ada cara yang dapat diandalkan dan aman untuk me-restart server secara bertahap. Pada saat yang sama, skala perubahan tidak penting dan setiap kali kita harus me-restart server sepenuhnya, mengimpor semua modul, menciptakan kembali semua kelas dan fungsi, mengkompilasi ulang semua ekspresi reguler, dan sebagainya. Biasanya, sejak saat restart server terakhir, 99% kode belum berubah, tetapi kita masih harus melakukan hal yang sama berulang-ulang untuk memasukkan perubahan.
Selain memperlambat pengembang, ini mengarah pada pemborosan sumber daya sistem dalam jumlah besar yang tidak produktif. Faktanya adalah bahwa kami bekerja dalam mode penyebaran perubahan yang berkelanjutan, yang berarti pemuatan ulang kode server produksi secara konstan.
Sebenarnya, inilah masalah pertama kami: mulai lambat dan mulai ulang server. Masalah ini muncul karena fakta bahwa sistem harus terus-menerus melakukan sejumlah besar tindakan berulang selama impor kode.
Masalah # 2: Efek Samping dari Perintah Impor Tidak Aman
Ini adalah tugas lain yang, ternyata, pengembang sering menyelesaikan ketika mengimpor modul. Ini memuat pengaturan dari penyimpanan konfigurasi jaringan:
MY_CONFIG = get_config_from_network_service()
Selain memperlambat startup server, itu juga tidak aman. Jika layanan jaringan tidak tersedia, maka ini tidak hanya akan mengarah pada kenyataan bahwa kami akan menerima pesan kesalahan tentang ketidakmampuan untuk memenuhi beberapa permintaan. Ini akan menyebabkan server gagal memulai.
Mari kita kentalkan catnya dan bayangkan seseorang ditambahkan ke modul yang bertanggung jawab untuk menginisialisasi layanan jaringan penting, beberapa kode yang dieksekusi selama impor. Pengembang sama sekali tidak tahu di mana menambahkan kode ini kepadanya, jadi dia meletakkannya dalam modul yang diimpor pada tahap awal memulai server. Ternyata skema ini berhasil, sehingga solusinya dianggap berhasil dan pekerjaan terus berlanjut.
Tetapi kemudian orang lain menambahkan di tempat lain tim impor, yang pada pandangan pertama tidak berbahaya. Akibatnya, melalui rantai impor dengan kedalaman dua belas modul, ini mengarah pada fakta bahwa modul yang mengunduh pengaturan dari jaringan sekarang diimpor ke modul yang menginisialisasi layanan jaringan yang sesuai.
Sekarang ternyata kami mencoba menggunakan layanan sebelum diinisialisasi. Sistem mengalami crash secara alami. Dalam kasus terbaik, jika kita berbicara tentang sistem di mana interaksi sepenuhnya deterministik, ini dapat mengarah pada fakta bahwa pengembang akan menghabiskan satu atau dua jam mencari tahu bagaimana perubahan kecil menyebabkan kegagalan dalam sesuatu, dengan dia, tampaknya tidak terhubung. Tetapi dalam situasi yang lebih kompleks, ini dapat menyebabkan "jatuhnya" proyek dalam produksi. Namun, tidak ada cara universal untuk menggunakan
linter untuk memerangi masalah seperti itu atau untuk mencegahnya.
Akar masalahnya terletak pada dua faktor, interaksi yang mengarah pada konsekuensi yang menghancurkan:
- Python memungkinkan modul untuk memiliki efek samping yang sewenang-wenang dan tidak aman yang terjadi selama impor.
- Urutan impor kode tidak diatur secara eksplisit dan tidak dikontrol. Pada skala proyek, semacam "impor komprehensif" adalah apa yang terdiri dari perintah impor yang terdapat di semua modul. Dalam hal ini, urutan impor modul dapat bervariasi tergantung pada titik input sistem yang digunakan.
Dilanjutkan ...Pembaca yang budiman! Apakah Anda mengalami masalah terkait dengan lambatnya memulai proyek Python?
