જ્યારે તમે અઝરબૈજાનમાં કાર બ્રોકરેજ ચલાવતા હોવ અને યુનાઇટેડ સ્ટેટ્સમાંથી સેલ્વેજ વાહનો (salvage vehicles) આયાત કરતા હોવ, ત્યારે તમારી સોફ્ટવેર સમસ્યાઓ સિલિકોન વેલીના સ્ટાર્ટઅપ કરતા અલગ હોય છે. તમે લાખો સમાંતર વપરાશકર્તાઓ (concurrent users) માટે ઓપ્ટિમાઇઝેશન નથી કરી રહ્યા. તમે સ્પષ્ટતા, અપટાઇમ અને બાર વગના અંતરે આવેલા ઓક્શન હાઉસ સાથે સંકલન કરતી વખતે મધ્યરાત્રિએ જાતે વસ્તુઓ સુધારવાની ક્ષમતા માટે ઓપ્ટિમાઇઝેશન કરી રહ્યા છો. જ્યારે મેં AutoMakler બનાવ્યું ત્યારે હું બરાબર આ જ પરિસ્થિતિમાં હતો. આ પ્લેટફોર્મ લાઈવ ઓક્શન સ્ક્રેપિંગ અને Carfax લુકઅપ્સથી લઈને ડિલિવરી અંદાજ અને પેમેન્ટ પ્રોસેસિંગ સુધી બધું જ સંભાળે છે. તે વાસ્તવિક ગ્રાહકોને સેવા આપતી વાસ્તવિક પ્રોડક્શન સિસ્ટમ છે, અને તે એવી ટેક સ્ટેક પર ચાલે છે જેને મોટાભાગના ડેવલપર્સ 'અત્યંત બોરિંગ સ્ટેક' કહેશે.

એ ટેક સ્ટેક જેને કોઈ પ્રોમોટ કરવા નથી માંગતું

તેમાં React નથી. No Vue. No Redis, no Celery, અને no WebSocket server. બેકએન્ડ plain Python સાથે FastAPI છે. ડેટાબેઝ PostgreSQL છે. ફ્રન્ટએન્ડ Jinja2 templates, Bootstrap, અને થોડા vanilla JavaScript નો ઉપયોગ કરીને સર્વર-રેન્ડર્ડ HTML છે. સ્ક્રેપિંગ માટે, હું Playwright નો ઉપયોગ કરું છું. બધું જ એક સિંગલ Python પ્રોસેસ તરીકે ચાલે છે જે સીધું HTML સર્વ કરે છે.

તેમાં કોઈ બિલ્ડ સ્ટેપ નથી. ઓડિટ કરવા માટે કોઈ node_modules ફોલ્ડર્સ નથી, કન્ફિગર કરવા માટે કોઈ ટ્રાન્સપાઇલર્સ નથી, અને સાથે ચાલવા માટે કોઈ ફ્રન્ટએન્ડ ફ્રેમવર્કનું ચક્ર નથી. જ્યારે હું ડિપ્લોય કરું છું, ત્યારે હું Python ફાઇલો અને ટેમ્પ્લેટ્સ ખસેડું છું, બંડલર્સની પાઇપલાઇનનું સંચાલન નથી કરતો. તે સાદગી કોઈ સમાધાન નથી. તે આખી વાતનો મુખ્ય મુદ્દો છે.

મેસેજ બ્રોકર વગર જોબ્સને ક очередиમાં કેવી રીતે મૂકવા

લાઈવ કાર ઓક્શનનું સ્ક્રેપિંગ સિંક્રનસ રીતે થઈ શકતું નથી. Playwright પેજ લોડ કરે, JavaScript એક્ઝિક્યુટ કરે અને ડેટા એક્સટ્રેક્ટ કરે ત્યારે એક સિંગલ સ્ક્રેપમાં કેટલાક સેકન્ડ લાગી શકે છે. આ દરમિયાન વપરાશકર્તાને બ્લોક કરવો એ કોઈ વિકલ્પ નથી. સ્ટાન્ડર્ડ પદ્ધતિ મુજબ Redis ઇન્સ્ટોલ કરવું, Celery કન્ફિગર કરવું અને વર્કર પૂલ બનાવવો જોઈએ. મેં આ બધું જ છોડી દીધું.

તેના બદલે, AutoMakler તેના પોતાના જોબ ક્યુ તરીકે Postgres નો ઉપયોગ કરે છે. જ્યારે વપરાશકર્તા સ્ક્રેપ ટ્રિગર કરે છે, ત્યારે એપ્લિકેશન 'pending' સ્ટેટસ સાથે tasks ટેબલમાં એક નવી રો (row) લખે છે. એક asyncio બેકગ્રાઉન્ડ ટાસ્ક તે રો લે છે અને બ્રાઉઝર સ્ક્રેપ શરૂ કરે છે. તે દરમિયાન, બ્રાઉઝર સ્ટેટસ તપાસવા માટે દર ત્રણ સેકન્ડે એક લાઇટવેઇટ એન્ડપોઇન્ટને પોલ (poll) કરે છે. જ્યારે રો completed માં અપડેટ થાય છે, ત્યારે પેજ રિફ્રેશ થાય છે અને પરિણામો બતાવે છે.

આ પેટર્ન કામ કરે છે કારણ કે પોલિંગ ઇન્ટરવલ એટલો ટૂંકો છે કે તે પ્રતિભાવશીલ લાગે પરંતુ સર્વર પર ભાર ન પડે તેટલો લાંબો છે. કમ્પ્યુટર માટે ત્રણ સેકન્ડ એ અનંતકાળ છે અને બહારની ઓક્શન સાઇટની રાહ જોતા માનવી માટે તે ભાગ્યે જ નોંધપાત્ર છે. ડેટાબેઝ નેટિવલી કન્કરન્સી હેન્ડલ કરે છે, અને કારણ કે જોબ્સ માત્ર Postgres માં રો છે, હું Celery લોગ્સ અથવા Redis કીઝમાં ખોદવાને બદલે એક સરળ SQL ક્વેરી સાથે ક્યુ (queue) નું નિરીક્ષણ કરી શકું છું.

વર્કર પૂલ વગર સર્વરને જીવંત રાખવું

બ્રાઉઝર ઓટોમેશન મેમરી-ભક્ષક છે. એકસાથે ઘણા બધા Playwright ઇન્સ્ટન્સ લોન્ચ કરો અને તમારું સર્વર પડી જશે. પરંપરાગત ઉકેલ કન્કરન્સી લિમિટ સાથે મેનેજ્ડ વર્કર પૂલ છે, જે ઘણીવાર તે જ Redis અને Celery કોમ્બિનેશન દ્વારા સપોર્ટેડ હોય છે. હું Python ની એક લાઇનનો ઉપયોગ કરું છું: એક asyncio.Semaphore.

સેમાફોર (Semaphore) એ મર્યાદા નક્કી કરે છે કે કેટલા સમાંતર બ્રાઉઝર ઇન્સ્ટન્સ ચાલી શકે છે. જ્યારે નવો સ્ક્રેપ રિક્વેસ્ટ આવે છે, ત્યારે તે કાં તો તરત જ સ્લોટ મેળવે છે અથવા એક સ્લોટ ખાલી થાય ત્યાં સુધી રાહ જુએ છે. આ બધું જ સમાન પ્રોસેસની અંદર થાય છે. નિષ્ફળ જનાર કોઈ બાહ્ય ઓર્કેસ્ટ્રેટર નથી, શાંતિથી મરી જનાર કોઈ વર્કર પ્રોસેસ નથી, અને મોનિટર કરવા માટે કોઈ વધારાનું ઇન્ફ્રાસ્ટ્રક્ચર નથી. મારી મેમરી અનુમાનિત રહે છે, અને સર્વરને સુરક્ષિત રાખતો કોડ તેના ઉપયોગ કરતા કોડની બરાબર બાજુમાં છે, ડિપ્લોયમેન્ટ મેનિફેસ્ટમાં છુપાયેલો નથી.

એક જ કોલબેક URL સાથે નાણાંનું રૂટિંગ કરવું

પેમેન્ટ પ્રોસેસિંગે એક એવી મર્યાદા લાદી જે હું બદલી શકતો ન હતો. મારો પેમેન્ટ ગેટવે દરેક મર્ચન્ટ એકાઉન્ટ દીઠ બરાબર એક કોલબેક URL ની મંજૂરી આપે છે, પરંતુ મારે તે સિંગલ એકાઉન્ટ દ્વારા બે અલગ પ્રોજેક્ટ્સ માટે ટ્રાન્ઝેક્શન પ્રોસેસ કરવાની જરૂર હતી. બીજું મર્ચન્ટ પ્રોફાઇલ બનાવવાનો અર્થ વધારાની ફી, વધારાનું પાલન (compliance) અને વધારાનું પેપરવર્ક હોત જે એક નાની બ્રોકરેજ પાસે કરવા માટે સમય નથી.

તેનો ઉકેલ ગેટવે પર ગ્રાહકને મોકલતા પહેલા ઓર્ડર ID સ્ટ્રિંગમાં સીધું જ પ્રોજેક્ટનું નામ એન્કોડ કરવાનો હતો. જ્યારે કોલબેક મારા સર્વર પર આવે છે, ત્યારે AutoMakler તે ID ને ડિકોડ કરે છે, કયો પેમેન્ટ કયા પ્રોજેક્ટનો છે તે ઓળખે છે, અને નોટિફિકેશનને સાચા ઇન્ટરનલ હેન્ડલર પર મોકલે છે. હાલનું લોજિક અસ્પૃશ્ય રહ્યું. આ એડિટિવ ડિઝાઇન છે: મેં પેમેન્ટ ફ્લો ફરીથી લખ્યો નથી, મેં ફક્ત આઇડેન્ટિફાયરને થોડો વધુ સંદર્ભ (context) આપ્યો છે. આ એ પ્રકારનો હેક છે જે પાછળથી જોતા સ્પષ્ટ લાગે છે પરંતુ આર્કિટેક્ચરલ જિમ્નાસ્ટિક્સના કલાકો બચાવે છે.

WebSockets વગર કામ કરતા ચેટ

Customer support chat is usually where engineers cave and add WebSockets. I needed in-app messaging, but I also needed to keep the infrastructure footprint tiny. So I reused the same polling strategy that powers the auction scrapes.

Messages are stored in Postgres. When a user sends a message, it writes to the table. The client polls for updates, and the UI reflects new messages and read receipts in near real-time. To keep this fast even as the conversation table grows, I added a Postgres partial index that only covers unread messages for active conversations. The database does not waste cycles scanning old history, and the query planner can satisfy most chat lookups with a tight index range scan.

For a support chat where a few seconds of latency is acceptable, this is perfectly adequate. The users get the feedback they need, and I never had to debug a stale WebSocket connection or manage a separate socket server.

The Honest Downsides

This architecture makes real trade-offs, and pretending otherwise would be dishonest. Polling is chatty. Every three seconds, every active client hits the server. The bandwidth and query load are higher than a persistent socket connection would demand. If the Python process restarts, any in-flight background task dies immediately because there is no external worker to pick it back up. I accept this because the tasks are small and the cost of a retry is low. A failed browser scrape can simply be re-triggered by the user.

There is also a ceiling to this approach. If AutoMakler ever needs to serve thousands of simultaneous scrapes, the single-process model with polling will strain. But that is not the business I am in. I need reliability for dozens of concurrent users, not