Unapokuwa unasimamia biashara ya dalali wa magari nchini Azerbaijan na kuagiza magari ya salvage kutoka Marekani, matatizo yako ya programu yanaonekana tofauti na yale ya kampuni changa ya Silicon Valley. Haupangi mfumo wako kwa ajili ya watumiaji milioni wanaotumia kwa wakati mmoja. Unapanga mfumo wako kwa ajili ya uwazi, uendelevu wa huduma (uptime), na uwezo wa kurekebisha mambo mwenyewe usiku wa manane huku ukiratibu na nyumba ya mnada iliyo saa kumi na mbili mbele au nyuma ya eneo lako. Hiyo ndiyo hali niliyokuwa nayo nilipojenga AutoMakler. Jukwaa hili linashughulikia kila kitu kuanzia scraping ya mnada wa moja kwa moja na utafutaji wa Carfax hadi makadirio ya uwasilishaji na uchakataji wa malipo. Ni mfumo halisi wa uzalishaji unaohudumia wateja halisi, na unaendesha kwa kutumia mchanganyiko wa teknolojia (stack) ambao watengenezaji wengi wangeuita "aggressively boring".
Stack Ambayo Hakuna Anayetaka Kuutangaza
Hakuna React. Hakuna Vue. Hakuna Redis, hakuna Celery, na hakuna seva ya WebSocket. Backend ni FastAPI kwa kutumia Python ya kawaida. Database ni PostgreSQL. Frontend ni HTML inayotolewa na seva (server-rendered) kwa kutumia Jinja2 templates, Bootstrap, na kiasi kidogo cha vanilla JavaScript. Kwa ajili ya scraping, ninatumia Playwright. Kila kitu kinaendesha kama mchakato mmoja wa Python (single Python process) unaotoa HTML moja kwa moja.
Hakuna hatua ya ujenzi (build step). Hakuna folda za node_modules za kukagua, hakuna transpilers za kusanidi, na hakuna mabadiliko ya mara kwa mara ya frontend frameworks ya kuifuata. Ninapoweka mfumo (deploy), ninahamisha faili za Python na templates, si kuratibu mfululizo wa bundlers. Urahisi huo si upungufu. Ndio lengo kuu.
Jinsi ya Kupanga Kazi (Queue Jobs) Bila Message Broker
Scraping ya mnada wa magari wa moja kwa moja haiwezi kufanyika kwa wakati mmoja (synchronously). Scraping moja inaweza kuchukua sekunde kadhaa wakati Playwright inapakia ukurasa, inatekeleza JavaScript, na kutoa data. Kumzuia mtumiaji wakati huu unatokea si chaguo sahihi. Utaratibu wa kawaida unasema uandae Redis, sanidi Celery, na uanzishe kundi la wafanyakazi (worker pool). Mimi niliruka hatua zote hizo.
Badala yake, AutoMakler inatumia Postgres kama mfumo wake wa kupanga kazi (job queue). Mtumiaji anapochochea scraping, programu inaandika mstari mpya kwenye jedwali la kazi (tasks table) ukiwa na hali ya pending. Kazi ya nyuma ya asyncio (asyncio background task) inachukua mstari huo na kuanzisha scraping ya kivinjari (browser scrape). Wakati huo huo, kivinjari kinaulizia (polls) sehemu ndogo ya API (endpoint) kila baada ya sekunde tatu ili kuangalia hali. Mstari unapobadilika kuwa completed, ukurasa unajirekebisha na kuonyesha matokeo.
Mfumo huu unafanya kazi kwa sababu muda wa kuulizia (polling interval) ni mfupi vya kutosha kuhisi mwitikio wa haraka lakini mrefu vya kutosha kuepuka kuichosha seva. Sekunde tatu ni muda mrefu sana kwa kompyuta na haihisiwa kabisa na binadamu anayesubiri tovuti ya nje ya mnada. Database inashughulikia uendeshaji wa kazi nyingi (concurrency) kiasili, na kwa sababu kazi hizo ni mistari tu kwenye Postgres, naweza kukagua foleni kwa kutumia SQL query rahisi badala ya kuchimbua kwenye logs za Celery au Redis keys.
Kuweka Seva Hai Bila Worker Pool
Uendeshaji wa kivinjari (browser automation) unatumia kumbukumbu (memory) nyingi. Ukizindua mifano mingi ya Playwright kwa wakati mmoja, seva yako itaporomoka. Suluhisho la kawaida ni kutumia worker pool inayodhibitiwa yenye mipaka ya uendeshaji wa kazi nyingi, mara nyingi ikitegemewa na mchanganyiko ule ule wa Redis na Celery. Mimi ninatumia mstari mmoja wa Python: asyncio.Semaphore.
Semaphore huweka kikomo cha mifano mingi ya kivinjari inayoweza kuendesha kwa wakati mmoja. Ombi jipya la scraping linapokuja, linaweza kuchukua nafasi mara moja au kusubiri hadi nafasi ifunguke. Hii yote inatokea ndani ya mchakato uleule. Hakuna mratibu wa nje (external orchestrator) wa kufeli, hakuna mchakato wa mfanyakazi (worker process) wa kufa kimya kimya, na hakuna miundombinu ya ziada ya kufuatilia. Kumbukumbu yangu inabaki inayotabirika, na kodi inayolinda seva iko karibu kabisa na kodi inayotumia seva hiyo, si iliyofichwa kwenye deployment manifest.
Kusambaza Fedha kwa Kutumia URL Moja ya Callback
Uchakataji wa malipo ulileta kizuizi ambacho sikuweza kukibadilisha. Mlango wangu wa malipo (payment gateway) unaruhusu URL moja tu ya callback kwa kila akaunti ya mfanyabiashara, lakini nilihitaji kuchakata miamala kwa miradi miwili tofauti kupitia akaunti hiyo hiyo moja. Kujenga wasifu wa pili wa mfanyabiashara kungekuwa na maana ya ada za ziada, uzingatiaji wa sheria (compliance) wa ziada, na karatasi za ziada ambazo dalali mdogo hana muda nazo.
Suluhisho lilikuwa ni kuweka jina la mradi moja kwa moja kwenye mfuatano wa ID ya oda (order ID string) kabla ya kumtuma mteja kwenye gateway. Callback inapofika kwenye seva yangu, AutoMakler inafafanua (decodes) ID hiyo, inatambua ni mradi upi unahusika na malipo hayo, na inatuma taarifa kwa msimamizi sahihi wa ndani (internal handler). Mantiki iliyokuwepo haikuguswa. Hii ni usanifu wa kuongeza (additive design): sikuandika upya mtiririko wa malipo, nilifanya tu utambulisho ubebe muktadha zaidi. Ni aina ya mbinu (hack) ambayo inaonekana kuwa wazi baada ya kuifanya, lakini huokoa saa nyingi za mambo magumu ya usanifu (architectural gymnastics).
Chat Inayofanya Kazi Bila WebSockets
Soga za huduma kwa wateja mara nyingi ndipo wahandisi hukata tamaa na kuongeza WebSockets. Nilihitaji mfumo wa ujumbe wa ndani ya programu, lakini pia nilihitaji kuweka ukubwa wa miundombinu kuwa mdogo sana. Hivyo, nilitumia tena mkakati ule ule wa polling unaowezesha ukusanyaji wa data (scrapes) za mnada.
Ujumbe huhifadhiwa kwenye Postgres. Mtumiaji anapotuma ujumbe, huandikwa kwenye jedwali. Mteja (client) huulizia (polls) mabadiliko, na UI huonyesha ujumbe mpya na uthibitisho wa kusomwa (read receipts) karibu na wakati halisi. Ili kuifanya hii iwe ya haraka hata mazungumzo yanapoongezeka, niliongeza Postgres partial index inayohusu ujumbe usiosomwa pekee kwa mazungumzo yanayoendelea. Kanzidata haipotezi nguvu (cycles) kuskani historia ya zamani, na query planner inaweza kukidhi mahitaji mengi ya kutafuta soga kwa kutumia index range scan inayofanya kazi kwa ufanisi.
Kwa soga ya huduma ambapo kuchelewa (latency) kwa sekunde chache inakubalika, hii inatosha kabisa. Watumiaji wanapata mrejesho wanaohitaji, na sijawahi kulazimika kutatua (debug) muunganisho wa WebSocket uliopitwa na wakati au kusimamia socket server tofauti.
Hasara za Kweli
Usanifu huu unahusisha mabadilishano (trade-offs) ya kweli, na kudai vinginevyo kungekuwa ni kusema uongo. Polling inatuma maombi mengi (chatty). Kila baada ya sekunde tatu, kila mteja hai huulizia seva. Bandwidth na mzigo wa query ni mkubwa kuliko unavyohitajika na muunganisho wa socket wa kudumu. Ikiwa mchakato wa Python utaanza upya, kazi yoyote ya nyuma (background task) inayotekelezwa itakufa mara moja kwa sababu hakuna mfanyakazi wa nje (external worker) wa kuichukua tena. Nakubali hili kwa sababu kazi hizo ni ndogo na gharama ya kujaribu tena ni ndogo. Ukusanyaji wa data (scrape) wa kivinjari ulioshindwa unaweza kurudishwa tu na mtumiaji.
Pia kuna kikomo (ceiling) kwa njia hii. Ikiwa AutoMakler itahitaji kuhudumia maelfu ya ukusanyaji wa data (scrapes) ya wakati mmoja, mfumo wa mchakato mmoja (single-process model) wenye polling utapata ugumu. Lakini hiyo si biashara ninayofanya. Nahitaji uimara kwa makumi ya watumiaji wanaotumia kwa wakati mmoja, si
