Armazenamento de dados escalável: SQL vs NoSQL
Escolha entre bancos de dados relacionais, armazenamentos de chave-valor, bancos de dados de documentos e armazenamentos de colunas largas com base nos padrões de acesso, na consistência e nos requisitos de escala.
Armazenamento de dados escalável: SQL vs NoSQL é uma aula grátis de DSA Interview Prep no CoddyKit. Esta é a aula 2 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de DSA Interview Prep, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de DSA Interview Prep inclui 4 aulas no total.
A escolha do armazenamento envolve um compromisso
Escolher um armazenamento de dados é uma das decisões de maior impacto no design de sistemas. Nenhum banco de dados é universalmente melhor — cada tipo é otimizado para diferentes padrões de acesso, garantias de consistência e características de escala. Fazer a escolha errada em produção pode causar meses de trabalho difícil de migração.
Nas entrevistas, os entrevistadores avaliam se você entende as diferenças fundamentais e consegue associar um mecanismo de armazenamento aos requisitos de um problema. A pergunta nunca é “qual é melhor?”, mas sim “qual é melhor para esta carga de trabalho específica?”. Sempre justifique sua escolha com requisitos específicos.
# Storage decision matrix summary
factors = [
'Data structure (tabular, documents, key-value, graph, time-series)',
'Read vs write ratio (read-heavy, write-heavy, balanced)',
'Query patterns (point lookups, range scans, aggregations, joins)',
'Consistency requirements (ACID vs eventual consistency)',
'Scale requirements (single node, sharding, global distribution)',
'Latency requirements (milliseconds vs microseconds)',
'Team familiarity and operational complexity',
]
print('Key factors for storage selection:')
for f in factors:
print(f' - {f}')Bancos de dados relacionais (SQL): pontos fortes
Bancos de dados relacionais (PostgreSQL, MySQL, SQLite) armazenam dados em tabelas com esquemas fixos e oferecem transações ACID — Atomicidade, Consistência, Isolamento e Durabilidade. Eles são excelentes para consultas complexas com junções, agregações e filtros, o que os torna ideais para dados estruturados com relações bem definidas.
Principais pontos fortes: consultas complexas em várias tabelas por meio de SQL, restrições de chave estrangeira para garantir a integridade dos dados, indexação avançada (árvore B, hash e texto completo) e um ecossistema maduro com replicação e cópias de segurança. Use SQL quando seus dados forem altamente relacionais, a consistência for crítica e as consultas forem complexas e variadas.
# SQL excels at: complex queries, joins, transactions
# Example: find top 5 products by revenue this month
sql_query = '''
SELECT p.name, SUM(oi.quantity * oi.price) AS revenue
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.created_at >= DATE_TRUNC('month', NOW())
GROUP BY p.id, p.name
ORDER BY revenue DESC
LIMIT 5;
'''
print('SQL shines for relational queries with JOINs:')
print(sql_query)
print('ACID guarantees example (transfer $100 between accounts):')
transfer_sql = '''
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- either both succeed or neither does
'''
print(transfer_sql)Desafios de escalabilidade do SQL
Os bancos de dados SQL escalam verticalmente de forma natural (servidor maior, mais CPU/RAM), mas a escalabilidade horizontal é complexa. As réplicas de leitura lidam com cargas predominantemente de leitura ao encaminhar as leituras para nós de réplica e as gravações para o nó primário. Porém, a taxa de gravação fica limitada a um único nó primário, a menos que você adicione fragmentação.
A fragmentação particiona os dados entre várias instâncias de banco de dados por meio de uma chave de fragmentação (por exemplo, o identificador do usuário módulo N). Isso permite escalar as gravações, mas impede junções e transações entre fragmentos — dois dos recursos mais importantes do SQL. A maioria das aplicações web deixa de atender às necessidades de uma única instância SQL quando chega a cerca de 5–10 TB de dados ou aproximadamente 100 mil gravações por segundo.
# SQL scaling strategies
strategies = {
'Read replicas': {
'how': 'One primary (writes), multiple replicas (reads)',
'scales': 'Read throughput (10x+)',
'limit': 'Write throughput still bounded by single primary',
},
'Connection pooling (PgBouncer)': {
'how': 'Pool of persistent DB connections shared among app servers',
'scales': 'Connection count (PostgreSQL max ~500 connections)',
'limit': 'Does not increase query throughput',
},
'Horizontal sharding': {
'how': 'Partition rows by shard key across N database instances',
'scales': 'Both reads and writes (N×)',
'limit': 'Cross-shard joins and transactions broken; complex routing',
},
'CQRS': {
'how': 'Separate write model (SQL) from read model (denormalised/NoSQL)',
'scales': 'Optimise each path independently',
'limit': 'Eventual consistency between write and read models',
},
}
for strategy, info in strategies.items():
print(f'{strategy}:\n How: {info["how"]}\n Scales: {info["scales"]}\n Limit: {info["limit"]}\n')Armazenamentos de chave-valor: Redis e DynamoDB
Armazenamentos de chave-valor guardam dados como pares de chave → valor, com leitura e gravação pela chave em O(1). Eles sacrificam a flexibilidade das consultas em favor de desempenho extremo e escalabilidade horizontal. O Redis (em memória) alcança latência de microssegundos; o DynamoDB (gerenciado) alcança latência de poucos milissegundos, com escalabilidade automática para qualquer taxa de transferência.
Use armazenamentos de chave-valor para: armazenamento de sessões, armazenamento em cache, sinalizadores de funcionalidades, mapeamentos de encurtadores de URL, carrinhos de compras e classificações em tempo real. Não os use quando precisar de: consultas complexas, relações entre entidades ou filtragem ad hoc — só é possível fazer pesquisas por uma chave exata.
# Key-value store use cases
kv_operations = {
'GET key': 'O(1) point lookup — the core operation',
'SET key value': 'O(1) insert or update',
'DEL key': 'O(1) delete',
'EXPIRE key ttl': 'Set time-to-live; key auto-deleted after ttl seconds',
'INCR key': 'Atomic increment — useful for counters and rate limiting',
'LPUSH/LRANGE': 'List operations — useful for queues and recent-items feeds',
'ZADD/ZRANGE': 'Sorted set — leaderboards, rate limiting with sliding window',
}
print('Redis operation set:')
for op, desc in kv_operations.items():
print(f' {op:25s}: {desc}')
print('\nDynamoDB vs Redis:')
print(' Redis: microsecond latency, in-memory, needs persistence config')
print(' DynamoDB: single-digit ms, managed, auto-scaling, durable by default')Armazenamentos de documentos: MongoDB
Armazenamentos de documentos (MongoDB, Couchbase) guardam dados como documentos semelhantes a JSON, permitindo esquemas flexíveis — os campos podem ser diferentes entre documentos da mesma coleção. Eles oferecem indexação em qualquer campo e consultas razoavelmente abrangentes (filtros, projeções e agregações), embora as transações envolvendo vários documentos sejam mais limitadas do que no SQL.
Os armazenamentos de documentos são adequados para aplicações nas quais: o formato dos dados varia conforme a entidade (perfis de usuários com atributos diferentes), é esperada uma evolução rápida do esquema (empresas iniciantes que alteram os modelos de dados com frequência) ou os padrões de leitura se concentram na recuperação de entidades inteiras, em vez de junções entre tabelas.
# MongoDB document example
user_doc = {
'_id': 'user123',
'name': 'Alice',
'email': 'alice@example.com',
'preferences': {
'theme': 'dark',
'language': 'en',
'notifications': ['email', 'push']
},
'addresses': [
{'type': 'home', 'city': 'Berlin', 'country': 'DE'},
{'type': 'work', 'city': 'Munich', 'country': 'DE'}
],
'subscription_tier': 'pro',
# Note: not all users have all fields -- flexible schema!
}
import json
print('Document structure (flexible schema):')
print(json.dumps(user_doc, indent=2))
print('\nDocument store strengths:')
print(' - Nested/array fields without joins')
print(' - Flexible schema (different fields per document)')
print(' - Scales horizontally by sharding on _id')Armazenamentos de colunas largas: Cassandra
Armazenamentos de colunas largas (Apache Cassandra, HBase) guardam dados em linhas e colunas, mas permitem que cada linha tenha um conjunto diferente de colunas. Eles são projetados para uma taxa de gravação massiva distribuída entre muitos nós, tendo a consistência eventual como padrão. O Cassandra alcança escalabilidade linear de gravação — dobrar o número de nós dobra a taxa de gravação.
O compromisso é que as consultas precisam ser projetadas em torno da chave de partição. Não é possível filtrar nem fazer sort com eficiência em colunas arbitrárias — primeiro, você precisa definir o padrão de query e, depois, projetar a tabela para corresponder a ele. Isso é o oposto da abordagem do SQL: “projetar os dados e depois escrever qualquer consulta”.
# Cassandra table design for time-series events
# Design around the query: 'give me all events for user X, most recent first'
cassandra_table = '''
CREATE TABLE user_events (
user_id UUID,
event_time TIMESTAMP,
event_type TEXT,
metadata MAP<TEXT, TEXT>,
PRIMARY KEY (user_id, event_time)
) WITH CLUSTERING ORDER BY (event_time DESC);
-- Query (matches partition key exactly):
SELECT * FROM user_events WHERE user_id = ? LIMIT 100;
'''
print('Cassandra wide-column design:')
print(cassandra_table)
print('Properties:')
print(' - user_id = partition key (all rows for one user on same node)')
print(' - event_time = clustering key (sorted within partition)')
print(' - Very fast writes: append-only, no locking')
print(' - Cannot query by event_type alone (no partition key)')Teorema CAP: consistência, disponibilidade e tolerância a particionamento
O teorema CAP afirma que um sistema distribuído pode garantir no máximo duas destas propriedades: Consistência (toda leitura retorna a gravação mais recente), Adisponibilidade (toda solicitação recebe uma resposta sem erro) e Particionamento tolerante (o sistema continua operando apesar de partições de rede).
Como as partições de rede sempre ocorrem em sistemas distribuídos, a escolha real é entre CP (prioriza a consistência, podendo rejeitar solicitações durante uma partição) e AP (prioriza a disponibilidade, podendo retornar dados desatualizados). Os bancos de dados SQL geralmente são CP; Cassandra e DynamoDB são AP (com consistência eventual por padrão). Redis Cluster é CP.
# CAP theorem applied to common databases
databases = {
'PostgreSQL (single node)': {'C': True, 'A': True, 'P': False, 'note': 'Not distributed; CA'},
'PostgreSQL (multi-AZ)': {'C': True, 'A': False, 'P': True, 'note': 'CP: primary fails over, brief downtime'},
'MySQL Cluster': {'C': True, 'A': False, 'P': True, 'note': 'CP'},
'Cassandra': {'C': False, 'A': True, 'P': True, 'note': 'AP: eventual consistency default'},
'DynamoDB (default)': {'C': False, 'A': True, 'P': True, 'note': 'AP: eventual consistency'},
'DynamoDB (strong read)': {'C': True, 'A': False, 'P': True, 'note': 'CP: strongly consistent reads'},
'Redis Cluster': {'C': True, 'A': False, 'P': True, 'note': 'CP'},
'MongoDB (default)': {'C': True, 'A': False, 'P': True, 'note': 'CP: reads from primary'},
}
for db, caps in databases.items():
c_str = 'C' if caps['C'] else '-'
a_str = 'A' if caps['A'] else '-'
p_str = 'P' if caps['P'] else '-'
print(f'{db:35s} [{c_str}{a_str}{p_str}] {caps["note"]}')Escolhendo o armazenamento: uma estrutura de decisão
Uma estrutura prática para decidir o armazenamento em entrevistas:
- Precisa de transações ACID? → Banco de dados relacional (PostgreSQL, MySQL)
- Precisa de pesquisas em menos de um milissegundo ou de armazenamento em cache? → Armazenamento de chave-valor (Redis)
- Precisa de um esquema flexível ou de dados orientados a documentos? → Armazenamento de documentos (MongoDB)
- Precisa de uma taxa de gravação massiva (>100 mil/s) com dados de séries temporais ou de eventos? → Armazenamento de colunas largas (Cassandra)
- Precisa de distribuição global com escalabilidade gerenciada? → DynamoDB ou Cosmos DB
- Precisa percorrer grafos? → Banco de dados de grafos (Neo4j)
# Decision tree in code form
def choose_storage(needs_acid, high_write_throughput, flexible_schema,
sub_ms_latency, graph_queries, global_scale):
if sub_ms_latency:
return 'Redis (in-memory key-value)'
if graph_queries:
return 'Neo4j (graph database)'
if needs_acid:
return 'PostgreSQL / MySQL (relational)'
if high_write_throughput and not flexible_schema:
return 'Cassandra (wide-column, write-optimised)'
if flexible_schema:
return 'MongoDB (document store)'
if global_scale:
return 'DynamoDB or Cosmos DB (managed global KV/document)'
return 'PostgreSQL (safe default for most web apps)'
# Example scenarios
scenarios = [
{'needs_acid': True, 'high_write_throughput': False, 'flexible_schema': False,
'sub_ms_latency': False, 'graph_queries': False, 'global_scale': False},
{'needs_acid': False, 'high_write_throughput': True, 'flexible_schema': False,
'sub_ms_latency': False, 'graph_queries': False, 'global_scale': False},
{'needs_acid': False, 'high_write_throughput': False, 'flexible_schema': False,
'sub_ms_latency': True, 'graph_queries': False, 'global_scale': False},
]
for s in scenarios:
print(f'{choose_storage(**s)}')Persistência poliglota: usando vários armazenamentos
Os sistemas de produção raramente usam um único banco de dados para tudo. Persistência poliglota significa usar a tecnologia de armazenamento adequada para cada parte do sistema. Uma aplicação web típica pode usar: PostgreSQL para os dados de referência de usuários e pedidos, Redis para armazenamento de sessões e armazenamento em cache, Elasticsearch para pesquisa de texto completo, S3 para armazenamento de arquivos e Cassandra para registros de eventos e análises.
O compromisso é que a complexidade operacional aumenta a cada tipo de banco de dados. A equipe precisa manter, monitorar e criar cópias de segurança de vários sistemas. Justificativa do projeto: em grande escala, os ganhos de desempenho e escalabilidade obtidos ao associar cada carga de trabalho ao armazenamento adequado compensam o custo operacional.
# Polyglot persistence in an e-commerce system
components = {
'User accounts, orders, payments': {
'storage': 'PostgreSQL',
'reason': 'ACID transactions (payment integrity), complex queries',
},
'Product catalogue': {
'storage': 'MongoDB or PostgreSQL with JSONB',
'reason': 'Flexible product attributes vary by category',
},
'Session tokens': {
'storage': 'Redis (with TTL)',
'reason': 'O(1) lookup, automatic expiry, high throughput',
},
'Product search': {
'storage': 'Elasticsearch',
'reason': 'Full-text search, faceted filtering, relevance scoring',
},
'Activity/event log': {
'storage': 'Cassandra or Kafka + S3',
'reason': 'High write throughput, append-only, time-series queries',
},
'Product images / videos': {
'storage': 'S3 + CloudFront CDN',
'reason': 'Cheap object storage, global distribution via CDN',
},
}
for component, info in components.items():
print(f'{component}:\n {info["storage"]}: {info["reason"]}\n')Estratégia de indexação entre tipos de armazenamento
Todos os sistemas de armazenamento usam índices para acelerar as leituras, ao custo de gravações mais lentas e de espaço de armazenamento adicional. Entender a indexação entre os tipos de armazenamento é fundamental para o design de sistemas:
- SQL: índice de árvore B em qualquer coluna; índices compostos para consultas com várias colunas; índices de cobertura para evitar acessos à tabela
- MongoDB: índice em qualquer campo; índices compostos; índices TTL para expiração automática
- Cassandra: somente a chave de partição e as colunas de agrupamento são indexadas nativamente; índices secundários têm custo alto
- Redis: conjuntos ordenados como índices para consultas por intervalo; nenhuma indexação tradicional é necessária para armazenamentos de chave-valor
# Indexing examples across storage types
# PostgreSQL: B-tree composite index
postgres_index = '''
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC);
-- Optimises: SELECT * FROM orders WHERE user_id=? ORDER BY created_at DESC
'''
# MongoDB: compound index
mongo_index = '''
db.products.createIndex({ category: 1, price: -1 })
// Optimises: db.products.find({category:'Electronics'}).sort({price:-1})
'''
# Cassandra: cluster key ordering (built into table design)
cassandra_index = '''
-- No separate index needed; clustering key IS the index:
PRIMARY KEY (user_id, event_time) WITH CLUSTERING ORDER BY (event_time DESC)
'''
print('PostgreSQL:', postgres_index)
print('MongoDB:', mongo_index)
print('Cassandra:', cassandra_index)SQL versus NoSQL em entrevistas: o que dizer
Quando perguntarem “SQL ou NoSQL?” em uma entrevista de design de sistemas, nunca dê uma resposta de uma palavra. Em vez disso, siga esta estrutura:
- Declare a carga de trabalho: “Esta carga é predominantemente de leitura e tem filtragem complexa, então…”
- Declare o requisito: “Precisamos de consistência forte para transações financeiras, então…”
- Declare a escolha: “Eu usaria PostgreSQL com réplicas de leitura”
- Declare o compromisso: “O compromisso é que a escalabilidade horizontal das gravações exige fragmentação, o que adiciona complexidade”
- Mencione uma alternativa: “Se as gravações fossem mais numerosas, poderíamos considerar DynamoDB”
# Sample answer structure for 'SQL or NoSQL?'
def answer_storage_question(workload, consistency_need, scale):
print(f'Workload: {workload}')
print(f'Consistency: {consistency_need}')
print(f'Scale: {scale}')
print()
if 'financial' in workload.lower() or consistency_need == 'strong':
choice = 'PostgreSQL (ACID, strong consistency)'
tradeoff = 'Horizontal write scaling requires sharding'
alternative = 'Google Spanner for global transactions'
elif 'event' in workload.lower() or 'log' in workload.lower():
choice = 'Cassandra (high write throughput, time-series)'
tradeoff = 'Eventual consistency; queries limited to partition key'
alternative = 'Kafka + S3 for long-term event archival'
else:
choice = 'DynamoDB (managed, auto-scale, low latency)'
tradeoff = 'Limited query flexibility; cross-item transactions limited'
alternative = 'PostgreSQL if complex queries emerge'
print(f'Choice: {choice}\nTrade-off: {tradeoff}\nAlternative: {alternative}')
answer_storage_question('Social media feed', 'eventual', '10M users')Verificação rápida
Teste sua compreensão dos conceitos de Estruturas de Dados & Algoritmos — Preparação para Entrevistas de Programação abordados nesta lição.
Recapitulação da lição
Nesta lição, você aprendeu que: os bancos de dados SQL oferecem transações ACID e consultas complexas, mas têm dificuldade para escalar as gravações, enquanto os bancos de dados NoSQL trocam consistência ou flexibilidade de consulta por escala e disponibilidade extremas, o teorema CAP obriga os sistemas distribuídos a escolher entre consistência e disponibilidade durante partições de rede e os sistemas de produção normalmente usam persistência poliglota — associando cada carga de trabalho ao mecanismo de armazenamento adequado. A seguir, exploraremos camadas de armazenamento em cache, redes de distribuição de conteúdo e balanceamento de carga para escalar ainda mais os sistemas com muitas leituras.
Perguntas Frequentes
A aula “Armazenamento de dados escalável: SQL vs NoSQL” é grátis?
Sim — o texto completo de “Armazenamento de dados escalável: SQL vs NoSQL” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de DSA Interview Prep, atualize para CoddyKit PRO. O curso de DSA Interview Prep inclui 4 aulas no total.
O que vou aprender em “Armazenamento de dados escalável: SQL vs NoSQL”?
Escolha entre bancos de dados relacionais, armazenamentos de chave-valor, bancos de dados de documentos e armazenamentos de colunas largas com base nos padrões de acesso, na consistência e nos requis… Você pratica DSA Interview Prep com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar DSA Interview Prep?
Nenhuma experiência prévia é necessária. DSA Interview Prep no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 4.
Quanto tempo leva a aula “Armazenamento de dados escalável: SQL vs NoSQL”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de DSA Interview Prep?
Sim. Cada aula de DSA Interview Prep inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- A estrutura da entrevista de projeto de sistemas
- Armazenamento de dados escalável: SQL vs NoSQL
- Cache, CDNs e balanceamento de carga
- Projetar um limitador de taxa e um feed do Twitter