Você sabia que, na realidade, os icebergs estão localizados horizontalmente na água , e não verticalmente, como na maioria das imagens conservadas em estoque?
Mas mesmo que você esqueça o grupo tradicional de dispositivos corporativos como análises, suporte de compatibilidade com versões anteriores e testes A / B e se concentre exclusivamente no código diretamente relacionado à funcionalidade implementada, você pode ver que sua complexidade muitas vezes fica fora de controle.
Neste artigo, vou falar sobre vários recursos que meus colegas e eu implementamos no Joom em diferentes momentos, desde a declaração do problema até os detalhes de implementação, e mostrarei como facilmente coisas aparentemente simples se transformam em um emaranhado de lógica complexa que requer muito desenvolvimento iterações.
Pesquisa por usuários
Uma das grandes seções do aplicativo Joom é a rede social interna, onde os clientes podem escrever avaliações de produtos, curti-los e discuti-los e se inscreverem uns aos outros. E que rede social sem busca de usuários!
Claro, pesquisar não é uma tarefa tão fácil de ver (pelo menos depois do meu artigo anterior ). Mas eu já tinha todo o conhecimento necessário e também tínhamos um componente pronto em nossa empresa
joom-mongo-connector
que era capaz de transferir dados de uma coleção para o MongoDB para um índice Elasticsearch, se necessário ajustando dados adicionais e fazendo algum outro pós-processamento. A tarefa parecia muito simples.
Uma tarefa... Faça um back-end para pesquisa por usuários de redes sociais. Não são necessários filtros, a classificação pelo número de assinantes servirá para começar.
Ok, isso realmente parece simples.
socialUsers
Configuramos o estouro da coleção para Elasticsearch escrevendo uma configuração em YAML. No back-end, adicionamos um novo endpoint com uma API semelhante à API de pesquisa de produto, mas até agora sem suporte para filtros e classificações (apenas o texto da solicitação e a paginação permanecem, isso é tudo). No manipulador, fazemos uma solicitação simples ao cluster Elasticsearch (o principal é não errar com o cluster!), Do resultado obtemos os IDs dos documentos encontrados - são IDs de usuário - de acordo com os usuários próprios, então nós convertemos para cliente JSON, escondendo informações privadas de olhos curiosos, e pronto. Ou não?
O primeiro problema que encontramos foi a transliteração. Os nomes de usuário foram retirados de redes sociais, onde os usuários da Rússia (e eles eram a maioria na época) costumavam escrevê-los em latim. Você tenta encontrar Mads, e ele está no Facebook de Mads, e é isso - ele não está nos resultados. Da mesma forma, Ivan não será capaz de encontrar Ivan, mas eu gostaria muito de fazê-lo.
Esta é a primeira complicação - ao indexar, começamos a ir para a API do Microsoft Translator para transliteração e salvar duas versões do nome e sobrenome, e o componente de indexação geral começou a depender do cliente do transliterador (e ainda depende).
Bem, o segundo problema, que é fácil de prever se sua língua nativa for o russo, mas existe em outras línguas europeias também - formas diminutas e abreviações de nomes. Se Ivan decidir se nomear Vanya no Facebook, o pedido de Ivan não o encontrará mais, não importa o quanto você transliterar.
Portanto, a próxima complicação foi que encontramos um índice de nomes diminutos no Gramota.ru (do dicionário exclusivo de nomes russos de Nikandr Aleksandrovich Petrovsky), o adicionamos à base de código como uma placa codificada (cerca de duas mil linhas) e tornamos o índice não só o nome e sua transliteração, mas também todos encontraram formas diminutas (curiosidade: em inglês existe um termo hipocorismo para eles). Pegamos cada palavra do nome de usuário e fizemos uma busca em nossa humilde planilha.
Uma captura de tela autenticada da base de código do Joom. Por volta de 2018.
Mas então, para não ofender a outra metade de nossos usuários, distribuídos em uma camada desigual por todo o mundo que não fala russo, gritamos aos gerentes do Joom e pedimos a eles que nos encontrassem livros de referência com abreviações nacionais nomes em seus países. Se não for acadêmico, pelo menos alguns. E descobriu-se que em algumas línguas, além da tradição de ter um nome composto (Juan Carlos, Maria Aurora), também há reduções de duas, três ou mesmo quatro palavras em uma (María de las Nieves → Marinieves).
Esta nova circunstância nos privou da oportunidade de fazer uma pesquisa uma palavra de cada vez. Agora precisamos dividir a sequência de palavras em fragmentos de comprimento arbitrário e, além disso, partições diferentes podem levar a abreviações diferentes! Não queríamos mergulhar nas profundezas da linguística e escrever inteligência artificial que abreviava um nome espanhol da maneira que um espanhol vivo o abreviaria, então esboçamos, perdoamos Knut, um exagero combinatório.
E, como sempre é o caso com pesquisas combinatórias, ele estourou em um dos usuários e tivemos que cortar com urgência um limite para o número máximo de grafias geradas. Isso complicou ainda mais o código, que era inesperadamente difícil para essa tarefa.
Tradução automática de mercadorias
Tarefa . É necessário traduzir os nomes e descrições dos produtos fornecidos pelos vendedores em inglês para o idioma do usuário.
Provavelmente, todo mundo já viu memes sobre a tradução distorcida dos nomes de produtos chineses. Nós os vimos também, mas o tempo desejado para o mercado não nos permitiu chegar a algo melhor do que usar alguma API existente para tradução.
É fácil escrever um cliente HTTP, criar uma conta e, quando as mercadorias são enviadas para o usuário, é fácil traduzi-las para o idioma do dispositivo. Mas as traduções não são baratas e seria um desperdício traduzir o mesmo produto popular para o russo para cada uma das dezenas de milhares de visualizações. Portanto, ativamos o cache: para cada produto, salvamos as traduções no banco de dados e, se houvesse traduções lá, não íamos mais ao tradutor.
Mas o potencial de economia ainda estava lá. Decidimos que um compromisso razoável entre a qualidade da tradução e o preço seria superar as descrições das frases e armazená-las em cache - afinal, as mesmas frases de modelo são freqüentemente encontradas em produtos e é um desperdício traduzi-las sempre. É assim que outra camada de abstração apareceu em nosso tradutor - uma camada entre o cliente HTTP e o cache que armazena mercadorias inteiras em diferentes idiomas, que está empenhada em quebrar o texto em fragmentos.
Depois do lançamento, a qualidade das traduções, claro, nos assombra, e pensamos: e se usarmos um tradutor mais caro? Mas será bom para nossas letras específicas? Você não pode compará-los a olho nu, você precisa fazer um teste A / B. Assim, em nosso cache de tradução, além do ID do produto, o ID do tradutor apareceu e começamos a solicitar uma tradução do ID do tradutor, dependendo do grupo de teste A / B em que o usuário estava.
O querido tradutor teve um bom desempenho, mas ainda era um desperdício executá-lo em todos os produtos. Mas fomos para países cujos idiomas nacionais nosso principal tradutor lidou tão mal que estávamos prontos para pagar por um lançamento bem-sucedido; então a lógica de escolher um tradutor se tornou mais complicada.
Então decidimos que algumas lojas na plataforma são tão boas e a plataforma está tão torcendo pelo sucesso que está sempre pronta para traduzir seus produtos com um tradutor mais caro. Assim, a lógica de escolha de um tradutor passou a depender do usuário, país e identidade da loja.
E, finalmente, decidimos que, ao longo dos vários anos de existência do Joom, nosso tradutor principal poderia melhorar e talvez faça sentido atualizar o cache de tradução em alguns intervalos. Mas e sem um teste A / B? Então, o campo de frescor apareceu em nosso cache e as coisas ficaram complicadas novamente. Como resultado, nosso componente de tradução é incrivelmente complexo, e isso apesar do fato de que ainda não colocamos nenhuma lingüística computacional caseira nele. Por enquanto.
Conversão de tamanhos de roupas
Talvez um dos problemas mais dolorosos ao comprar roupas e sapatos online seja escolher o tamanho certo. E se, quando entregues de depósitos locais, jogadores como Lamoda puderem simplesmente trazer vários tamanhos de uma vez e retirar os inadequados com a mesma facilidade, isso não funcionará em um cross-border. As encomendas demoram muito, o custo de cada quilograma extra é alto e os remetentes não esperam um grande fluxo de correspondência.
Além disso, o problema é agravado pelo fato de que os vendedores de diferentes países podem ter ideias completamente diferentes sobre os tamanhos. O chinês M poderia facilmente acabar sendo o russo XS, e o aterrorizante 9XL pode não ser tão diferente do XXL. Usuários costurados têm que confiar nas medidas, mas mesmo essas nem sempre são corretas: por exemplo, o usuário espera que a circunferência do peito de uma pessoa seja indicada e o vendedor indique as medidas das próprias roupas - elas diferem em cinco a dez por cento . Não queremos que o usuário tenha que se preocupar tanto para fazer compras no Joom!
Tarefa . Em vez dos tamanhos fornecidos pelos vendedores, mostre aos usuários os tamanhos que calculamos a partir de uma única tabela com base nas circunferências.
OK. Pegamos uma tabela de tamanhos, que analisamos a partir da descrição do produto (isso é feito por uma espaçonave separada por 5k linhas) e é armazenada em um campo separado, e substituímos os tamanhos nela pelos calculados. Codifique a tabela para converter a circunferência em tamanho, encontrada na Internet, e aproveite a vida.
Mas se não houver uma tabela ou se não houver linhas suficientes nela, isso não funcionará. O recurso é desabilitado no produto implicitamente várias vezes.
Hmm, na tabela as circunferências do corpo humano, e muitos vendedores indicam medindo nas próprias coisas. Costura no coeficiente de diferença. O gerente de produto Rodion, o feliz proprietário do M-ki perfeito, vai ao shopping, mede um monte de coisas diferentes nele mesmo e vem com coeficientes - eles são semelhantes, mas diferem significativamente para diferentes categorias de produtos. Para uma gola redonda, a diferença é de quase 0%, e para um suéter, todos 10%. Além disso, as roupas externas variam em ajuste: ajuste fino, ajuste normal, ajuste solto e isso dá um balanço de ± 5%. Agora, nosso coeficiente (imortalizado por mim no código como o coeficiente de Rodion ) consiste em dois fatores.
Para determinar o patamar, fazemos outro analisador que tenta extraí-lo do nome ou descrição do produto. Se o produto não se enquadrar em uma das categorias verificadas por Rodion, o recurso está implicitamente desativado número dois.
O toque final: muitos produtos listam o busto de axila a axila, significando apenas metade da circunferência, o que resulta em tamanhos ridiculamente pequenos. Acrescentamos a lógica de que se a circunferência for menor que X, então bem, isso não pode ser, esta é claramente a metade da circunferência e multiplicamos por dois. É bom que os adultos geralmente não difiram entre si por duas vezes a circunferência do peito.
Agora tudo é tão complicado que ao testar um recurso pelo tipo de produto no painel de administração, é impossível entender por que ele não ligou ou funcionou de uma forma ou de outra. Adicionamos uma grande camada de lógica ao código, registrando em detalhes os motivos para desligar a conversão. Para poder rastrear totalmente a causa do desligamento em um produto específico, você deve encaminhar mensagens de erro para cima, enriquecendo com detalhes, várias vezes. O código se torna assustador.
E tudo funciona de forma diferente dependendo do grupo do teste A / B, é claro.
Conclusão
Cuidado com os