MongoDB – Buscar termo em todos os campos

Ao desenvolver uma API, certamente você se deparou com a missão de ter que fazer uma busca genérica em todos os campos do seu banco.

A primeira vista, essa missão pode assustar, pois, você deve pensar, como eu vou fazer isso? E se eu tiver vários campos, vou ter que especificar tudo na query?

Nesse artigo eu te mostro uma forma bem tranquila de fazer isso e ainda te digo os prós e contras.

O problema

Você está desenvolvendo uma API para o seu aplicativo e quer fazer uma busca “simples”, onde o usuário tem apenas um campo “buscar por…” e ao digitar um termo qualquer, a sua API tem que “se virar” para encontrar os resultados, seja o nome do usuário, idade, sexo, etc. não importa o que o usuário tenha digitado.

Recebendo o termo de busca

Normalmente, em uma API REST você vai receber esse termo pela querystring.

Seria alguma coisa como isso:

GET https://api.seuapp.com.br/v1/endpoint?search=termo

Se você estiver usando express do Node.js para escrever sua API, você vaii obter esse valor da seguinte forma:

app.get('endpoint', (request, response) => {
   const searchTerm = request.query.search;
});

Bacana, agora você já sabe obter o termo para a pesquisa, mas antes de nós fazermos a pesquisa no banco, vou te mostrar o pulo do gato para que isso tudo funcione de uma forma simples.

Criando o índice Text Search

Para resolver esse problema, o MongoDB nos proporciona a funcionalidade Text Search.

O Text Search nada mais é do que um índice múltiplo que quando a gente fizer a busca por ele. Veremos isso em breve.

Para criar esse índice, devemos rodar o seguinte comando no terminal do MongoDB.

db.suacolecao.createIndex( { campo1: "text", campo2: "text" } )

Para acessar o terminal do MongoDB (Mongo Client) você precisa estar com o daemon do banco de dados iniciado e digitar “mongo” no terminal do seu computador.

Pronto! Dessa forma você configurou que quando você fizer sua busca genérica, você vai pesquisar o termo nos campos: “campo1” e “campo2”.

Fique atento!

Digamos que sua coleção tenha 4 campos e você configurou no índice apenas 2 deles, fique atento, pois o termo será procurado apenas nos 2 campos configurados.

Como fazer a query?

Com a confiugração do índice, você “disse” para o MongoDB onde ele deve pesquisar o termo quando for procurado no índice text e para fazer essa busca, você precisa usar esse código.

mongoClient.find({ $text: { $search: searchTerm }});

Bônus: Como melhorar a sua experiência com a busca em índices

É possível que você quera excluir algum termo da busca ou até mesmo pesquisar por um termo exato.

Você pode excluir termos da sua busca, apenas colocando um sinal de menos na frente do termo.

db.suaColecao.find( { $text: { $search: "gol fusca -brasilia" } } )

Essa busca retorna tudo que contenha gol, fusca e que não contenha brasilia.

Você também pode procurar por um termo específico.

db.stores.find( { $text: { $search: "\"brasilia amarela\"" } } )

Esse código vai trazer apenas os resultados que contenham “brasilia amarela”.

Você também pode ordenar os resultados conforme o score da sua busca. O score é uma espécie de pontuação na qual identifica o quanto o termo encontrado é parecido ou igual ao termo pesquisado.

db.suaColecao.find(
   { $text: { $search: "gol fusca puma" } },
   { score: { $meta: "textScore" } }
).sort( { score: { $meta: "textScore" } } )

Outra forma

Existe ainda um outra forma de fazer algo parecido com isso, talvez seja a forma mais comum, entretanto, crio que não é a forma mais performática.

Essa forma não cria um índice específico pra isso, apenas adiciona os valores que devem ser pesquisados em um único campo, exemplo:

db.latasVelhas.insert(
   [
     { _id: 1, name: "Fusca", color: "azul porrada", tags: "Fusca azul porrada" },
     { _id: 2, name: "Bianco", color: "vermelho montana", tags: "Bianco vermelho montana" },
     { _id: 3, name: "Puma", color: "verde", tags: "Puma verde" },
     { _id: 4, name: "Brasilia", color: "amarela", tags: "Brasilia amarela" },
     { _id: 5, name: "Rural", color: "azul e branca", tags: "Rural azul e branca" }
   ]
)

E a busca seria algo como:

db.latasVelhas.find({ tags: /Puma/ });

O que muda nessa coleção é que não temos um índice configurado para fazer o trabalho pra gente, nesse caso a gente faz o trabalho do índice manualmente colocando todos os termos relevantes em um campo dentro da coleção.

Apesar de eu achar que é uma “gambiarra”, funciona! Mas veremos a seguir os prós e contras de ambas as formas.

Prós Vs. Contras

Tudo na vida tem prós e contras e com essa funcionalidade não é diferente.

Se você optar por criar o índice Text Search, a cada vez que houver a necessidade de adicionar um novo campo à busca, você terá que recriar esse índice e isso pode ser bem custoso, entretanto, seu código fica mais limpo, você não precisa escrever nada a mais ao inserir ou editar um dado, isto é, a manutenção é bem menos custosa, fora a velocidade que um índice traz para a sua aplicação.

Se você optar por ter um campo de tags com os termos relevantes, você vai aumentar o tamanho da sua base de forma desnecessária, fora toda a manutenção que esse campo vai exigir quando você inserir, mas principalmente quando precisar editar esses dados. Também sabemos que os índices foram criados para melhorar a performance e nesse caso você não os tem.

Mas nem pense em criar o campo de tags e ainda colocar um índice nele. Essa seria sua pior opção, tendo em vista que a cada modificação você precisara recriar os índices.

Exemplos

Considere a coleção abaixo:

db.latasVelhas.insert(
   [
     { _id: 1, name: "Fusca", color: "azul porrada" },
     { _id: 2, name: "Bianco", color: "vermelho montana" },
     { _id: 3, name: "Puma", color: "verde" },
     { _id: 4, name: "Brasilia", color: "amarela" },
     { _id: 5, name: "Rural", color: "azul e branca" }
   ]
)

Configure o índice Text Search:

db.latasVelhas.createIndex({ name: "text", color: "text" });

Busque por algum termo

db.latasVelhas.find( { $text: { $search: "fusca amarela" } } )

Busque por um termo específico

db.latasVelhas.find( { $text: { $search: "\"Bianco\"" } } )

Referência

Esse artigo foi escrito baseado na documentação do MongoDB que pode ser consultada no link abaixo (em inglês).

https://docs.mongodb.com/manual/text-search/

Como resolver problemas de migrações

Você está desenvolvendo uma aplicação na sua estação. Cria, modifica, remove tabelas, chaves (constraints) e colunas, gera um monte de migrações (migrations) com o seu ORM favorito para manipular uma base de dados relacional e na hora do deploy o sistema quebra porque as migrações estão uma bagunça querendo remover coisas que não existem mais ou criar cosias que já existem.

Isso acontece e eu vou te mostrar aqui algumas formas de resolver.

O problema…

Na hora do desenvolvimento é normal que a gente faça muita alteração, afinal de contas, é nessa hora que podemos testar hipóteses e errar bastante, mas isso gera um certo “lixo” nas migrações que podem e vão nos dar trabalho futuramente.

Esse tipo de problema acontece normalmente com quem usa geradores de migração ou com desenvolvedores que as escrevem de forma muito simplista, assim como os geradores fazem.

Vejamos um exemplo gerado pelo gerador de migrações do Typeorm.

import {MigrationInterface, QueryRunner} from "typeorm";

export class ModifySomething implements MigrationInterface {
    name = 'ModifySomething987982495702935873450987'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query("ALTER TABLE `syndicate` ADD CONSTRAINT `FK_sd9f8g7s8df6g89f6s87dfg` FOREIGN KEY (`addressId`) REFERENCES `address`(`id`) ON DELETE CASCADE ON UPDATE CASCADE");
        await queryRunner.query("ALTER TABLE `address` ADD CONSTRAINT `FK_c76sdf8g6sdf87g6s9g67` FOREIGN KEY (`companyId`) REFERENCES `company`(`id`) ON DELETE CASCADE ON UPDATE CASCADE");
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query("ALTER TABLE `address` DROP FOREIGN KEY `FK_c76sdf8g6sdf87g6s9g67`");
        await queryRunner.query("ALTER TABLE `syndicate` DROP FOREIGN KEY `FK_sd9f8g7s8df6g89f6s87dfg`");
    }

}

Notem que ele não faz nenhuma verificação se as chaves existem ou não e em caso de uma coluna, se tem algum dado lá dentro.

Ele simplesmente sai fazendo loucamente e é isso que normalmente geram erros ao rodar as migrações. Porque o sistema tenta adicionar algo que já existe ou remover algo que não existe mais.

Erro de migração
A imagem mostra a tela do editor de códigos com um erro de migração

Forma fácil, mas…

A forma mais fácil de resolver esse tipo de problema depois que ele já aconteceu é encontrar a última migração que foi executada com sucesso, remover todas as seguintes e depois gerar uma nova migração com todas as modificações compiladas nela mesma, mas… (sempre tem um mas) se você trabalha com mais desenvolvedores, pode ser que você complique as coisas já que no commit anterior haviam as migrations com problemas e o novo tem a solução… não necessariamente vai dar problema, mas pode dar!

Faça isso por sua conta e risco, combine com o seu time e antes disso, faça backups e etc. Lembre-se que migrations servem para versionar o banco de dados e é importante manter o histórico. Depois que tudo der certo, se livre dessas tranqueiras.

Forma mais trabalhosa, mas…

A forma mais “trabalhosa” é você criar as migrações você mesmo, ou usar o código gerado pelo ORM (Typeorm nesse caso) e refatorá-lo fazendo as verificações necessárias.

Normalmente, os ORM’s oferecem funções que ajudam nesse sentido, como no caso do Typeorm que tem uma API específica para isso.

Basta você utilizar QueryRunner como injeção de dependência no seus métodos up() e down() e chamar os métodos que quiser, tais como: createTable(), createIndex(), addColumn(), createForeignKey(), getTable(), etc.

Nesse caso, o método getTable() é um método muito importante, já que ele trás as informações da tabela e com ele saberemos se a coluna a ser criada existe, se a chave estrangeira já foi criada, etc.

Então, essa é a forma mais “trabalhosa”, mas (sempre tem um mas…) a mais elegante também (você achou que vinha alguma coisa ruim aqui, certo? heheh)

Previna-se

Lembre-se que você provavelmente só está nessa situação porque não fez um bom planejamento da funcionalidade ou porque você “comitou” o código sem revisar antes e levou um monte de bugs para o seu repositório remoto.

Para prevenir isso é fácil, é só fazer as revisões, usar testes e nunca “comitar” direto na sua branch master. Caso você trabalhe sozinho (como uma “euquipe”) redobre os cuidados, pois resolver problemas de desenvolvimento é custo e você precisa de lucro, estou certo? Caso você tenha uma equipe com mais desenvolvedores, crie uma pull request e solicite as revisões de código, aos seus colegas, discuta as melhores formas de resolver o problema antes de fazer o merge para a master e evite problemas.

Não deixe seu sistema quebrar…

Quando uma migração não acontece, o seu sistema quebra e isso é ruim, certo?

Imagine você que seu sistema está funcionando “bonitamente” e na sexta-feira você tem a brilhante ideia de fazer o deploy de uma release super massinha. Está tudo indo bem, mas no último segundo para o expediente acabar a sua migração quebra e a API do seu sistema vai pro saco…

Isso aconteceu comigo e eu não quero que aconteça com você 🙂

Docker Swarm Vs. Kubernates
Mostra os logos de Docker Swarm versus Kubernates

A melhor ideia para resolver esse problema é utilizar orquestradores de containers, como o Kubernates ou Docker Swarm por exemplo.

Eles fazem um monte de coisas e uma delas é manter a API em pé e só subir a nova realease quando tudo estiver certinho.

Isso pode salvar seu final de semana e evitar muitos cabelos brancos.

Por hoje é isso aí!

Esperto muito ter ajudado.

Não esqueça de deixar um comentário bem bacana contando suas experiências e como eu posso ajudar 🙂

Grande abraço!

Licenciamento de Oracle Database: Virtualizar ou não?

Logotipo Oracle

Introdução

Estou liderando um projeto relativamente de grande porte na empresa em que trabalho. O desafio dessa vez é levar o ambiente para um datacenter aqui da cidade (Porto Alegre) ou melhorar as nossas dependências a fim de fornecer a segurança física necessária contra desastres, entretanto, havia a cerca de 3 anos outro projeto parado que o assunto era a criação de uma “nuvem privada”, somei os projetos e estou por aqui ficando quase de cabelo branco… (hehe).

No decorrer desse projeto, me deparei com a seguinte situação, virtualizar ou não virtualizar a minha instância de banco de dados?

No nosso caso utilizamos o Oracle como banco de dados principal na empresa. Nele temos nosso ERP, GED e CRM, além dele temos outros bancos de dados menores para outras tarefas, como intranet por exemplo, mas vamos nos ater ao Oracle.

Quando pensei em virtualizar, logo fui consultar o EULA da Oracle (como de praxe) para ver como ficariam as licenças para esse servidor. Entre diversos EULA’s, conversas com amigos, consultores, fornecedores e DBA’s, pude juntar as informações que forneço para vocês abaixo.

Importante: Não se baseie somente nessas informações, eu não trabalho para a Oracle, não sou certificado Oracle (ainda hehe) e essas informações servem somente como conhecimento e não como documento. Fiquem atentos as versões dos EULA’s fornecidos diretamente no site da fabricante do software. Não me responsabilizo por qualquer ação tomada com base nesse artigo.

Particionamento de servidor

A Oracle considera duas formas de “particionar” um servidor, são elas: soft partition e hard partition.

Abaixo algumas considerações sobre ambas.

Soft Partition

Soft partition é quando fazemos o particionamento em um hypervisor (VMWare, Citrix XenServer, Microsoft Hyper-V, etc). Quando configuramos a VM com um processador apenas.

Essa modalidade NÃO É ACEITA para o cálculo de número de processadores para aquisição de licenças Oracle.

Obs.: caso a virtualização seja feita sob o hypervisor da Oracle, o Oracle VM, existe outro documento permitindo contabilizar somente o processador da VM para licenciamento.

Hard Partition

Hard partition é quando conseguimos realmente especificar o hardware que será utilizado pelo sistema operacional. Essa modalidade sim é aceita pela Oracle como cálculo de numero de processadores para aquisição de licenças Oracle.

Entretanto, nas arquiteturas Intel x86 e x86_64 não são possíveis fazer hard partition, os SO’s não são preparados para isso, somente em arquiteturas RISC, como é o caso de Power (PPC) da IBM por exemplo.

Dessa forma é possível “informar” para o SO quanto de recurso físico ele vai utilizar, independentemente da máquina possuir no total dois processadores, por exemplo.

Licenças Oracle Database Standard Edition One e Oracle Database Standard Edition

A Oracle possui muitas outras licenças e formas de licenciamento, entretanto vou comentar apenas essas duas licenças de processador, as quais eu estudei para a implementação desse projeto.

Obs.: essas licenças podem ser pagas anualmente ou na forma perpétua.

Obs. 1: o valor do suporte anual é de 20% do valor das licenças, é preciso pagar para quantidade total de processadores que você tem e é muito importante que você tenha o suporte atualizado, caso contrário não terá direito de baixar pacotes de atualização do seu software e não terá suporte pela Oracle quando for necessário (geralmente quando chega a esse ponto é porque a coisa está muito, mas muito feia…). Outro motivo é que você paga retroativo, caso pule um ano, por exemplo (isso pode onerar o orçamento do seu departamento de TI e seu gestor certamente não vai gostar).

Considerações sobre Oracle Database Stanadard Edition One

Nessa edição, podemos licenciar equipamentos com até 2 processadores físicos. FIQUEM ATENTOS ENTRETANTO, que mesmo que a licença permita que sejam licenciados até dois processadores, não significa que uma máquina com dois processadores necessitará apenas de uma licença, pelo contrário, significa que caso tenha mais do que 2 processadores, deverá ser usada outro tipo de licença, falaremos disso mais adiante.

Além disso, esse licenciamento custa o equivalente a metade do valor da versão Standard (em 2013) e não possui direitos de uso de Oracle RAC (Real Application Cluster – Consiste basicamente em um conjunto de serviços de alta disponibilidade).

Exemplificando:

Exemplo 1: Um servidor com dois processadores físicos deverá utilizar duas licenças Oracle Database Standard Edition One.

Exemplo 2: Um servidor com quatro processadores físicos deverá utilizar outro tipo de licenciamento.

Exemplo 3: Um servidor com mais de quatro processarores físicos deverá utilizar um tipo de licenciamento diferente do comentado no Exemplo 2 (esse não será comentado nesse artigo).

Considerações sobre Oracle Database Stanadard Edition

Nessa versão, podemos utilizar em equipamentos com até 4 processadores físicos e o mesmo comentado na versão anterior vale para cá.

Essa versão é bem cara e contempla a utilização do Oracle RAC.

Obs.: Ainda existe a versão Enterprise para equipamentos com mais de 4 processadores, que possui um algoritmo de cálculo baseado no total dos cores dos processadores físicos multiplicado por 0,5, mas não vou comentar nada além disso nesse artigo, pois não estudei muito a fundo… além disso, dependendo do tamanho da empresa (quantidade de usuários, se está virtualizado ou não, falaremos mais adiante).

 

Considerações sobre utilização de virtualização

 

As formas de licenciamento supracitadas servem tanto para ambientes virtuais como físicos, entretanto devemos observar alguns pontos além dos já citados em ambientes virtuais. São eles:

Suporte

No projeto inicial iríamos virtualizar a instância de banco de dados sob o Microsoft Windows Server 2012 Hyper-V, entretanto um ponto levantado foi fortemente considerado. A Oracle “não presta suporte” á ambientes que não estão virtualizados com Oracle VM (até então). Na verdade, eles não vão deixar de te atender, mas vão te mandar simular o erro que está ocorrendo em uma máquina física (caso o erro ainda não esteja relatado), aí já viu que a dor de cabeça vira enxaqueca rapidinho…

Utilização de V-motion

Todos os hypervisors tem a possibilidade de v-motion (mover máquinas virtuais dentro de um grupo [pool] de servidores) que é uma ferramenta de excelente aplicabilidade em qualquer ambiente, principalmente quando utilizado com HA (High Available, alta disponibilidade).

Com a utilização desse serviço, a sua VM com a instância Oracle passará por diversos equipamentos e a política da Oracle é clara quanto à isso: toda a máquina que rodar o produto deverá ser licenciada. Portanto, se você tem um pool com 4 máquinas físicas por onde essa VM poderá passar por V-motion, você terá que licenciar todas as máquinas do pool.

Oracle VM

Conforme já citado, a Oracle tem um documento específico para o licenciamento sob Oracle VM, nesse caso é contato apenas o processador da máquina virtual em que está instalado o banco de dados.

Obs.: até então o Oracle VM não faz motion de storage, entretanto, isso não é um impecílio, o próprio storage tem tecnologia suficiente para suprir essa necessidade.

Conclusão

Conforme o estudo realizado sob as licenças Oracle, tive que alterar parte considerável do escopo do projeto inicial, inclusive os custos do projeto.

No meu caso, optei por não virtualizar, apenas utilizar uma máquina (host) específica para esse serviço, mas cada ambiente é um organismo e precisa ser avaliado de perto.

Essas são algumas considerações que eu tive para o meu projeto, mas não tome como documento, pergunte e informe-se em fontes garantidas, como o site da fabricante.

Você também pode contribuir para a comunidade. Tem alguma sugestão ou correção? Descreva nos comentários.

 

E você o que usou ou usaria em seu ambiente?

GRRF: Erro com banco de dados

Recentemente, tivemos problemas em instalar o GRRF em uma máquina no ambiente da Empresa.

Atualmente, trabalhamos com Domínios e usuários com direitos limitados e por esse fato, muitas aplicações dão um trabalho danado na hora de serem executadas. Não diferente, as aplicações do nosso Governo (geralmente dão mais trabalho), o aplicativo GRRF deu muita dor de cabeça por aqui.

Como encontramos a solução, vou disponibiliza-la para que a dor de cabeça de vocês seja menor que a minha…

ATENÇÃO

Se você não trabalha com infraestrutura de TI ou não é familiarizado com isso, apenas execute o programa como administrador. Tem um comentário do Felipe Fonseca aqui em baixo que explica como fazer isso e essa dica foi dada pela Myller Meirelles também nos comentários.
Caso contrário, segue o baile… valeu!

  1.  Primeiramente, é necessário setar permissão para o grupo de usuários em questão nos arquivos Hl_med32.dllHl_pub32.dllHlsoft32.dll localizados no diretório C:\WINDOWS\SYSTEM32.
  2. Depois, dê também permissão no diretório C:\WINDOWS\PREFETCH
  3. Também em C:\Arquivos de programas\Caixa e em HKLM\SOFTWARES\Caixa
  4. Vá no Painel de Controle > Ferramentas Administrativas > Diretivas de Segurança Local > Configurações de Segurança > Diretivas Locais > Atribuição de Direitos de Usuário > ‘Criar Objetos Globais’ > Adicionar o usuário ou adicione à uma GPO.
Mais auxílio para esse problema, pode ser encontrado nesse link (que foi de onde tirei tudo isso hehe) http://social.technet.microsoft.com/Forums/pt-BR/winvistapt/thread/905b1d18-90a3-4e50-80fe-028035209677
Abraço!