Em dic Oleg Ermakov, treballo a l'equip de desenvolupament de backend de l'aplicació Yandex.Taxi. És habitual que fem stand-ups diaris, on cadascú de nosaltres parla de les tasques que hem fet aquell dia. Així és com passa...
Potser s'han canviat els noms dels empleats, però les tasques són força reals!
Són les 12:45, tot l'equip està reunit en una sala de reunions. Ivan, un desenvolupador en pràctiques, pren la paraula primer.
Ivan:
Estava treballant en el problema de mostrar totes les quantitats possibles que un passatger podria donar a un conductor donat el cost d'un viatge. La tasca és força coneguda: s'anomena "Intercanvi de monedes". Tenint en compte els detalls, vaig afegir diverses optimitzacions a l'algorisme. Vaig enviar una sol·licitud d'extracció per a la revisió abans d'ahir, però des d'aleshores he anat corregint els comentaris.
A partir del somriure satisfet de l'Anna, va quedar clar els comentaris de qui l'Ivan estava corregint.
En primer lloc, vaig realitzar una descomposició mínima de l'algorisme i vaig codificar el rebut dels bitllets. En la primera implementació, es van escriure possibles bitllets al codi, així que els vaig posar a la configuració per país.He afegit comentaris per al futur perquè qualsevol persona que llegeixi pugui entendre ràpidament l'algorisme:
for exception in self.exceptions[banknote]: exc_value = value + exception.delta if exc_value - cost >= banknote: continue if exc_value > cost >= exception.banknote: banknote_results.append(exc_value) # основные разветвления алгоритма дают некратные купюры for exception in self.exceptions[banknote]: # для таких исключений можно посчитать результат по остатку от # деления таких купюр exc_value = value + exception.delta # но при этом результат не может получиться больше самой банкноты # (corner case) if exc_value - cost >= banknote: continue if exc_value > cost >= exception.banknote: banknote_results.append(exc_value)Bé, per descomptat, vaig passar la resta del temps cobrint tot el codi amb proves.
RUB = [1, 2, 5, 10, 50, 100, 200, 500, 1000, 2000, 5000] CUSTOM_BANKNOTES = [1, 3, 7, 11] @pytest.mark.parametrize( 'cost, banknotes, expected_changes', [ # no banknotes ( 321, [], [], ), # zero cost ( 0, RUB, [], ), # negative cost ( -13, RUB, [], ), # simple testcase ( 264, RUB, [265, 270, 300, 400, 500, 1000, 2000, 5000], ), # cost bigger than max banknote ( 6120, RUB, [6121, 6150, 6200, 6300, 6500, 7000, 8000, 10000], ), # min cost ( 1, RUB, [2, 5, 10, 50, 100, 200, 500, 1000, 2000, 5000], ), ... ], )A més de les proves habituals que s'executen amb cada construcció del projecte, vaig escriure una prova que utilitza un algorisme sense optimitzacions (considereu que és una cerca completa). El resultat d'aquest algorisme per a cada factura dels primers 10 mil casos es va posar en un fitxer i es va executar per separat a l'algorisme amb optimitzacions per assegurar-se que realment funciona correctament.
Fem una pausa del stand-up un minut i resumim els resultats locals de tot el que parla l'Ivan. Quan escriu codi, l'objectiu principal és assegurar-se que funcioni. Per tal d'assolir aquest objectiu, s'han de realitzar les tasques següents:
- Descompondre la lògica empresarial en fragments atòmics. La llegibilitat es fa més difícil quan es visualitza un llenç de codi escrit en una única funció.
- Afegiu comentaris a parts "particularment complexes" del codi. El nostre equip té el següent enfocament: si durant una revisió del codi fan una pregunta sobre la implementació (demanen que expliqui l'algorisme), heu d'afegir un comentari. Millor encara, penseu-hi amb antelació i afegiu-lo vosaltres mateixos.
- Escriu proves que cobreixen les branques principals de l'execució d'algorismes. Les proves no són només un mètode per comprovar la funcionalitat del codi. També serveixen com a exemple de com utilitzar el vostre mòdul.
Malauradament, fins i tot els especialistes amb molts anys d'experiència no sempre utilitzen aquests enfocaments en la seva feina. EN , que estem fent actualment, els estudiants adquiriran habilitats pràctiques per escriure codi arquitectònicament d'alta qualitat. Un altre dels nostres objectius és difondre la pràctica de cobrir un projecte amb proves.
Però tornem a posar-nos dempeus. L'Anna parla després de l'Ivan.
Anna:
Estic desenvolupant un microservei per retornar imatges promocionals. Com recordeu, el servei inicialment proporcionava dades estàtiques de talons. Aleshores, els provadors van demanar personalitzar-los i els vaig posar a la configuració, i ara estic fent una implementació "honesta" amb dades que retornen de la base de dades (PostgreSQL 10.9). La descomposició que es va posar en marxa inicialment em va ajudar molt, en el marc de la qual la interfície d'obtenció de dades en lògica de negoci no canvia, i cada nova font (ja sigui una configuració, una base de dades o un microservei extern) només s'implementa. la seva pròpia lògica.
Vaig provar el sistema escrit sota càrrega, les proves van demostrar que el mànec comença a frenar bruscament quan anem a la base de dades. Segons explicar, vaig veure que l'índex no s'utilitza. Encara no he descobert com arreglar-ho.
Vadim:
Quin tipus de petició?
Anya:
Dues condicions sota OR:
SELECT * FROM table_1 JOIN table_2 ON table_1.some_id = table_2.some_id WHERE (table_2.attr1 = 'val' OR table_1.attr2 IN ('val1', 'val2')) AND table_1.deleted_at IS NULL AND table_2.deleted_at IS NULL ORDER BY table_2.created_atExpliqueu que la consulta mostra que no utilitza cap dels índexs dels atributs attr1 de la taula taula_2 i attr2 de la taula taula_1.
Vadim:
Em vaig trobar amb un comportament similar a MySQL, el problema es troba precisament en la condició OR, a causa de la qual només s'utilitza un índex, per exemple, attr2. I la segona condició utilitza seq scan: una passada completa per la taula. La sol·licitud es pot dividir en dues sol·licituds independents. Una alternativa és dividir i congelar el resultat de les sol·licituds al costat del backend. Però llavors heu de pensar en embolicar aquestes dues sol·licituds en una transacció o combinar-les mitjançant UNION, bàsicament, al costat de la base de dades:
SELECT * FROM table_1 JOIN table_2 ON table_1.some_id = table_2.some_id WHERE (table_2.attr1 = 'val') AND table_1.deleted_at IS NULL AND table_2.deleted_at IS NULL ORDER BY table_2.created_at SELECT * FROM table_1 JOIN table_2 ON table_1.some_id = table_2.some_id WHERE (table_1.attr2 IN ('val1' , 'val2')) AND table_1.deleted_at IS NULL AND table_2.deleted_at IS NULL ORDER BY table_2.created_at
Anya:
Gràcies, ho provaré ^_^
Tornem a resumir:
- Gairebé totes les tasques de desenvolupament de productes impliquen la recuperació de registres de fonts externes (serveis o bases de dades). Cal abordar amb cura el tema de la descomposició de les classes que descarreguen dades. Les classes dissenyades correctament us permetran escriure proves i modificar les fonts de dades fàcilment.
- Per treballar eficaçment amb una base de dades, cal conèixer les especificitats de l'execució de consultes, per exemple, entendre explicar.
Treballar amb informació i organitzar els fluxos de dades és una part integral de les tasques de qualsevol desenvolupador de fons. L'escola us introduirà en l'arquitectura d'interacció entre serveis (i fonts de dades). Els estudiants aprendran a treballar amb bases de dades arquitectònicament i des d'un punt de vista operatiu: migració de dades i proves.
Vadim és l'últim ponent de la reunió.
Vadim:
Vaig estar de servei durant una setmana, resolent una cua d'incidències. Un error ridícul al codi va trigar molt de temps: no hi havia registres sota demanda al producte, tot i que la seva creació s'especificava al codi.
A jutjar pel silenci lamentable de tots els presents, és evident que tothom ja s'ha trobat amb el problema d'una manera o altra..
Per obtenir tots els registres d'una sol·licitud, s'utilitza request_id, que s'insereix a tots els registres de la forma següent:
# запись без request_id logger.info( 'my log msg', ) # запись с request_id logger.info( 'my log msg', extra=log_extra, # здесь передается request_id — связующая информация о запросе )log_extra és un diccionari amb metainformació de consulta, les claus i valors de la qual s'escriuran al registre. Sense passar log_extra a la funció de registre, l'entrada no s'associarà amb tots els altres registres perquè no tindrà un request_id.
Vam haver de corregir l'error del servei, tornar-lo a implementar i només llavors tractar l'incident. No és la primera vegada que passa això. Per evitar que això torni a passar, vaig intentar solucionar el problema globalment i desfer-me de log_extra.
Primer vaig escriure un embolcall sobre l'execució de la consulta estàndard:
async def handle(self, request, handler): log_extra = request['log_extra'] log_extra_manager.set_log_extra(log_extra) return await handler(request)Calia decidir com emmagatzemar log_extra dins d'una sol·licitud única. Aquí hi havia dues opcions. El primer és canviar task_factory per eventloop des d'asyncio:
class LogExtraManager: __init__(self, context: Any, settings: typing.Optional[Dict[str, dict]], activations_parameters: list) -> None: loop = asyncio.get_event_loop() task_factory = loop.get_task_factory() if task_factory is None: task_factory = _default_task_factory @functools.wraps(task_factory) def log_extrad_factory(ev_loop, coro): child_task = task_factory(ev_loop, coro) parent_task = asyncio.Task.current_task(loop=ev_loop) log_extra = getattr(parent_task, LOG_EXTRA_CONTEXT_KEY, None) setattr(child_task, LOG_EXTRA_CONTEXT_KEY, log_extra) return child_task # updating loop, so any created task will # get the log_extra of its parent loop.set_task_factory(log_extrad_factory) def set_log_extra(log_extra: dict): loop = asyncio.get_event_loop() task = asyncio.Task.current_task(loop=loop) setattr(task, LOG_EXTRA_CONTEXT_KEY, log_extra)La segona opció és "impulsar" a través de l'equip d'infraestructura una transició a Python 3.7 per utilitzar-la :
log_extra_var = contextvars.ContextVar(LOG_EXTRA_CONTEXT_KEY) class LogExtraManager: def set_log_extra(log_extra: dict): log_extra_var.set(log_extra)Bé, llavors va ser necessari reenviar el fitxer desat al context log_extra al registrador.
class LogExtraFactory(logging.LogRecord): # this class allows to create log rows with log_extra in the record def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) task = asyncio.Task.current_task() log_extra = getattr(task, LOG_EXTRA_CONTEXT_KEY, None) if not log_extra: return for key in log_extra: self.__dict__[key] = log_extra[key] logging.setLogRecordFactory(LogExtraFactory)
Resultats:
- Yandex.Taxi (i a tot arreu de Yandex) utilitza activament asyncio. És important no només poder-lo utilitzar, sinó també entendre la seva estructura interna.
- Desenvolupa l'hàbit de llegir els registres de canvis de totes les noves versions de l'idioma, pensa com pots fer-te la vida més fàcil a tu i als teus companys amb l'ajuda de les innovacions.
- Quan treballeu amb biblioteques estàndard, no tingueu por d'aprofundir en el seu codi font i entendre'n l'estructura. Aquesta és una habilitat molt útil que us permetrà entendre millor el funcionament del mòdul i obrir noves oportunitats en la implementació de funcions.
Professors vam menjar més d'un quilo de sal i vam cometre molts errors en el funcionament asíncron dels serveis. Explicaran als estudiants les característiques del treball asíncron en Python, tant a nivell d'aplicació pràctica com pel que fa a l'anàlisi de l'interior dels paquets.
Llibres i enllaços
El següent us pot ajudar a aprendre Python:
- Tres llibres: , и .
- Conferències en vídeo a càrrec de pilars de la indústria informàtica com Raymond Hettinger i David Beasley. De les videoconferències de la primera es poden distingir "Més enllà de PEP 8: pràctiques recomanades per a un codi intel·ligent bonic". T'aconsello que miris a Beasley's sobre l'asyncio.
Per obtenir un nivell superior de comprensió de l'arquitectura, llegiu els llibres següents:
- . Els problemes d'interacció amb les dades (codificació de dades, treball amb dades distribuïdes, replicació, particions, transaccions, etc.) es descriuen amb detall aquí.
- . El llibre mostra els enfocaments principals de l'arquitectura de microserveis, descriu les mancances i problemes que s'ha d'afrontar quan es passa d'un monòlit als microserveis. Gairebé no hi ha res sobre ells a la publicació, però tot i així recomano llegir aquest llibre. Començareu a entendre les tendències de les arquitectures i aprendreu pràctiques bàsiques de descomposició de codi.
Una altra de les habilitats més importants que pots desenvolupar sense parar en tu mateix és llegir el codi d'altres persones. Si de sobte t'adones que poques vegades llegeixes el codi d'altres persones, t'aconsello que desenvolupi l'hàbit de veure regularment nous continguts populars. .
El stand-up va acabar, tothom va anar a treballar.
Font: www.habr.com


