O cuidado aqui tem que ser sempre em obscurecer informações sensíveis. Claro que senhas e derivados já não deveriam nunca estar em texto plano por natureza. Porém, ainda existe a chance de expor acidentalmente dados sobre seus usuários que não deveriam ficar em qualquer log por aí. Imagens de conversas privadas são um exemplo clássico de algo que deveria ser obscurecido em um log.
Pode até parecer tudo bem modelar algo no seu sistema e depois ter que voltar atrás logo no começo, porém, se você já tem uma base de clientes ou usuários em produção, esse tipo de fenômeno deve passar a ser exceção. Afinal de contas, ninguém vai gostar de entrar no seu sistema caso ele passe um dia inteiro fora do ar, "voltando no tempo" antes de um erro de modelagem ou de uma perda ou poluição de dados causada acidentalmente.
Para ter um produto admirável o suficiente pelo usuário final, é importante que esse tipo de situação seja prevenida através da suíte de testes que comentamos e também através de um período de validação mínimo em ambiente staging (homologação) também citado.
Portanto, deve haver, no mínimo, uma parte do seu processo de desenvolvimento que conte com esse período de validação, principalmente do que é mais sensível em termos de regras de negócio. Não esqueça que um tempo suficiente para projetar e modelar uma nova funcionalidade também é fundamental, para que não sejam feitos "puxadinhos" dos quais você com certeza vai se arrepender no futuro.
Pode se tornar complicado evoluir um produto que já está em produção com um time pequeno. Afinal, seu time agora também tem que eventualmente tratar incidentes e talvez fornecer suporte técnico. Isso é ainda mais difícil se você não tiver um plano. Por isso, recomendo que seja feito sempre um roadmap, que pode ser através de listas com bullets ou mapas mentais, ou outra forma semelhante de ao menos mapear o que será (ou pelo menos o que é desejável que seja) desenvolvido nos próximos 2, 3 e até 6 meses de projeto.
Aprendemos com a experiência aqui na ateliware que desenvolver olhando para o escuro é a receita perfeita para um sistema que pode ficar estagnado no tempo ou perder a chance que tinha de conquistar um grande mercado. Logo, tenha sempre um objetivo, mesmo que resumido, e itere sobre esse objetivo ao longo do tempo (exemplo: mensalmente).
Não é preciso desenvolver no modo go-horse para deixar para trás algum débito técnico. É natural que mesmo um sistema desenvolvido com planejamento tenha seus débitos técnicos ou refatorações ainda não feitas, que por conta do ritmo rápido de crescimento tiveram de ser toleradas e postergadas.
Entretanto, é recomendado que se itere sobre tais débitos, como códigos repetidos, a fim de evitar que em um futuro próximo sua base de código seja insustentável de manter.
Outro tipo de débito técnico pode ser também causado por features que funcionam para um sistema com poucos usuários. Por exemplo: é natural que um sistema com poucos usuários possua algum tipo de listagem sem paginação. Isso pode inclusive ganhar tempo de desenvolvimento inicialmente, afinal, para que fazer todas as funcionalidades do seu sistema terem de lidar com big data caso ele não tenha dados "ainda tão big" assim.
Entretanto, é fundamental que se acompanhe esse tipo de funcionalidade mais "precária" para que, ao subir rapidamente em número de usuários, seu sistema não engasgue por conta de algo simples de resolver.
Do ponto de vista das bibliotecas e frameworks, atualizá-los sempre e pouco a pouco também é parte de um processo constante, que ao ser comprometido, pode prejudicar o avanço do projeto depois.
Pare para pensar: até que é rápido atualizar a versão do seu projeto em Ruby on Rails da versão 5.2 para a 6, não? Porém, ao fazer o mesmo processo da versão 3 para a 6, por exemplo, em um sistema que já existe em produção, isso poderia levar meses.
Além do framework, cada lib de suporte tem suas próprias dependências. Portanto, a dica é atualizar sempre para atualizar facilmente. Tire um tempo do projeto, em um intervalo que pode ser de 2 em 2 meses, ou até mais longo, mas que seja dedicado a iterar sobre as dependências das bibliotecas, versões de frameworks e linguagens. E, claro, teste isso tudo no staging antes de subir para produção.
Um debate constante tem se intensificado, principalmente no Brasil, sobre as formas corretas de se lidar com dados dos usuários. Com a divulgação da LGPD (Lei Geral da Proteção de Dados), esse cuidado se tornou ainda mais importante e fundamental para que qualquer sistema em produção tenha uma política clara sobre a retenção de dados dos usuários, e até recursos para limpeza, exportação ou remoção desses dados a pedido do cliente.
Vale lembrar que guardar senhas e outras informações sensíveis - como número de cartões de crédito - em texto plano nunca foi aceitável! Prefira cartões tokenizados, se for guardar, e senhas somente em hash.
Para que seu time mantenha as entregas e um pace (ritmo) de deploy confiável e constante é fundamental que se otimize esse processo. Portanto, é importante confiar em ferramentas de Continuous Delivery/Continuous Deployment (CD) e Continuous Integration (CI).
No caso do CI, a principal função dessas ferramentas é fazer com que o código sempre seja re-testado e re-integrado com a funcionalidade que já estava lá anteriormente. É possível integrar, por exemplo, para que seu container rode a suíte de testes sempre que um pull request for aberto. Caso não passe nessa suíte de testes, significa que o código não está de fato preparado para aquele pull request, e ferramentas como o Github têm integrações disponíveis para travar com que esse merge do pull request seja feito em caso de reprovação automaticamente. É possível testar também se a cobertura de testes está completa, se a estrutura de workers para filas funciona adequadamente, entre outros.
No caso do CD, a ideia é parecida, porém com o intuito de focar na facilidade de se deployar algo novo. Afinal de contas, ter um processo de deploy que dura 2 dias faz com que você e seu time percam muito mais tempo com o processo em si do que talvez com o próprio desenvolvimento. Daí a necessidade do CD. Assim que um código entrar na branch destino (master, staging ou outras, por exemplo), é possível fazer com que esse código tenha seu deploy facilitado e em grande parte automatizado. Assim, você garante que pequenas features vão entrar sempre que algo novo estiver pronto.
Dessa forma, a dor de cabeça é bem menor, pois é bem mais simples garantir que uma funcionalidade nova pequena esteja intacta, do que algo que muda completamente o comportamento do sistema.
É quase impossível que um sistema grande em produção (monolítico ou não) não dependa de nenhuma estrutura de organização de tarefas assíncronas. O próprio disparo de e-mails, por exemplo, já pode se tornar um gargalo caso a aplicação cresça e tenha de ser feito em tempo real. Importações, exportações, machine learning, cron jobs (com data e hora para acontecer), e muitos outros processos são exemplos claros de que seu sistema em produção teria que comer muito arroz e feijão (falando de recursos) para dar conta de tudo isso.
Portanto, desde que features como essas são pensadas, já se pode escolher ferramentas para lidar com isso. Exemplos não faltam em qualquer linguagem: Sidekiq, RabbitMQ, quaisquer libs que utilizam Redis ou Memcached por baixo para gestão mais elaborada de filas e jobs assíncronos ajudam nesse aspecto. Escolha sempre as ferramentas mais bem avaliadas (open source ou não), e mais focadas naquilo que seu projeto precisa.