Re-arquitetando o Stack Overflow
ou como construímos o Stack Overflow for Teams
Roberta Arcoverde
/whois
- recifense
- programadora há 15 anos
- principal software developer na stack overflow
- co-host do hipsters.tech
- @rla4
- desde 2008
- 50+ milhões de usuários únicos/mês
- 18 milhões de perguntas
- 27 milhões de respostas
- top 50 sites mais acessados do mundo
- 3k Teams criados, 50k usuários
- 10 meses em desenvolvimento
- lançado em maio/2018
- equipe tinha originalmente 3 devs, agora são 7
- melhor nome de time da história: Teams Team 😎
https://stackoverflow.com/c/demo
https://stackoverflow.com
>170 sites
números do dia 03/05
- 278.912.108 HTTP requests
- 67.188.355 page views
- 3.506.670.995.363 bytes (3.5 TB) enviados
- 953.860.308 SQL queries executadas
- 5.250.697.564 redis hits
- 600.000 websockets ativos
-
19ms de tempo de renderização da Question page
- 54.290.431 page views, ou 80% do total
- 123ms de tempo de renderização geral
9 WEB SERVERS
4 SQL SERVERS
LIVE
HOT STANDBY
LIVE
HOT STANDBY
Stack Exchange, Meta, Talent
Stack Overflow
~350 req/s
por servidor
528 M queries/dia
498 M queries/dia
~5% CPU
imagem gentilmente cedida por Marco (@sklivvz) em http://www.slideshare.net/howtoweb/marco-cecconi-stack-overflow-architecture
como?
spoilers: é boring
performance é uma feature
tech stack
- c#
- asp.net mvc*
- sql server
- dapper, ef core
- typescript
- vanilla
- redis
- elasticsearch
- ha proxy
*migrando pra .NET Core
🤷♀️
multi tenant application
- um único app pool para todos os sites
- roteado via host headers
Q&A pra dados privados?
(o nome original do SO for Teams era Channels)
nasce uma ideia! (sim, o screenshot é legítimo)
times são sites que existem dentro do Stack Overflow
tratá-los como se fossem novos sites na rede, porém visíveis apenas a partir do
public class Post {
public int Id { get; }
public string Title { get; }
public int? TeamId { get; }
...
}
// reusar banco
// criar novo código
public class Post {
public int Id { get; }
public string Title { get; }
...
}
// criar novo banco
// reusar código
[StackRoute("help/search-inline")]
public async Task<ActionResult> SearchInline(string q)
{
var searchSite = GetSearchSite();
var results = await searchSite.HelpPostIndex.SearchAsync(searchSite, q);
var sm = new SearchModel
{
SearchString = q,
Results = results
};
return PartialView("~/Views/Help/SearchInline.cshtml", sm);
}
https://stackoverflow.com/help/search-inline
https://askubuntu.com/help/search-inline
https://stackoverflow.com/c/demo/help/search-inline
Modelo
Segurança
Escalabilidade
evitar forks, DRY, minimizar alterações no core do projeto
default private, mudança de mindset, crash na aplicação > vazamento de dados
capacity planning, o que acontece se tivermos 1k, 10k, 100k times?
👍
- Bases isoladas entre Teams
- Dados isolados dos dados públicos
- Mínimo de alterações no código (usar modelo existente pra novos sites)
👎
-
Escalabilidade. AG distribuídos começam a degradar rapidamente a partir de 1k bancos
-
Hardware e instrumentação para gerenciar milhares de bases de dados
Plano A: um banco para cada Team
👍
- Escalabilidade
- Dados isolados dos dados públicos
👎
- Sem isolamento entre Teams
- Reescrever boa parte das consultas
- Consultas não são mais as mesmas para sites vs Teams
Plano B: um banco para todos os Teams
👍
- Dados isolados entre Teams
- Dados isolados dos dados públicos
-
Escalabilidade é... decente
-
Baixo custo de reescrita
👎
-
Precisamos escrever infra de provisionamento dinâmico
Plano C: um schema por time no mesmo banco
basicamente: saindo de 170 para 10k+ sites
- SQL Server
- 1 banco per-site
- 1 banco pra todos os Teams, 1 schema per-Team
- Elasticsearch
- 1 índice per-site
- 1 índice per-team, até 5k
- Provisionamento
- tarefa agendada cria sempre um buffer de 100 schemas para futuros Teams
Escalabilidade
- onde manter os dados dos Teams?
- como comunicar o site público com o Team?
- migrar *tudo* pra lugares seguros
- notificações
- emails
- monitoramento
- internal API
- websockets
- tags
Segurança
como as redes se comunicam?
Proxying
- Já usávamos no /jobs
- Requisição é "clonada" e enviada para a CFZ
- Response é jogada direto no stream de saída
- 800 LoC
-
Por que não usar APIs/serviços?
- custo de serialização
- mais código, menos uniformidade
[StackRoute("c/{slug}")]
[StackRoute("c/{slug}/{*pathInfo}")]
public async Task<ActionResult> Proxy(string slug)
{
if (!Current.Settings.Channels.Enabled)
{
return PageNotFound();
}
...
if (Current.Request.IsProxied())
{
// yo dawg, I heard you like proxies so we put a proxy in your proxy
// so you can channel yo inner channels... Let's not allow this
return PageNotFound();
}
var returnUrl = Current.Request.Url.PathAndQuery;
if (!Current.SiteChannels.Contains(channelSite.Id))
{
// user does not have access to this channel
return RedirectToJoinPage();
}
...
return await this.BlindProxy(channelSite, path);
}
// BlindProxy:
// valida a requisição (authorization);
// constrói um Request;
// envia via HTTP para o Team app;
// retorna o resultado
// profit :D
// No, you can't:
// - Use a CookieCollection (it'll get headers, but not pass them here)
// - Set the Set-Cookie header on the response (ASP.Net strips it)
// - Set an additional Set-Cookie (also stripped)
// - Take the raw header and pass it (comma delimited, only the first cookie will set)
// - Use Headers.GetValues(string) (it screws up on commas)
// - Maintain your sanity working with ASP.Net and cookie headers
// Fun fact: half of the cookie BS here is supporting IIS6 and IE5. Not kidding.
if (cResponse.Headers["Set-Cookie"].HasValue())
{
var nvc = cResponse.Headers;
var result = new List<string>();
for (var i = 0; i < nvc.Count; i++)
{
if (nvc.GetKey(i) == "Set-Cookie")
{
// Don't ask. You'll cry.
var vals = nvc.GetValues(i);
if (vals != null) result.AddRange(vals);
}
}
// ...
}
lições
- entenda seus cenários de escalabilidade
- quando não souber: capacity planning
- segurança vai além de proteger dados de acesso externo
outras palestras
- instrumentação
- adaptamos todos os nossos sistemas de monitoramento pra incluir Teams
- proxy v2
- protobuf
- grpc
- structured model
- single sign-on
- re-arquitetando o modelo de autenticação e autorização
- modelo de segurança
- dados (perguntas, respostas, tags)
- metadados (traffic logs, IPs, urls)
- external endpoints (ads, APIs, emails)
obrigada!
rla4
roberta at stackoverflow.com
rla4.com
hipsters.tech
- instância privada, standalone do Stack Overflow
- SLA, priority support
- single sign-on
- on premise ou Azure
- releases trimestrais
- completamente customizável
- apropriado para grandes empresas
- $$$
QCon SP 2019
By rla4
QCon SP 2019
- 359