Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encerramento da parceria com a Vercel #1724

Open
filipedeschamps opened this issue Jun 14, 2024 · 30 comments
Open

Encerramento da parceria com a Vercel #1724

filipedeschamps opened this issue Jun 14, 2024 · 30 comments
Labels
refatoração Melhoria no código que não modifica o comportamento externo

Comments

@filipedeschamps
Copy link
Owner

Descrição

Recebi o seguinte email da Vercel:

image

Não tenho interesse em continuar com a parceria, pois não estamos oferecendo nenhum retorno e provavelmente não iremos fornecer.

Sugestão de implementação

@aprendendofelipe @Rafatcb precisaremos atuar de forma rápida para levantar quais recursos estamos estourando lá na conta da Vercel, fazer uma força-tarefa para otimizar isso ou até remover por completo o uso (como o Analytics, que a gente poderia arranjar uma solução final).

Não faria mal avaliar a redução da instância do banco na AWS, que hoje estamos usando db.t4g.medium e que está nos custando em média USD 172 ou BRL 923 (totalizando aproximadamente BRL 11.000) anuais.

@filipedeschamps filipedeschamps added novo recurso Nova funcionalidade/recurso refatoração Melhoria no código que não modifica o comportamento externo and removed novo recurso Nova funcionalidade/recurso labels Jun 14, 2024
@filipedeschamps
Copy link
Owner Author

Inclusive, sugiro a gente fazer um levantamento de todos os recursos usados na Vercel e cadastrar aqui na issue para virar histórico, para repassar isso um dia no curso.dev e depois deixar registrado como otimizamos tudo isso (pode virar conhecimento para comunidade open source).

@aprendendofelipe
Copy link
Collaborator

Por enquanto, fora os assentos no time, que são $ 20 por pessoa, temos pouco mais de $ 3 mensais por causa do middleware e $ 56 de Analytics. O resto ficava tudo dentro da cota do plano Pro.

O problema é que a forma de cobrança da Vercel mudou completamente nas últimas semanas, então nosso histórico não ajuda nada, pois o custo teria sido bem maior pelas novas regras e métricas. Pelo que já dá para estimar do volume atual, acredito que vamos ficar em torno dos $300 que vão dar de desconto até dezembro.

Para reduzir o custo, acho que a primeira coisa que teremos que fazer, talvez a única com impacto relevante, será mudar a forma que usamos o cache, diminuindo as revalidações baseadas em tempo e passando para revalidações sob demanda.

@aprendendofelipe
Copy link
Collaborator

Sobre o banco de dados, até pelas otimizações que fizemos nos últimos meses, temos boa margem para diminuir a instância. 👍

E acredito que a Revenue Share não vai aumentar muito a demanda do banco.

E o sistema próprio de busca?

O único impacto imediato seria uma diminuição das chances de sucesso no que está sendo testado em #1698, que é usar o próprio banco para a pesquisa das publicações. Diminui as chances, mas ainda pode funcionar. Só que a implementação não chegou ainda no ponto de testar, pois a funcionalidade ainda está muito inferior ao que temos hoje, que usa o Google, mas que apresenta publicidades deles.

Se a ferramenta própria exigir uma instância do banco mais cara, é melhor continuar usando o Google, pelo menos por enquanto. Quando valer a penas aumentar nosso custo para remover as propagandas do Google, pode compensar usar o plano pago do Google, que no volume de hoje custaria menos de $ 50, e sem impacto no banco de principal.

@filipedeschamps
Copy link
Owner Author

Não saberia avaliar quanto ao sistema de busca.

Em paralelo, precisamos avaliar também o uso do Middleware e Log Drain na Vercel 🤝

@rodrigoKulb
Copy link
Contributor

@filipedeschamps devido ao fluxo de trabalho, ando bem afastado do projeto. Mas, pensando como empresário, acredito que seja necessário uma parceria com algum host de forma "real", colocando um logo no rodapé e criando uma página explicando que ele é um patrocinador da infraestrutura do projeto.

Não vejo nenhum problema em ter um parceiro estratégico para um projeto de código aberto. É bem comum algumas empresas quererem patrocinar projetos de código aberto.

Nesse caso, a troca seria a visibilidade e a possibilidade de novos clientes.

Fora o Tabnews, acredito que você pode ter essa parceria de forma clara no curso.dev, até porque você recomenda a Vercel em algumas aulas.

@Rafatcb
Copy link
Collaborator

Rafatcb commented Jun 14, 2024

Aproveitando o assunto de Analytics, recebi uma notificação hoje sobre uma discussão que estou inscrito. Agora é possível exportar os Analytics da Vercel em um CSV: https://vercel.com/changelog/csv-export-in-web-analytics

@aprendendofelipe
Copy link
Collaborator

Levantei o volume de uso dos recursos da Vercel nos últimos 30 dias, ordenados do maior custo estimado para o menor. O ideal seria fazer uma média do uso nos últimos meses, mas os novos custos são baseados em métricas que antes não eram fornecidas.

Os valores mudam de acordo com a região, então estou estimando com os valores do Brasil (gru1), que é um dos mais caros. Ainda não recebemos faturas no novo padrão, pois ele só passou a valer em 25 de maio.

Recurso Últimos 30 dias Cota inicial Unidade adicional Valor em gru1 (USD) Total (USD) Observações
Data Cache Writes 24M 2M 1M 6,40 140,80 Novo
Fast Origin Transfer 284GB 100GB 1GB 0,41 75,44 Novo
Web Analytics Events 326k 25k 100k 14,00 56,00
Team seats 2 - 1 20,00 40,00  
Log Drains 14GB - 5GB 10,00 30,00 Novo
Fast Data Transfer 45GB 1GB 1GB 0,44 21,56 Novo
Edge Middleware Invocation 6M 1M 1M 2,40 12,00 Quase quadruplicou o custo
Data Cache Reads 13M 10M 1M 0,64 1,92 Novo
Function Invocations 2M 1M 1M 0,60 0,60 Novo
Edge Requests 8M 10M 1M 3,20 0,00 Novo
Edge Request CPU Duration 8 minutos 1 hora 1 hora 0,48 0,00 Novo
Function Duration 160GB-Hour 1.000GB-Hour 1 GB-Hour 0,18 0,00  
Total estimado         378,32  

Middleware

Mesmo tendo quase quadruplicado o valor, ela nos protege de uma forma que só seria possível em planos mais caros da Vercel e/ou da Cloudflare, então não deve compensar mudar isso.

Analytics

Analytics não mudou a forma de cobrança, mas no nosso volume já compensa trocar para o plano de $ 50, pois tem 500k eventos inclusos, o que talvez permita incluir de novo os eventos da Home. E se passar de 500k, são $20 para os próximos 500k eventos.

Pode ser que outros serviços sejam um pouco mais baratos, mas podem não funcionar corretamente com o Next.js, pois tem que contar navegações SPA e diretas junto, e não deve computar as requisições de prefetch.

Dreno dos logs

Dá para desativar o dreno automático da Vercel e enviar os logs tudo via requisições usando a biblioteca next-axiom. Nesse caso entrará no custo de transferência de dados, que é bem menor do que do dreno, mas pode impactar no tempo de resposta das requisições.

Outro caminho, que parece menos interessante, é configurar para a Vercel enviar apenas uma amostragem, ou seja, enviar uma certa porcentagem dos logs. Se enviar apenas 30%, já fica dentro da primeira faixa de 5GB por $ 10, e os 30% são suficientes para analisar o funcionamento normal do sistema, mas em caso de falhas isoladas nós podemos ficar sem logs.

Cache

Cache é por onde acho melhor começar, pois representa a maior parte do custo, então é o que mais tem potencial de redução. Diminuir as revalidações, além de reduzir as "Data Cache Writes", reduz também a "Fast Origin Transfer", que são os dados trafegados entre as lambdas e a edge, ou seja, diminui os dois itens de maior custo.

Região gru1 vs iad1

Outra possibilidade de redução de custos seria mudar nossos servidores para os Estados Unidos. Como o cache é na Edge, continuaríamos com as páginas estáticas sendo carregadas no mesmo tempo, pelo menos as que já estiverem em cache. O impacto mesmo seria na API, mas possivelmente seria um impacto mínimo.

Fiz um comparativo de valores onde ocultei os itens que tem o mesmo preço nas duas regiões. Fica visível que, se reduzirmos bastante os dois primeiros custos, não seria muito vantajoso mudar para iad1. Isso, é claro, olhando apenas para a Vercel. Como o banco de dados teria que migrar junto, também representa uma redução nos custos que deve ser considerada na decisão.

Métrica Valor gru1 (USD) Valor iad1 (USD) Total gru1 (USD) Total iad1 (USD)
Data Cache Writes 6,40 4,00 140,80 88,00
Fast Origin Transfer 0,41 0,06 75,44 11,04
Fast Data Transfer 0,44 0,15 21,56 7,35
Edge Middleware Invocation 2,40 1,50 12,00 7,50
Data Cache Reads 0,64 0,40 1,92 1,20
Edge Requests 3,20 2,00 0,00 0,00
Edge Request CPU Duration 0,48 0,30 0,00 0,00
Total que não depende da região     126,60 126,60
Total estimado     378,32 241,69

@Rafatcb
Copy link
Collaborator

Rafatcb commented Jun 15, 2024

Ótimas tabelas!

Como o banco de dados teria que migrar junto, também representa uma redução nos custos que deve ser considerada na decisão.

Considerando que já estava nos planos (#1172), é uma opção viável.

Como o Filipe mencionou avaliar uma redução na instância do banco, quero compartilhar essa thread do Hacker News que fala sobre como tirar um melhor proveito das configurações de memória do PostgreSQL. Não sei qual é o nosso gargalo, mas na thread e no artigo mencionam algumas configurações, além do uso do REINDEX.

@aprendendofelipe
Copy link
Collaborator

Ainda estamos abaixo de $300 🎉

No relatório de uso da Vercel é possível filtrar algumas métricas por região, o que mostra que nosso maior custo já está em iad1. Leituras e gravações de cache aparecem 100% como iad1. Já o tráfego tem mais de 40% em regiões com o mesmo custo de iad1.

Se estiver correto, é uma ótima notícia, pois o custo estimado fica em $294 (ou $288 se mudar para o Analytics Plus). Com isso, continuamos sem custos na Vercel pelos próximos meses, já que ficamos um pouco abaixo dos $300 que teremos de desconto. E poderemos fazer os ajustes no cache com mais calma, tomando cuidado para não prejudicar a UX.

Será que é isso mesmo? 🤔

Ainda não entendi porque o cache está todo como iad1, sendo que a documentação diz que a cobrança é pelas regiões onde as leituras e gravações ocorrem. Nossas funções estão configuradas para rodar em gru1, e isso deveria valer também para a região onde são executadas as funções que revalidam as páginas estáticas (getStaticProps). E se fosse pela região de consumo do cache, estaria dividido em diferentes regiões. Tomara que não seja um bug no relatório.

Sobre a configuração para gru1

No painel da Vercel está com a configuração padrão, que é iad1, mas essa configuração é sobrescrita para gru1, pois é o que está definido no arquivo vercel.json. Na página de status do TabNews também podemos confirmar que nossas funções estão em gru1, pois é esse o valor da variável de ambiente VERCEL_REGION.

Até subi um projeto de teste na Vercel para ler o valor da variável de ambiente VERCEL_REGION nas diferentes situações (API e getStaticProps). Usei a mesma configuração do TabNews, ou seja, padrão iad1 no painel, mas gru1 no vercel.json. Nos dois ambientes a variável sempre retornou corretamente o valor gru1. E no relatório de uso ficou como no TabNews, o cache todo em iad1 e o tráfego dividido entre as regiões. Então a dúvida permanece se é um bug no relatório ou se a documentação está errada. Parece que só vamos descobrir quando a fatura fechar no dia 29.

@filipedeschamps
Copy link
Owner Author

Em paralelo, a thread no Hacker News:

https://news.ycombinator.com/item?id=40682711

E isso me chamou atenção:

https://news.ycombinator.com/item?id=40685579

@EduContin
Copy link

Possível alternativa para migração (Coolify)

Recentemente me deparei com o projeto Coolify no GitHub (https://github.com/coollabsio/coolify). Promete ser uma substituição self-hosted e open-source da Vercel. Muito massa!

@filipedeschamps
Copy link
Owner Author

Mais informações sobre o Coolify (é bem interessante mesmo):

https://www.youtube.com/watch?v=SANSysQlS18

https://www.youtube.com/watch?v=ZZ1lnw8D3Qo

Outra alternativa:

https://sst.dev/

@repires
Copy link

repires commented Jul 9, 2024

Outras alternativas:

CapRover: https://www.youtube.com/watch?v=2d2U8tRBFyM

Dockploy: https://www.youtube.com/watch?v=QAQyRyUXfMs

Kubero: https://www.youtube.com/watch?v=3hvdqQoxU70

@filipedeschamps
Copy link
Owner Author

Apenas para deixar registrado:

image

@aprendendofelipe depois quando fechar o billing cycle, seria legal se você trouxesse alguns prints da interface lá da Vercel com o uso

@aprendendofelipe
Copy link
Collaborator

Trago os valores aqui sim... 🤝

Eu estou acompanhando e está mais ou menos como previsto. Devemos ficar abaixo dos $300 🎉

@aprendendofelipe
Copy link
Collaborator

O ciclo de faturamento fechou hoje e o custo veio zerado por ser menor do que os $300 de desconto. Só que, por ter fechado zerado, omitiram os valores que seriam cobrados se o desconto não existisse. ☹️

Até onde vi antes do fechamento, estava um pouco acima de $200. Vou tentar salvar os dados parciais quando estiver perto do fim do novo ciclo.

A utilização aparece assim:

Produto Uso
Fast Data Transfer 45,79 GB
Fast Origin Transfer 201,64 GB
Edge Requests 6.9M
Edge Request CPU Duration 10 m
Data Cache Reads 9.5M
Data Cache Writes 21M
Function Invocations 2M
Function Duration 168.6 GB-Hrs
Edge Middleware Invocations 4.8M
Log Volume 12,86 GB
Web Analytics Events 291K
Bandwidth 45,79 GB

@filipedeschamps
Copy link
Owner Author

Edge Requests | 6.9M
Edge Middleware Invocations | 4.8M

Não sei qual a diferença entre os dois, mas doidera demais esses valores.

Fast Origin Transfer | 201,64 GB

Como que a gente pode tá transferindo tantos GB?


Em paralelo, até hoje eu tenho problema de entrar numa página e não conseguir pegar o cache dela atualizado :( Inclusive me fez criar o costume de nunca confiar numa página ao entrar, eu sempre espero alguns segundos e dou refresh uma ou duas vezes (por exemplo, se vejo que na lista apareceu que tem comentário, mas dentro não mostra nada).

@aprendendofelipe
Copy link
Collaborator

Edge Requests | 6.9M
Edge Middleware Invocations | 4.8M

Não sei qual a diferença entre os dois, mas doidera demais esses valores.

Um é o total de requisições e o outro é apenas o que deu match com /((?!_next/static|va/|favicon|manifest).*).

Fast Origin Transfer | 201,64 GB

Como que a gente pode tá transferindo tantos GB?

Cada atualização de cache é replicada para diferentes partes da Edge. A estratégia atual de ficar revalidando mesmo se nada mudou acaba fazendo o tráfego de Vercel para Vercel ser maior do que da Vercel para os Clients.

Em paralelo, até hoje eu tenho problema de entrar numa página e não conseguir pegar o cache dela atualizado :( Inclusive me fez criar o costume de nunca confiar numa página ao entrar, eu sempre espero alguns segundos e dou refresh uma ou duas vezes (por exemplo, se vejo que na lista apareceu que tem comentário, mas dentro não mostra nada).

Dado que o custo de ficar revalidando o cache a todo momento ficou inviável, vamos precisar revalidar apenas as páginas que mudarem, o que implica já revalidar tudo que for necessário quando algo mudar, então não devemos ter mais inconsistências devido ao cache desatualizado. Bom, ainda vai acontecer para quem usa a API, mas não mais nas páginas estáticas.

@aprendendofelipe
Copy link
Collaborator

Complementando sobre o total de requisições...

Aquele é o número que chegou na Vercel (com ou sem cache da Vercel), mas o total de requisições atendidas pela Cloudflare é um pouco maior, pois também tem o nível de cache deles.

Nos últimos 30 dias a Cloudflare atendeu 7.91M de requisições. Não é exatamente o mesmo intervalo de tempo, mas serve como comparação.

@felipechiodini
Copy link

Outras alternativas:

CapRover: https://www.youtube.com/watch?v=2d2U8tRBFyM

Dockploy: https://www.youtube.com/watch?v=QAQyRyUXfMs

Kubero: https://www.youtube.com/watch?v=3hvdqQoxU70

tenho um servidor rodando esse: https://easypanel.io/

@Rafatcb
Copy link
Collaborator

Rafatcb commented Aug 21, 2024

Nossos custos com a Vercel em Analytics, em Junho, foram de US$ 56 (#1724 (comment)), isso porque não coletamos eventos na página principal e na /publicar por causa da cota da conta Pro. Esse custo se deu porque:

  • 326k visitas = 25k visitas inclusas + 4 * 100k visitas = 4 * US$ 14 = US$ 56

Uma alternativa super simples para diminuir esse custo é usar o Web Analytics Plus, que custa US$ 50, mas contém 500 mil eventos inclusos, então só de ativar já teríamos uma economia, caso todos os meses tenham mais de 325k visitas. Não tenho certeza se é simples assim porque, se for, parece não fazer nenhum sentido usar o Web Analytics normal no plano Pro gerando 325k à 500k eventos.

Outra outra plataforma, como o Tinybird. A Vercel usa o Tinybird para analytics. Eu fiz alguns testes e publiquei uma comparação da Vercel x Tinybird no TabNews: Criei uma API para substituir o Vercel Analytics.

Pelas minhas estimativas, é possível economizar usando o Tinybird, além de ter uma flexibilidade maior sobre quais dados coletar, por quanto tempo armazenar e, principalmente, testar aos poucos. O custo do Tinybird não será uma nova conta de US$ 50 assim que decidirmos usar, pois ele cobra por armazenamento e processamento de dados. Isso permite usarmos o Tinybird lado a lado do Analytics da Vercel para conseguir estimar os custos no nível de consumo do TabNews, e também desativar o Tinybird a qualquer momento sem pagar uma conta alta.

Acredito que as proteções que precisaremos no endpoint de coleta de analytics seriam as mesmas que já precisamos hoje, para não abrir a torneira de custos da Vercel.

O risco de testar o Tinybird é baixo, e o custo inicial iria para o lado do tempo de desenvolvimento da solução.

Também existe a opção de usar um banco de dados sem adicionar mais uma plataforma, ou seja, usar diretamente o nosso banco na AWS. Imagino que isso seja ainda melhor do que usar o Tinybird, mas aqui já não tenho familiaridade para estimar custos; talvez outra pessoa possa confirmar se faz sentido. A implementação deve ser parecida com o que seria necessário para implementar o Tinybird, mudando apenas a parte de leitura e escrita no banco, e também a agregação de dados para consumir menos recursos (no Tinybird, isso se dá por meio de materialized views).

Outro ponto positivo de nós termos esse tipo de solução mais flexível é que a mesma solução poderia ser usada para contar visitas em publicações (#1115).

@aprendendofelipe
Copy link
Collaborator

O ciclo de faturamento fecha amanha e já estamos com $291.14. Se não passar dos $300, teremos 100% de desconto.

No ciclo anterior, não ficaram visíveis os valores individuais de cada recurso depois do fechamento, mas agora disponibilizaram. Por desencargo, já estou salvando aqui os valores parciais.

Ago/24 Parcial

Utilização de recursos da Vercel em ago 24

Jul/24

Utilização de recursos da Vercel em jul 24

@Rafatcb
Copy link
Collaborator

Rafatcb commented Sep 1, 2024

Para reduzir o custo, acho que a primeira coisa que teremos que fazer, talvez a única com impacto relevante, será mudar a forma que usamos o cache, diminuindo as revalidações baseadas em tempo e passando para revalidações sob demanda.

Eu refleti e pesquisei sobre como são as revalidações sob demanda. Usando o Pages Router, parece que a forma de fazer isso é chamando await res.revalidate('/pagina/para/revalidar/1'). Em várias situações, isso fica complexo. Por exemplo, ao criar uma nova publicação, precisaria revalidar todas páginas /recentes/pagina/... e /recentes/todos/..., visto que a posição de todas publicações mudaria. Não parece ter uma forma simples de fazer isso.

Se formos considerar a página Relevantes, que dificilmente existem mais do que 5 páginas, então uma forma simples inicial seria revalidar apenas X páginas quando alguma ação específica ocorre. Porém, são várias as ações que podem demandar a revalidação: novo conteúdo, voto, mudança de título da publicação, mudança de slug, remoção do conteúdo, banimento de usuário, mudança de nome do usuário e talvez outras coisas. Além disso, o tempo também influencia na posição das publicações relevantes. Não tenho certeza se é possível utilizar os dois tipos de revalidação na mesma página porque o comportamento no cache miss é diferente.

Em páginas de usuários ou das próprias publicações, é mais simples, mas ainda existem várias ações que demandam a revalidação. Acho que as páginas de conteúdos seriam as "mais simples" dentre as que podem ter um impacto relevante na métrica Data Cache Writes.

O App Router tem algumas facilidades para a revalidação, como as funções revalidatePath e revalidateTag, mas ainda precisaríamos da mesma lógica condicional para entender o que revalidar.

Na documentação do App Router, tem um artigo bem extenso sobre o assunto e que inclui vários diagramas exemplificando as situações possíveis: Building Your Application: Caching (cache miss, hit, stale, fetches etc.).

É esse o caminho mesmo? É tanta coisa a considerar que não parece certo 🤔


Aproveitando, é possível estimar o quanto o prefetch dos links está influenciando em algum consumo dentre os valores citados? Eu acho o prefetch no hover exagerado. Não sei o quão mais lento o site ficaria se o removêssemos, e quanto economizaríamos.

De vez em quando eu navego para uma página e a página anterior "recarrega" como se eu tivesse visitado ela novamente, não sei se é algum efeito colateral de quando o prefetch é mais lento do que a navegação ou se é algum outro bug.

@filipedeschamps
Copy link
Owner Author

@repires
Copy link

repires commented Oct 2, 2024

Vercel vs Cloudflare
Edge requests:
https://www.youtube.com/watch?v=xDu-wACrJlc

@filipedeschamps
Copy link
Owner Author

To com uma baita vontade de subir uma infra própria, banco local, só pra ver o que vai dar. Fico imaginando que máquina conseguimos numa Hetzner da vida por USD 40.

@repires
Copy link

repires commented Oct 21, 2024

Free tier cloudflare

https://youtu.be/DJtOn_Vt1uw?si=50AT5ihuzXBiuRlZ

@luanfvieira
Copy link

Self-Hosting Next.js

https://youtu.be/sIVL4JMqRfc

@gpoleszuk
Copy link

gpoleszuk commented Dec 3, 2024

Pensei na mesma ideia, luanfvieira. Outra ideia seria criar uma interface estática assim como temos no hackernews, algo simples, sem muita inteligência consumindo banda por detrás (telemetria, ações que ocorrem quando o mouse paira sobre um tópico, etc.). A ideia de um sistema distribuído não seria tão ruim também. De todas as vezes que acesso o Tabnews, sempre vejo pelo menos umas 6 conexões ativas na área de Status. Não faço ideia quantos megabytes o Tabnews tem de publicações, mas seria possível replicar seu conteúdo em diferentes nodos que queiram manter um PC ligado pelo menos parte do dia para sincronização? Já viram alguma ideia semelhante de, em vez concentrar o conteúdo do banco de dados em um único servidor, distribuí-lo para nodos voluntários, ficando o servidor central (um PC na Filipe Deschamps Enterprise) responsável por prover a interface e hashes para as assinatura das publicações?

@yvesmariano
Copy link

yvesmariano commented Dec 6, 2024

Self-Hosting Next.js

https://youtu.be/sIVL4JMqRfc

Interessante como tem um vídeo oficial da Vercel sobre como hospedar você mesmo uma aplicação Next.js e de como o framework trouxe melhorias na sua nova versão 15 para aplicações hospedadas em infraestrutura própria. Acredito que isso é um movimento de mercado na busca por soluções mais eficientes em questão de custo benefício, ao mesmo tempo que a Vercel tenta mostrar como sua plataforma é developer friendly.

PS: Vou deixar aqui para quem não leu ainda as histórias de terror do serverless.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refatoração Melhoria no código que não modifica o comportamento externo
Projects
None yet
Development

No branches or pull requests

10 participants