Publicado originalmente em: Muros em times de dados (flavioclesio.com)
A ideia deste ensaio não é vender a ideia do “porque times de dados deveriam ser mais de ponta a ponta” ou da “importância de que todos desenvolvedores tenham a noção mínima de como atuar em inúmeras partes de um serviço de Machine Learning”, dado que esses argumentos já foram feitos pelo Eric Colson e pelo Eugene Yan nos respectivos ensaios “Cuidado com o time de Data Science de apertadores de parafuso: O poder do generalista Full-Stack de Ciencia de Dados e os perigos da divisão de trabalho através da funções” e “Opinião impopular: Cientistas de Dados devem trabalhar mais de ponta a ponta”.
Eu vou tentar colocar de forma detalhada e anedótica (e dentro de alguns limites de privacidade e linha do tempo), alguns eventos que eu presenciei tanto quanto membro e como cliente de times de dados que tinham uma hiperespecialização de funções e algumas de suas consequências não-intencionais.
Apesar do ensaio falar especificamente de um time de produtos de dados, o mesmo pode ser aplicado para times de Analytics, BI, Data Intelligence, Delivery Science e demais tipos de times de análise de dados.
Em um determinado momento da minha carreira eu participei de um time Dados que tinha a seguinte composição: um Engenheiro de Machine Learning, um Cientista de Dados e um Engenheiro de Software.
O time tinha três características bem peculiares que entram como fatores contribuintes em relação aos casos colocados aqui que são:
- (1) o time não tinha nenhum líder técnico pois a organização acreditava em um modelo de Holocracia;
- (2) por conta do (1) foi estabelecida uma autoridade informal do time de produtos que criou uma relação de total precedência sobre o time de engenharia e analytics, a ponto de ditar partes do código que iriam para produção ou não; e
- (3) devido ao ponto (2), o time não trabalhava em prol de resolução de problemas um serviço que atendia um consumidor final, mas sim cada engenheiro recebia as tarefas exatamente de acordo com o nome da função que estava no crachá no cadastro do RH.
Clarificando o último ponto:
- O Cientista de Dados não faria nada mais do que pegar tickets de análise de dados ou prototipação; mesmo se tivesse histórico profissional em engenharia de software para implementação de APIs;
- o Engenheiro de Machine Learning só poderia traduzir código R, e colocar tudo dentro do Falcon usando Python; mesmo se ele tivesse experiência com modelagem estatística; e por fim
- o Engenheiro de Software era expressamente desencorajado a colocar a mão no código Python de implementações no Falcon para melhoria de performance, mesmo com uma vasta experiência em backend.
Em suma, o time estava realizando um trabalho do conhecimento, mas atuando dentro de um paradigma Taylorista em relação ao fluxo de trabalho. Inicialmente estávamos todos satisfeitos com o fato de ter um foco específico em nossas próprias disciplinas. Afinal de contas não ficaríamos distraídos em relação a trocas de contexto ou ligados em assuntos “não relevantes” dentro de cada uma das disciplinas.
No começo existia uma percepção do aumento da produtividade do time, dado que para cada indivíduo existia um backlog de tarefas bem definido. Como cada profissional tinha as suas tarefas da sprint previamente estabelecidas dentro de um quadro personalizado no Jira, o mesmo não teria que se preocupar com absolutamente nada além da sua esfera de habilidades.
De forma explícita:
- O Cientista de Dados não teria que fazer deploy de modelo e nem atualizar registro de Dockerfile no ECR;
- O Engenheiro de ML não teria que ler/entender intermináveis linhas de código em Scala + Ruby para embutir o modelo na plataforma, ou mesmo se preocupar com integração com outros serviços clientes; e por fim,
- O Engenheiro de Software não precisaria gastar horas depurando modelo serializado em caso de tempo de resposta acima de 500ms por requisição, restando apenas esperar que o problema fosse resolvido pela pessoa que tivesse aquela competência específica no crachá.
No começo a produtividade do time estava em níveis estratosféricos. Tickets e mais tickets no JIRA sendo fechados com uma velocidade impressionante; dado que o grau de incerteza em relação ao conhecimento necessário para a entrega da tarefa era muito menor.
Cientistas de dados estavam treinando modelos com bibliotecas que usavam estado da arte em grau de sofisticação, engenheiros usando serviços da AWS lançados semanas antes no AWS re:Invent, e claro, os times de produtos extremamente felizes com as coisas saindo como esperado.
Mas parafraseando Shakespeare na peça Hamlet, “algo não cheirava bem reino da Dinamarca” no que se refere à forma na qual as coisas estavam sendo construídas e principalmente se algo precisasse de manutenção…
Como diria o baluarte da filosofia contemporânea Mike Tyson, “Todo mundo tem um plano, até tomar o primeiro soco na cara”. E o nosso plano era simples: Dado que cada um estava fazendo um trabalho bem especifico, tínhamos a vantagem de implementar soluções mais otimizadas desde o início do ciclo de desenvolvimento, dado que cada um trabalharia especificamente em sua disciplina, sem distrações ou demais incertezas.
E para completar, o nosso plano tinha como pressuposto que a transferência de conhecimento para o profissional imediatamente posterior da sequência de trabalho seria simples e fácil.
Em outras palavras, não colocamos na equação questões como a incerteza sobre o conhecimento do receptor em relação ao conteúdo informado, tempo de aprendizado caso houvessem lacunas de conhecimento, clarificações, e demais aspectos que poderiam ser um ponto de fricção durante essa transferência de conhecimento.
E o nosso soco na cara começou a ficar mais próximo quando começamos a colocar inúmeros modelos/serviços/produtos de Machine Learning em produção.
Para cada um destes modelos tínhamos que lidar com diversos aspectos como: monitoramento das requisições e outputs, concept/data drift, pipelines cada vez mais complexos (até com casos de atualização de dados de treinamento em tempo real), situações extremas (corner cases) chegando, orquestração do retreinamento dos modelos, segurança, logging, inclusão de regras de negócio nas APIs, e claro bugs.
Quando os modelos/serviços/produtos de Machine Learning entravam em produção para ajudar os nossos clientes, ao mesmo tempo que sentimentos de satisfação, orgulho e alívio floreciam, quando enfim as primeiras recomendações/predições/classificações/inferências eram passadas aos nossos clientes; depois do êxtase dos cinco primeiros minutos todo mundo só pensava:
“Se esse serviço parar e fulano não tiver aqui, como vai ser pra arrumar?”.
…
Muitas vezes a sorte e a robustez de grande parte das tecnologias atuais escondem inúmeros problemas latentes sem que ninguém perceba o que está acontecendo. E isso dá uma falsa sensação de que somos planejadores sagazes, e em alguns casos isso termina em um excesso de otimismo por parte de alguns stakeholders. O raciocínio era: se nunca aconteceu* um evento de ausência de algum membro do time na ocasião de um problema em produção, logo isso não era um problema.
Mas na realidade, com inúmeras partes móveis para orquestrar, monitorar, e manutenir, e com essa disfunção estabelecida por design de que colaboração era considerada transgressão, era mais do que óbvio que o nosso inevitável soco na cara oferecido pela realidade não viria a cavalo: este soco viria montado em uma Hayabusa de 1300 cilindradas a 300Km/h.
Dois exemplos de como esse muro artificial prejudicou a organização de maneira direta.
No primeiro caso foi um serviço de recomendação que depois de um amplo debate em relação à instrumentação de observabilidade do serviço de ML, chegou-se ao ponto de que o responsável pela implementação do serviço decidiu arbitrariamente que não seria haveria aspectos como tracing, logging, heartbeat, e monitoramento nesta plataforma de recomendação.
Houve quase uma guerra civil entre os membros em relação à implementação, mas por conta do muro que havia dentro do time em relação a essas implementações (sendo que apenas aquele especialista de domínio iria fazer a tarefa de fato), determinou-se que ninguém iria gastar 8 horas a mais para implementar observabilidade neste serviço; mesmo com um dos membros do time com 95% do código pronto para implementar essa instrumentação.
Acontece que por conta de um erro no serviço de agendador, que fazia a orquestração da atualização dos dados que serviam de base para o serviço de Machine Learning, as listas de recomendação não estavam sendo enviadas de forma atualizada para os clientes.
O serviço em produção não tinha alertas ou monitoramento nem na máquina do agendador e nem no sistema de orquestração de atualização dos dados. Sequer haviam mecanismos checagem de consistência e atualização dos dados. O instrumental de observabilidade estava à disposição e alguns dos SREs se ofereceram para ajudar na integração, mas devido a questões de precedência citadas anteriormente, e de impedimento de colaboração, o problema atualização dos dados ocorreu de maneira silenciosa por semanas.
Resultado: Todos os clientes recebendo os mesmos itens, 99% dos itens expirados depois de 72 horas, e claro toda receita proveniente de recomendações zerada.
No segundo caso, a plataforma core começou a ter tempos de resposta médios de 11 segundos em um módulo de predição, o que causou muitas reclamações por conta dos clientes. E por conta dessa investigação, um ticket foi aberto para o Engenheiro de ML especificando que “A API estava lenta”.
A investigação começou na API que fazia o servicing do modelo no qual a plataforma core consumia as predições.
Depois de alguns testes vimos que o modelo serializado estava retornando a predição em menos de 40ms, e a API no Falcon no qual o serviço de predição estava rodando levava pouco mais de 47ms. Preocupante, mas ainda faltava achar os 8.9 segundos restantes.
Como o time não tinha acesso ao backlog pessoal dos outros desenvolvedores, ou acesso à base de código para ver o que tinha mudado, descobrimos depois de muito tempo que a lentidão começou após de um deployment feito por um desenvolvedor em uma “tarefa paralela” a pedido de um stakeholder.
Só existiam alguns detalhes impedindo a solução do problema: (i) o desenvolvedor responsável entrou de licença não remunerada no dia seguinte e (ii) ninguém além desta alma sabia onde estava a base de código do módulo em questão.
Para se ter a ideia do grau de hiperespecialização, e da ausência de funcionalidade cruzada, os outros desenvolvedores Scala + Ruby da empresa não conseguiram nem mesmo criar o mesmo setup do ambiente que esse desenvolvedor usou para colocar esse código em produção.
E agora vem o pior: Um dos membros do time pediu cerca de um mês antes acesso ao repositório e informações sobre o setup para que ele pudesse ver a base de código e aprovar os PR (dado a experiência com Ruby); mas por conta desse muro artificial essa iniciativa foi cancelada. Resultado: Tivemos que ficar com esse problema por um longo período de tempo até esse desenvolvedor voltasse de sua licença e corrigisse o problema.
Desnecessário dizer a quantidade de estresse, e frustração que toda essa situação provocou.
…
Sistemas socio-tecnológicos são altamente dinâmicos e consideram inúmeras variáveis como pessoas, recursos materiais, ambiente, pressões, fatores psicológicos, etc. Seria muito pedante da minha parte querer fazer uma lista prescritiva de x aspectos que todo time de dados deveria fazer para evitar problemas como muros em times de dados sem o devido contexto.
Entretanto, dentro do que eu vivi eu posso falar um pouco de algumas das consequências não-intencionais de quando a colaboração entre membros de um mesmo time é interpretada como transgressão de domínios.
O Eugene Yan fez um argumento interessante sobre as vantagens da funcionalidade cruzada em times de dados e do porque esses times conseguem entregar mais.
Desta forma ao invés de falar do lado bom de não haver muros em times de dados, eu vou colocar algumas das consequências que eu passei na pele e eu deixo as conclusões para cada um dos 3 leitores que conseguiram chegar até nessa parte do post:
- “Quem tem um, não tem nenhum”: Uma das coisas que alguns dos meus amigos Daniel e Eiti sempre me falaram é que {…} quem tem um não tem nenhum, quem tem dois tem um, quem tem três tem apenas dois {…}. Aqui não é uma questão de redundância ou headcount, mas sim o entendimento da fragilidade dos serviços quando os membros do time não têm o conhecimento e contexto mínimo do que foi implementado, e principalmente de como cada um deve atuar em algum problema ou indisponibilidade. O ponto é que na falta de compartilhamento de conhecimento, quando acontecer a ausência de algum membro do time, a plataforma/produto/serviço vai parar. Simples assim. Daí vem duas soluções (i) fica indisponível e a vida segue ou (ii) o tempo de resolução do problema vai ser alto;
- Batata quente como metodologia de transferência de conhecimento: O novo comum era o que deveria ser uma atividade de compartilhamento de conhecimento entre dois membros do time (ex: DS e MLE ou MLE e ES) acabou virando um desenvolvedor passando a batata quente para o outro. Como o trabalho fica altamente especializado e a lacuna entre emissor e receptor na comunicação é grande, surgem dois padrões (i) para cada solução o emissor tem quase que fazer um MOOC para explicar a lógica de implementação (ex: explicação de atualização de taxa de aprendizado em CNN para melhoria de convergência) e (ii) quando o emissor sabia que o receptor iria perder o fio da meada depois de 5 minutos, ambos concordam que o melhor é economizar tempo e colocar em produção para fechar o ticket no Jira. Dado que cada tarefa era individualizada e não havia a possibilidade de colaboração para dar ou receber ajuda, o mais importante era fechar o próprio ticket e passar a tarefa adiante do que ser considerado um gargalo em todo o processo;
- Latifúndios Técnicos: Com um muro impedindo a colaboração sobre o que seria feito, dentro do time acabou acontecendo a criação o que eu chamo vulgarmente de latifúndios técnicos: propriedades de silos de tecnologia de grande extensão, pertencente a uma pessoa ou um pequeno grupo, latifúndio este que geralmente possuí uma baixa produtividade. E aqui rola de tudo: Code Review na broderagem, commit direto na main/master, pessoa se recusando a tirar credencial da AWS do código, *deploy *em produção com um monte de variável de ambiente fantasma, superestimativas, etc.;
- Teatro de revisão: Por conta da hiperespecialização, chegou em um momento que ninguém tinha a mínima ideia do que realmente estava na plataforma em produção. Com todo mundo fora da visão geral, e não sabendo o que a pessoa sentada na estação de trabalho na frente estava fazendo, toda vez que vinha um PR para revisão por mais técnico e bem intencionado fosse o nosso revisor, essa pessoa não fazia a menor ideia do que o código fazia, se os testes estavam nos lugares corretos, ou se a lógica fazia sentido dentro dos requerimentos e atual arquitetura da plataforma. Muitas das vezes o revisor saia da figura da pessoa que iria ajudar a minimizar erros e bugs, e potencialmente vai ajudar a melhorar a performance e a qualidade do código; para se transformar como o último obstáculo entre o trabalho de alguém e o status como resolvido no Jira;
- O eterno telefone sem fio e a sobrecarga de comunicação: Como as pessoas estavam isoladas, a comunicação tinha que ser feita individualmente para cada membro do time com expressões, informações e contextos distintos para cada receptor da informação. Por conta da difusão descentralizada da informação, eram recorrentes os problemas de interpretação. Um outro obstáculo foi que a comunicação técnica era realizada por pessoas sem background. Com isso aconteciam dois problemas: (i) o tempo em reuniões para a comunicação técnica muitas das vezes era maior do que a implementação propriamente dita, e (ii) por conta da ausência de percepção da criticidade, muito da comunicação técnica que demandava um alto grau de precisão era perdido; seja por conta de uma abstração prematura por parte do comunicador ou por simples esquecimento.
- Aumento do prazo de entrega (lead time) das soluções: Com cada profissional dependendo do elemento imediatamente anterior do fluxo de trabalho, o fluxo de trabalho ficava muito lento. A hiperespecialização dificultava a transferência de conhecimento, devido ao fato de que ao invés de ser um investimento sistemático na educação das pessoas para o conhecimento fosse comum a priori, o que acontecia de forma recorrente era uma transferência a quente e de forma ad hoc. Neste caso, aspectos fundamentais que os membros do time poderiam saber via treinamento, eram “aprendidos” no momento da implementação. Outro ponto é que devido a hiperespecialização muitas da vezes não somente os desenvolvedores mas também outros stakeholders ficavam impacientes de realizar essa transferência de conhecimento, e acabavam acumulando as tarefas em apenas uma pessoa. Era comum existir situações como de um desenvolvedor tendo que cuidar de 4 problemas críticos, enquanto os outros dois estavam respectivamente fazendo PoC e aumentando tamanho de caixa de texto para software não crítico para stakeholder interno;
- Criação de dissidentes silenciosos dentro do time e a normalização de desvios: O engenheiro de machine learning precisava entender o backend para saber se ele implementaria o NiFi ou o Flink como solução de processamento de dados streaming, mas ele não podia por conta de que isso era tarefa do engenheiro de integração. O cientista de dados queria entrar nas reuniões de experimentação para ajudar na elaboração seja de estratégias de testes A/B ou Multi-Armed Bandits, mas era impedido pois isso era de exclusividade do time de gerenciamento de produtos. O que na idealização era uma simples e inofensiva divisão de tarefas, na prática promoveu um ambiente que promovia soluções convolutas sem diálogo, colaboração e construção. As pessoas mesmo com melhores ideias, argumentos, dados simplesmente não se sentiam seguras para emitir opiniões, dado que no final apenas uma voz dominante ou um pensamento de grupo era responsável por todas as decisões independentemente da qualidade das informações. E desse ponto era onde começava os pequenos desvios normalizados que meses depois viravam indisponibilidades, mas isso é história para outro momento;
- E no final, as pessoas vão embora: Desenvolvimento de Software em geral tem algumas características únicas como profissão como: (i) é profissão de pratica que só melhora ou se mantém com o constante exercício, (ii) que envolve conhecimento aplicado & criatividade (i.e. exercício contínuo de aspectos cognitivos para aprendizado, retenção e lapidação dos conhecimentos) e que (iii) tem ciclos de atualização extremamente curtos (i.e. um framework de dois anos atrás pode ser obsoleto hoje). Os profissionais da economia da informação (especificamente desenvolvimento de software) sabem que se caírem em um ambiente que não tenha minimamente estes três aspectos (prática, conhecimento aplicado e ciclo de atualização que acompanhe o mercado), eles vão ficar para trás muito rapidamente. E quando as pessoas se sentirem nesta situação, elas simplesmente vão embora. O racional é o seguinte: Uma grana a mais em troca de conforto, complacência e ausência de aprendizado no presente, pode significar uma futura estagnação salarial, desemprego puro e simples, ou uma volta ao mercado muito mais lenta e difícil.
O meu objetivo com essa evidência anedótica foi comentar o lado nem sempre falado dos muros em times de Dados/Machine Learning/Data Science/BI/Analytics. Claro que os aspectos de precedência gerencial e a adoção de um modelo de holocracia tiveram uma associação importante no que foi colocado.
A criação destes silos artificias além de colocar um limite fictício na produtividade dos times, também impede o crescimento dos profissionais em inúmeras esferas (e.g. carreira, corporativa, financeira [N1]). Além do mais, não por acaso, uma porção considerável de projetos de Machine Learning e Data Science estão falhando miseravelmente também por conta da falta de expertise.
O ponto principal é que na economia do conhecimento, grande parte das pessoas terão a inclinação de ir atrás de oportunidades que não somente exercitem as suas habilidades, mas que também ofereçam problemas que as ajudem a expandir e/ou lapidar esses conhecimentos através da experiência no mundo real.
O trabalhador do conhecimento sabe que os seus maiores ativos são (i) a capacidade de aprendizado, (ii) a lapidação e/ou a expansão de conhecimentos que resolvam problemas do mundo real e que sejam cobiçados pelo mercado, e (iii) a capacidade de reconhecimento de que a degradação dessas habilidades pode o colocar fora do mercado em um período de tempo cada vez mais curto.
REFERÊNCIAS
Beware the data science pin factory: The power of the full-stack data science generalist and the perils of division of labor through function — https://multithreaded.stitchfix.com/blog/2019/03/11/FullStackDS-Generalists/
Unpopular Opinion — Data Scientists Should Be More End-to-End — https://eugeneyan.com/writing/end-to-end-data-science/
Collective Ownership — http://www.extremeprogramming.org/rules/collective.html
WikiWikiWeb — Thrown Over The Wall
[N1] — O binômio profissionais qualificados + maiores salários é discutível, mas pessoalmente eu acredito na relação, ainda que ela seja assimétrica entre as partes.