Author: vitorcampos

Criando databases: qual charset usar?

Uma das decisões que temos que tomar ao criar um database é: qual character set (charset) usar? A resposta a essa pergunta é crucial, já que vários bancos de dados (PostgreSQL, por exemplo) não permitem que uma tabela tenha um charset diferente do restante do banco de dados. Nos próximos parágrafos irei explicar os conceitos relacionados à codificação de caracteres e como isso afeta a sua decisão.

Antes de mais nada, um pouco de terminologia: os character sets (ex: Unicode) determinam o conjunto de caracteres disponíveis; os encodings (ex: UTF-8, ISO-8859-1) determinam como os caracteres serão convertidos em bytes; e os collates (ex: latin1_general_ci) definem como os caracteres são ordenados e comparados (ex: letras maiúsculas próximas ou separadas das equivalentes minúsculas, caracteres acentuados próximos ou separados dos equivalentes sem acento, etc). Esses conceitos causam confusão em muitas pessoas, por isso é muito comum ver ferramentas dizendo “character set” (também chamado “charset”) querendo dizer “encoding” e vice-versa, ou seja, não se apegue muito à definição desses dois termos. Para uma explicação mais detalhada sobre codificação de caracteres, sugiro que leia este artigo.

Beleza, Vitor, mas qual encoding eu devo escolher?

A escolha do encoding vai depender de quais caracteres sua aplicação vai aceitar. Se ela aceitar caracteres não ocidentais (ex: 日本語, Ελληνικά, עִברִית), emojis (ex: 🤔) ou algum outro caractere especial no banco de dados (ex: “ᴇ”, que é diferente de “E”), use UTF-8. Alguns ambientes em que isso acontece muito são chats, informações de Bio (ex: Instagram), aplicações internacionais e em tabelas que armazenem XML ou JSON, que são geralmente codificadas em UTF-8.

O encoding UTF-16 aceita os mesmos caracteres do UTF-8, só que permite que os caracteres não ocidentais geralmente ocupem menos espaço em memória do que seus equivalentes em UTF-8, porém com a desvantagem de os caracteres ASCII ocuparem 2 bytes cada, sendo que o UTF-8 ocupa apenas 1 para eles.

Para quem usa MySQL, uma pegadinha: o charset utf8 não implementa todos os caracteres Unicode disponíveis (os emojis, por exemplo, não são reconhecidos), sendo resolvido com a criação do encoding utf8mb4. Na prática, o utf8 não deve ser usado, devendo ser utilizado o utf8mb4.

Se você não precisa de nenhum desses caracteres, uma opção é usar os encodings da Europa Ocidental (ISO-8859-1, também conhecido como Latin1, ISO-8859-15 e WIN1252, também conhecido como CP1252). A vantagem desses encodings é que eles usam apenas 1 byte por caractere (UTF-8 pode usar de 1 a 4 bytes por caractere), economizando espaço em memória e disco. Esses encodings contêm praticamente todos os caracteres ocidentais, incluindo acentos, cedilha e outros caracteres usados em idiomas ocidentais (ex: ß em alemão ou ñ em espanhol). A diferença entre eles é que o ISO-8859-1 não possui o símbolo do Euro (€), além de alguns caracteres acentuados (ex: Š), apesar de ter todos os acentos e caracteres usados em português. O encoding WIN1252, por não ser um padrão ANSI (foi criado pela Microsoft), possui alguns caracteres acentuados com código diferente dos padrões ISO-8859-1 e ISO-8859-15. As diferenças entre esses encodings podem ser vistas neste link.

Mais uma pegadinha do MySQL: o charset latin1 representa o encoding WIN1252, e não o ISO-8859-1, como é o padrão, ou seja, sua aplicação não terá problemas com o caractere € e outros disponíveis em WIN1252.

O que acontece se eu escolher um encoding errado?

Se você escolher um encoding que não aceite todos os caracteres vindos das aplicações, o banco de dados pode se comportar de forma imprevisível, podendo truncar caracteres ou substituir por outros caracteres. A string “acentuação” poderia, por exemplo, ser gravada no banco de dados como “acentuao”, “acentua��o”, “acentuação” ou “acentua¿¿o”, não podendo ser recuperado posteriormente. Por conta disso, é muito importante escolher bem o encoding do seu banco de dados.

Já tenho um banco com determinado encoding e preciso mudar, e agora?

Nem sempre isso é motivo para pânico. Vários bancos de dados possuem formas de se contornar a situação nos casos de ser necessário criar uma tabela ou coluna usando um encoding diferente do padrão.

Em MySQL, por exemplo, é possível definir um charset diferente para a tabela ou mesmo para cada coluna, como no exemplo abaixo:

CREATE TABLE tabela1 (
  id INT NOT NULL,
  descricao VARCHAR(100) NOT NULL CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
) CHARACTER SET latin1 COLLATE latin1_general_ci;

No exemplo acima, a tabela terá o encoding latin1 por padrão, mas a coluna descricao terá o encoding utf8mb4.

Outros bancos de dados, como Oracle e SQL Server, possuem tipos de dados com encodings mais amplos, geralmente UTF-16. No caso do SQL Server, esse tipo é o NVARCHAR; no Oracle é o NVARCHAR2. Basta criar ou converter a coluna para permitir os caracteres adicionais.

No caso do PostgreSQL, não existe uma forma de se definir um encoding diferente para uma tabela ou campo, a única solução é efetuar um dump da base, apagar a base antiga, recriá-la em UTF-8 e restaurar o dump, como pode ser visto neste artigo. Para evitar a necessidade de uma migração futura, vale a pena criar os bancos de dados Postgres desde o início usando UTF-8.

E os collates, qual eu escolho?

A escolha de um collate vai depender de 2 fatores:

  • das regras de ordenação
  • da diferenciação de maiúsculas e minúsculas

A regra de ordenação depende do idioma de origem. Até 2010, por exemplo, o “ch” em espanhol era considerado como uma letra só e ficava entre o C e o D, ou seja, charada ficava depois de cocina, ao contrário do português, em que ele ficaria antes. No caso da língua portuguesa, não há uma regra específica, ele entra na regra geral de boa parte dos países da Europa ocidental.

Outro ponto é definir nas regras de ordenação é se os caracteres acentuados deverão ficar próximos ou depois dos caracteres sem acento (accent sensitive ou accent insensitive). Por exemplo, se a palavra “ação” tiver que ficar entre “acao” e “adorar”, ele é accent insensitive; se “ação” tiver que ficar depois dos dois, ele é accent sensitive.

A diferenciação de maiúsculas e minúsculas (case sensitive ou case insensitive) segue a mesma regra: se “acao” tiver que ficar antes de “ADORAR”, ele é case insensitive; se tiver que ficar depois (isto é, separando as maiúsculas das minúsculas), ele é case sensitive.

No caso do MySQL, por exemplo, um collate em UTF-8 com a regra geral da Europa ocidental e sem distinção entre maiúsculas e minúsculas seria o utf8mb4_general_ci.

Alguns bancos de dados, como por exemplo o Oracle, permitem uma ordenação binária, ou seja, usando os códigos ASCII/Unicode de cada caractere, o que aumenta a performance na ordenação mas obriga a usar ordenação case sensitive e pode causar problemas com a ordenação de alguns caracteres acentuados, dependendo do encoding utilizado.

Tá, resume aí que ficou complicado pra minha cabeça:

Se você quiser armazenar caracteres não ocidentais, emojis ou algum outro caractere especial no banco de dados, use UTF-8 (utf8mb4 no MySQL); se não precisar desses caracteres mas precisar do símbolo do Euro (€),  use ISO-8859-15 ou WIN1252 (também conhecido como CP1252). Se nem o caracter € for necessário, o ISO-8859-1 (também conhecido como Latin1) atende. O MySQL foge do padrão e considera o WIN1252 como Latin1.

Mesmo que sua aplicação consiga trabalhar em Latin1, vale a pena manter o banco em UTF-8 se a performance dele for melhor que a do Latin1, como acontece com o MySQL 8, por exemplo.

Já em relação ao collate, isso vai depender do encoding definido, das regras de ordenação (ex: binária, portuguesa, espanhola, etc) e se haverá distinção entre maiúsculas e minúsculas (ex: latin1_general_ci usa encoding Latin1, ordenação binária e case insensitive, ou seja, sem distinção entre maiúsculas e minúsculas).

Como escolher os melhores índices para suas queries

Uma cena muito comum é esta: logo ao lançar a primeira versão de uma nova aplicação (ou de uma nova feature), ela funcionava como um tiro, respondia muito rápido. Depois de um tempo, com o aumento do uso dessa feature, você começa a perceber no seu software de APM (ou pior, os usuários começam a reclamar) que o tempo de resposta aumentou muito.  Pesquisa mais um pouco e descobre que a culpada é uma query que está levando muito tempo para executar.

Uma das formas de resolver o problema de performance da query é criando um ou mais índices. Mas a pergunta que fica é:

Qual índice criar para otimizar minha query?

A primeira tentação é criar todos os índices que possam estar envolvidos na query, mas em muitos casos isso, além de não resolver o problema, acaba introduzindo outros: a diminuição de performance nas operações de DML (INSERTUPDATE e DELETE em SQL, insertOne/insertManyupdateOne/updateManydeleteOne/deleteMany no MongoDB), uma vez que, além de alterar a tabela/collection, é necessário atualizar os índices da mesma; e o desperdício de espaço em disco, já que o índice estaria ocupando espaço sem necessidade.

Tá, então como eu descubro quais índices criar?

A melhor forma de analisar uma query para saber quais índices criar é através da análise dos filtros da query (cláusulas WHERE em SQL, o primeiro parâmetro do método find() do MongoDB ou o argumento $match do método aggregate() do MongoDB) o do seu plano de execução. Com essas informações em mãos, você consegue saber quais colunas estão sendo filtradas e quantas linhas estão sendo analisadas.

Para consultar o plano de execução de uma query, execute um dos comandos abaixo, de acordo com o banco de dados utilizado:

-- MySQL e PostgreSQL:
EXPLAIN [sua query]

-- MySQL com saída em formato JSON (contém informações adicionais) 
EXPLAIN FORMAT=json [sua query]

-- Oracle:
EXPLAIN PLAN FOR [sua query]

-- SQL Server:

SET SHOWPLAN_XML ON;
GO

[sua query]
GO

SET SHOWPLAN_XML OFF;
GO

-- MongoDB:
db.[nome da collection].find({[filtros]}).explain()

-- Exemplo em MySQL/Postgres:
EXPLAIN SELECT * FROM cidades WHERE uf = 'ES';

-- Exemplo em MongoDB:
db.cidades.find({ "uf": "ES" }).explain()

Suponha, por exemplo, a tabela pedidos, que possui 100.000 linhas e a seguinte estrutura (banco de dados MySQL 5.7):

mysql> desc pedidos;
+------------+--------------+------+-----+---------+-------+
| Field      | Type         | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| id         | int(11)      | NO   | PRI | NULL    |       |
| id_cliente | int(11)      | NO   |     | NULL    |       |
| status     | varchar(20)  | NO   | MUL | NULL    |       |
| valor      | decimal(7,2) | NO   |     | NULL    |       |
+------------+--------------+------+-----+---------+-------+
4 rows in set (0.01 sec)

A coluna status possui 2 valores: ok (97% dos registros) e cancelado (3% dos registros).

A query que está com lentidão é essa:

SELECT * FROM pedidos WHERE status = 'cancelado';

Para analisar o plano de execução da query, rodamos o comando abaixo e recebemos o seguinte plano de execução:

mysql> EXPLAIN FORMAT=json SELECT * FROM pedidos WHERE status = 'cancelado';
+---------------------------------------------------------------------------+
| EXPLAIN                                                                   |
+---------------------------------------------------------------------------+
 { 
	"query_block": { 
		"select_id": 1, 
		"cost_info": { "query_cost": "19746.00" }, 
		"table": { 
			"table_name": "pedidos", 
			"access_type": "ALL", 
			"rows_examined_per_scan": 97605, 
			"rows_produced_per_join": 9760, 
			"filtered": "10.00", 
			"cost_info": { 
				"read_cost": "17793.90", 
				"eval_cost": "1952.10", 
				"prefix_cost": "19746.00", 
				"data_read_per_join": "381K" 
			},
			"used_columns": [ "id", "id_cliente", "status", "valor" ], 
			"attached_condition": "(`teste`.`pedidos`.`status` = 'cancelado')" 
		} 
	}
 } 
+---------------------------------------------------------------------------+

O valor ALL no campo access_type indica que não foi utilizado um índice e o campo rows_examined_per_scan indica que serão consultadas aproximadamente 97.605 linhas (é uma estimativa, não o valor real). O campo attached_condition indica o filtro utilizado na tabela (teste.pedidos.status = ‘cancelado’).

A partir desses dados, descobrimos que o índice deve ser criado na coluna status da tabela:

mysql> create index idx_pedidos_status on pedidos (status);
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0

Executando novamente o plano de execução da query, temos o seguinte resultado:

mysql> EXPLAIN FORMAT=json SELECT * FROM pedidos WHERE status = 'cancelado';
+---------------------------------------------------------------------------+
| EXPLAIN                                                                   |
+---------------------------------------------------------------------------+
{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "1275.00"
    },
    "table": {
      "table_name": "pedidos",
      "access_type": "ref",
      "possible_keys": [
        "idx_pedidos_status"
      ],
      "key": "idx_pedidos_status",
      "used_key_parts": [
        "status"
      ],
      "key_length": "22",
      "ref": [
        "const"
      ],
      "rows_examined_per_scan": 3000,
      "rows_produced_per_join": 3000,
      "filtered": "100.00",
      "cost_info": {
        "read_cost": "675.00",
        "eval_cost": "600.00",
        "prefix_cost": "1275.00",
        "data_read_per_join": "117K"
      },
      "used_columns": [
        "id",
        "id_cliente",
        "status",
        "valor"
      ]
    }
  }
}
+---------------------------------------------------------------------------+

No exemplo acima, podemos ver no campo key que o índice idx_pedidos_status que acabamos de criar foi utilizado e que a quantidade de linhas percorridas (rows_examined_per_scan)reduziu de 97605 para 3000 (97% de redução), permitindo que a leitura desses dados seja consideravelmente mais rápida.

Nos próximos artigos eu falarei de estatísticas de tabelas e outras funcionalidades dos bancos de dados que afetam o uso de índices. Até lá!

MongoDB: revolução ou hype?

Nos últimos anos, a adoção de bancos de dados NoSQL, como o MongoDB, tem aumentado de maneira expressiva. Muitos chegam a falar que é o fim da linha para os bancos de dados relacionais, como o MySQL, PostgreSQL, SQL Server, etc. Mas será que é isso mesmo?

A resposta depende de uma série de fatores , como as características de uso da aplicação, se a aplicação deverá rodar on premise ou em cloud, se há orçamento para comprar ferramentas adicionais, etc. Trataremos sobre esses pontos mais adiante.

Antes de continuar, um pouco de terminologia do MongoDB para evitar confusão. A tabela abaixo apresenta os nomes usados pelos objetos no MongoDB e seus equivalentes em SQL:

MongoDBSQL
CollectionTabela
DocumentoLinha
CampoColuna

Analisando sua aplicação

O primeiro ponto que deve ser visto antes de definir qual (ou quais) banco de dados usar é a modelagem de dados a ser usada pela aplicação. O MongoDB, assim como a grande maioria dos bancos de dados de documentos, pede que suas collections sejam modeladas de forma a minimizar as quantidade de joins. Apesar de o MongoDB possuir o comando $lookup, que possui funcionalidade semelhante ao LEFT JOIN do padrão SQL, ele não é tão poderoso quanto aos diversos tipos de joins disponíveis em bancos relacionais, ou seja, geralmente é mais vantajoso construir aplicações com muitos relatórios, ou com relatórios que envolvem muitas tabelas, em bancos SQL.

Por outro lado, aplicações que tenham alto volume de tráfego e pouca ou nenhuma transação longa, como logs, registros de leitura de email, dados de chat e de cliques de usuário, etc., são mais otimizados em bancos de dados NoSQL.

Outros tipos de aplicação que aproveitam bem as características do MongoDB são as que possuem muitos dados opcionais, como por exemplo num e-commerce, onde alguns produtos podem ter informações adicionais como cor, tamanho, peso, voltagem, etc. O Mongo também é muito útil em aplicações que recebem dados de terceiros em formato JSON ou XML ou quando recebem dados de diversos fornecedores distintos. Em ambos os casos, os dados podem ser facilmente convertidos para o formato do MongoDB (BSON) e consultados posteriormente.

E quando minha aplicação possui ambas características, ou seja, partes onde é melhor usar SQL e outras em que é melhor usar o MongoDB? Nesses casos, deve-se analisar a possibilidade de se usar os dois (seja no mesmo executável ou em módulos distintos). Se isso não for possível, deve-se escolher o banco de dados mais adequado para a maioria dos usuários na maioria do tempo.

Um exemplo de modelagem em MongoDB

Para modelar um cadastro de clientes com telefones e endereços, costuma-se usar uma estrutura de tabelas semelhante a esta:

Para evitar a necessidade de joins no MongoDB, pode-se usar uma estrutura semelhante a essa:

{
    "_id": ObjectId("507f191e810c19729de860ea"),
    "nome": "José das Couves",
    "cpf": "01234567890",
    "ativo": 1,
    "telefones": [
    	{ "ddd": "27", "telefone": "99999-8888" },
        { "ddd": "11", "telefone": "3333-4444" }
    ],
    "enderecos": [
    	{
            "descricao": "Casa",
            "endereco": "Rua das Palmeiras",
            "cep": "29030-010"
        },
        {
            "descricao": "Trabalho",
            "endereco": "Av. Brigadeiro Faria Lima",
            "numero": 1234,
            "complemento": "sala 1001",
            "cep": "01452-920"
    ]
 }

Um ponto interessante a observar é que um dos endereços não possui nem número e nem complemento. Isso permite economizar o espaço necessário para registrar o nome do campo e de um valor nulo. O MongoDB possui o valor null, mas na grande maioria dos casos é preferível simplesmente não registrar o campo no documento.

Apesar de ser possível criar documentos com vários níveis de hierarquia, lembre-se que não é uma boa ideia colocar tudo de um cliente em um só documento (ex: todo o histórico de vendas ou todos os dados de um chat dentro de um único documento de usuário, por exemplo), tanto por conta de limite de tamanho de documento (no máximo 16 MB) quanto em relação a performance, que cai consideravelmente quando os documentos são muito extensos. No caso de um histórico de vendas, é melhor criar uma collection de vendas e incluir um campo fazendo referência ao _id da collection de Clientes.

Uma dica para ajudar a organizar as collections é seguir os conceitos de Aggregates do padrão DDD (Domain Driven Design). Todos os objetos do Aggregate (ex: Pedido e Item do Pedido) ficarão no mesmo documento, mas objetos de outros Aggregates (ex: Clientes) serão referenciados apenas pelo seu ID.

Infraestrutura do MongoDB

É possível hospedar bancos de dados MongoDB de duas formas: em uma estrutura própria (on premise ou em uma VM em cloud) ou através de um serviço gerenciado. Trataremos das vantagens e desvantagens de cada a seguir.

Instalar o MongoDB em uma estrutura própria permite controle total do gerenciamento do banco de dados, além de ter uma economia de custos, principalmente se forem usadas versões open source do banco. Essas vantagens têm um custo: o gerenciamento de cluster e de backup fica por sua própria conta. Além disso, o MongoDB Community não possui uma boa ferramenta de backup, sendo necessário adquirir ferramentas adicionais como o MongoDB Cloud Manager (que apesar do nome, roda na sua própria estrutura) ou usar o Percona Server for MongoDB, que possui funcionalidades adicionais para backup, auditoria, etc., mas nem sempre são 100% compatíveis com o MongoDB padrão.

Caso sua infraestrutura já esteja rodando em cloud (na AWS, Azure ou Google Cloud), é possível usar as versões gerenciadas do MongoDB através do MongoDB Atlas. Com ele, você elimina a necessidade de gerenciar clusters, backups e upgrades manualmente, além de ter acesso a todas as funcionalidades do MongoDB Enterprise. Por outro lado, além do custo da própria solução, há também o custo de tráfego entre a conta da MongoDB e a da sua conta (que devem estar no mesmo provedor e região para evitar latência).

Existem fornecedores de soluções compatíveis com o MongoDB de terceiros, como o AWS DocumentDB e o Azure Cosmos DB, mas eles são bancos de dados distintos, apesar de terem compatibilidade com os drivers de acesso ao MongoDB.

Vantagens do MongoDB

O MongoDB possui várias funcionalidades muito úteis para as aplicações:

  • escalabilidade: o MongoDB permite a criação de clusters sem dificuldades e sem a necessidade de ferramentas de terceiros. Além disso, se a aplicação utilizar o protocolo mongodb+srv://, não é necessário informar a aplicação sobre a inclusão ou remoção de nodes no cluster. Também é possível configurar a aplicação para priorizar as consultas de dados através das réplicas de leitura, retirando esse peso do node de escrita.
  • replicação de dados: por padrão, todos os nodes de um cluster possuem todos os dados dos databases, evitando a perda de dados no caso de falha em um dos nodes.
  • schema flexível: permite que documentos distintos tenham campos diferentes, evitando a necessidade de criação de campos com dados nulos na maioria dos registros.
  • suporte a JSON: permite o armazenamento de dados vindos de uma API um de um arquivo JSON ou XML (ex: arquivos de NF-e) de forma fácil, permitindo consultas de forma bem mais fácil do que seria ao usar SQL, por exemplo.

Limitações do MongoDB

Apesar de o MongoDB ser uma ferramenta bastante poderosa, ele ainda não possui algumas funcionalidades muito importantes que já vêm por padrão na grande maioria dos bancos de dados relacionais. Vamos a elas:

  • segurança: por padrão o MongoDB vem com o módulo de autenticação desabilitado. Se ele não for habilitado, o banco de dados passa a ficar vulnerável a erros humanos (excluir uma collection sem querer, por exemplo) e a ataques (por exemplo, um ataque de ransomware que busque bancos de dados sem senha e embaralhe os dados, exigindo pagamento para que possam ser recuperados, geralmente em criptomoedas para evitar o rastreamento).
  • backup: o MongoDB nativamente não possui um gerenciador de backups decente. O mongodump, além de não permitir backups incrementais, tem problemas na exportação de collections muito grandes. Para resolver o problema, é necessário comprar o MongoDB Cloud Manager, migrar o banco de dados para a nuvem usando o MongoDB Atlas ou recorrer a ferramentas de terceiros, como o Percona Backup for MongoDB (gratuito). Com essas ferramentas, além do backup incremental, permitem fazer restauração de dados point-in-time (ex: restaurar os dados como estavam ontem às 13:45), muito útil quando uma operação é feita por engano (eu ouvi update sem where ? 😱).
  • particionamento de collections: a grande maioria dos bancos de dados relacionais (MySQL, PostgreSQL, Oracle, etc) permite que suas tabelas sejam particionadas para otimizar a consulta em grandes volumes de dados. O MongoDB não possui essa funcionalidade, permitindo apenas sharding, que será explicado logo abaixo.
  • sharding: é uma modalidade de particionamento em que cada partição fica em um cluster separado. Como cada cluster MongoDB requer no mínimo 3 máquinas, para se ter 2 shards são necessários pelo menos 6 hosts. Isso pode encarecer muito a operação, especialmente se ela estiver em cloud.
  • validação de dados: o MongoDB não possui nenhum tipo de validação dos dados recebidos pela aplicação, ao contrário do SQL, que possui tipos de dados fixos, check constraints e triggers. Essa ausência permite que os documentos estejam em um formato inválido pela aplicação (ex: tipos de dados diferentes e valores inválidos em um campo), o que pode fazer que determinado documento não seja encontrado pela aplicação. Se, por exemplo, a aplicação espera que o campo “codigo” seja uma string e alguém insere manualmente como int, um find() pela string daquele número não retornará dados.
  • gerenciamento de collections: o MongoDB possui a característica de criar uma nova collection no caso de se fazer um insert em um nome de collection que não existe. Isso facilita a criação de novas collections, mas pode acontecer de haver um erro de digitação (ex: informar a collection transaction sendo que o nome correto é transactions) e os dados serem gravados no lugar errado. É possível restringir o acesso do usuário para impedir que ele crie novas collections, mas isso também bloqueia a criação legítima de novas collections.
  • views e views materializadas: o MongoDB não possui suporte a views ou a views materializadas. A própria aplicação deverá ser responsável por simular a funcionalidade de uma view materializada.
  • limites para ordenação: ao contrário dos bancos de dados SQL, o MongoDB não faz ordenação em disco caso não haja memória suficiente para ordenar um result set. Além disso, por padrão o Mongo limita uma operação de sort a 32 MB, ou seja, ordenar um conjunto grande de dados irá causar erro de execução, mesmo que se use limit() para reduzir a quantidade de dados recebidos. A solução para esse problema é criar um índice com os mesmos campos encontrados no comando sort(), mas isso aumenta o consumo de espaço em disco e causa um certo aumento no tempo necessário para fazer insert/update/delete.
  • limites para criação/exclusão de índices: até antes da versão 4.2 do MongoDB, a criação de índices bloqueava a execução de outras operações na collection, a não ser que se especificasse o parâmetro background: true. Outra limitação é que, ao deletar um índice, o banco de dados mata todas as sessões que estejam usando o índice no momento da execução do comando, ou seja, o DBA tem que esperar um momento em que ninguém esteja usando a collection para poder deletar o índice.
  • linguagem de query complexa: a linguagem de query do MongoDB é mais complexa do que o SQL, tanto por não ser tão difundida quanto por ter uma legibilidade menor (em muitos casos é questão de gosto pessoal, mas pode ser uma barreira para quem não está acostumado com o padrão JSON). Veja abaixo um exemplo em SQL e seu equivalente em MongoDB:
SELECT nome, cpf
  FROM clientes
 WHERE data_criacao >= date '2020-01-15'
   AND sexo = 'M'
 ORDER BY nome DESC

Exemplo de query em SQL

db.clientes.find({
    "data_criacao": { $gte: new ISODate("2020-01-15") },
    "sexo": "M"
}, {
    "_id": 0,
    "nome": 1,
    "cpf": 1
}).sort({ "nome": -1 })

Exemplo de query em MongoDB

Conclusão

O MongoDB pode ser usado em uma grande variedade de aplicações, mas seus prós e contras devem ser levados em consideração antes de iniciar o projeto, especialmente se a modelagem de dados consegue ser encaixada tanto num modelo relacional quanto no MongoDB (com pouca ou nenhuma variação nas colunas entre os documentos).

Além das questões técnicas, deve ser levado em conta o conhecimento de cada tecnologia tanto por parte dos desenvolvedores quanto da equipe que irá administrar o banco de dados (DBAs, analistas de infraestrutura, etc) e a disponibilidade para estudar uma tecnologia nova e aplicá-la na operação da empresa. Também é necessário analisar o orçamento disponível para a operação, seja para a aquisição de hardware (on premise) ou para uso em cloud (instalando em máquinas virtuais ou como DBaaS, usando o serviço MongoDB Atlas).

© 2025 Vitor Campos

Theme by Anders NorenUp ↑