Arquitetura do sistema de negociação de baixa latência


Arquiteturas eficientes para sistemas de baixa latência e alta taxa de transferência na JVM.


Abstrato.


A motivação para nossa pesquisa começa com a crença comum de que a plataforma Java não é adequada para implementar aplicativos de alto desempenho. O Java é uma das plataformas de desenvolvimento de software mais utilizadas no mundo e fornece os meios para o desenvolvimento rápido de aplicações robustas e complexas que são fáceis de expandir, garantindo um curto período de entrega inicial e durante toda a vida do sistema. O ambiente de tempo de execução Java, e especialmente o Java Virtual Machine, em cima do qual os aplicativos são executados, é a principal fonte de preocupações em relação à sua adequação no ambiente de comércio eletrônico, principalmente por causa de seu gerenciamento de memória implícita. Neste artigo, pretendemos identificar algumas das medidas mais comuns que podem ser tomadas, tanto no nível de ambiente de tempo Java e no nível de arquitetura da aplicação, o que pode ajudar os aplicativos Java a alcançar um desempenho ultra alto. Também propomos duas arquiteturas eficientes para sistemas de troca comercial que permitem latências ultra baixas e alto débito.


Baixe informações.


Se você tiver problemas ao fazer o download de um arquivo, verifique se você possui o aplicativo apropriado para vê-lo primeiro. Em caso de problemas adicionais, leia a página de ajuda IDEAS. Observe que esses arquivos não estão no site IDEAS. Seja paciente porque os arquivos podem ser grandes.


Informações bibliográficas.


Volume (Ano): 17 (2018)


Pesquisa relacionada.


Referências.


Nenhuma referência listada em IDEAS.


Você pode ajudá-los a preencher este formulário.


O projeto CitEc ainda não encontrou citações para este item.


Este item não está listado na Wikipédia, em uma lista de leitura ou entre os principais itens em IDEAS.


Estatisticas.


Correções.


Ao solicitar uma correção, mencione o identificador deste item: RePEc: aes: infoec: v: 17: y: 2018: i: 3: p: 60-74. Veja informações gerais sobre como corrigir o material no RePEc.


Para questões técnicas relativas a este item, ou para corrigir seus autores, títulos, resumo, informações bibliográficas ou de download, contate: (Paul Pocatilu)


Se você é autor deste item e ainda não está registrado no RePEc, recomendamos que você o faça aqui. Isso permite vincular seu perfil a este item. Ele também permite que você aceite citações em potencial para este item sobre o qual não temos certeza.


Se as referências faltam completamente, você pode adicioná-las usando este formulário.


Se as referências completas listarem um item que está presente no RePEc, mas o sistema não ligou a ele, você pode ajudar com este formulário.


Se você souber de itens faltantes citando este, você pode nos ajudar a criar esses links, adicionando as referências relevantes da mesma maneira que acima, para cada item referente. Se você é um autor registrado deste item, você também pode querer verificar a guia "citações" em seu perfil, pois pode haver citações em espera de confirmação.


Observe que as correções podem levar algumas semanas para filtrar os vários serviços do RePEc.


Mais serviços.


Siga as séries, revistas, autores e Mais.


Novos trabalhos por e-mail.


Inscreva-se para novas adições ao RePEc.


Registro de autor.


Perfis públicos para pesquisadores de economia.


Vários rankings de pesquisa em Economia e Campos relacionados.


Quem era um aluno de quem, usando RePEc.


RePEc Biblio.


Artigos curados e amp; artigos sobre vários temas econômicos.


Carregue o seu papel para ser listado em RePEc e IDEAS.


EconAcademics.


Agregador de blogs para pesquisa econômica.


Plágio.


Casos de plágio em Economia.


Documentos de mercado de trabalho.


Série de papel de trabalho RePEc dedicada ao mercado de trabalho.


Fantasy League.


Imagine que você está no comando de um departamento de economia.


Serviços do StL Fed.


Dados, pesquisa, aplicativos e mais do St. Louis Fed.


11 Práticas recomendadas para sistemas de baixa latência.


Foram 8 anos desde que o Google notou que um extra de 500ms de latência caiu o tráfego em 20% e a Amazon percebeu que 100ms de latência extra caíram as vendas em 1%. Desde então, os desenvolvedores estiveram correndo para o fundo da curva de latência, culminando em desenvolvedores de front-end espremendo todos os últimos milésimos de segundo de seu JavaScript, CSS e HTML. O que se segue é uma caminhada aleatória através de uma variedade de práticas recomendadas para ter em mente ao projetar sistemas de baixa latência. A maioria dessas sugestões são levadas ao extremo lógico, mas é claro que podem ser feitas compensações. (Obrigado a um usuário anônimo por fazer esta pergunta no Quora e me fazer colocar meus pensamentos por escrito).


Escolha o idioma certo.


As linguagens de script não precisam se aplicar. Embora eles continuem ficando cada vez mais rápidos, quando você está olhando para raspar os últimos milissegundos do seu tempo de processamento, você não pode ter a sobrecarga de um idioma interpretado. Além disso, você quer um modelo de memória forte para ativar a programação sem bloqueio para que você esteja olhando Java, Scala, C ++ 11 ou Go.


Mantenha tudo na memória.


I / O irá matar sua latência, portanto, certifique-se de que todos os seus dados estão na memória. Isso geralmente significa gerenciar suas próprias estruturas de dados em memória e manter um registro persistente, para que você possa reconstruir o estado após uma reinicialização de máquina ou processo. Algumas opções para um registro persistente incluem Bitcask, Krati, LevelDB e BDB-JE. Alternativamente, você pode fugir com a execução de um banco de dados local, persistente na memória, como redis ou MongoDB (com dados da memória & gt; & gt;). Observe que você pode perder alguns dados sobre falhas devido à sua sincronização de fundo no disco.


Mantenha dados e processamento colocados.


O lúpulo de rede é mais rápido do que o disco, mas mesmo assim eles vão adicionar muitas despesas gerais. Idealmente, seus dados devem caber inteiramente na memória em um host. Com a AWS fornecendo quase 1/4 TB de RAM na nuvem e servidores físicos que oferecem múltiplas TBs, isso geralmente é possível. Se você precisa executar em mais de um host, você deve garantir que seus dados e solicitações sejam adequadamente particionados para que todos os dados necessários para atender uma determinada solicitação estejam disponíveis localmente.


Mantenha o sistema subutilizado.


A baixa latência requer sempre recursos para processar a solicitação. Não tente executar no limite do que seu hardware / software pode fornecer. Sempre tem muita sala de cabeça para rajadas e depois algumas.


Mantenha os parâmetros de contexto ao mínimo.


Os switches de contexto são um sinal de que você está fazendo mais trabalho de computação do que você tem recursos. Você quer limitar seu número de segmentos ao número de núcleos em seu sistema e colocar cada segmento no seu núcleo.


Mantenha suas leituras seqüenciais.


Todas as formas de armazenamento, murchar é rotacional, baseado em flash ou memória, melhoram significativamente quando usado sequencialmente. Ao emitir leituras seqüenciais para a memória, você desencadeia o uso da pré-busca no nível de RAM, bem como no nível de cache da CPU. Se for feito corretamente, a próxima peça de dados que você precisa estará sempre em cache L1 antes de precisar. A maneira mais fácil de ajudar esse processo é fazer uso intenso de matrizes de tipos ou tipos de dados primitivos. Os ponteiros a seguir, seja através do uso de listas vinculadas ou através de matrizes de objetos, devem ser evitados a todo custo.


Lote suas escritas.


Isso parece contraintuitivo, mas você pode obter melhorias significativas no desempenho através de gravações por lotes. No entanto, existe um equívoco de que isso significa que o sistema deve aguardar uma quantidade arbitrária de tempo antes de escrever. Em vez disso, um segmento deve girar em um loop apertado fazendo I / O. Cada escrita irá lote todos os dados que chegaram desde a última gravação emitida. Isso faz com que um sistema muito rápido e adaptável.


Respeite seu cache.


Com todas essas otimizações no local, o acesso à memória rapidamente se torna um gargalo. Fixar threads para seus próprios núcleos ajuda a reduzir a poluição do cache da CPU e as E / S seqüenciais também ajudam a pré-carregar o cache. Além disso, você deve manter os tamanhos de memória para baixo usando tipos de dados primitivos para que mais dados se encaixem no cache. Além disso, você pode procurar algoritmos cache-inconscientes que funcionam recursivamente, quebrando os dados até que ele se encaixe no cache e depois faça qualquer processamento necessário.


Não bloqueando o máximo possível.


Faça com que os amigos não bloqueiem e aguardem estruturas e algoritmos de dados gratuitos. Toda vez que você usa um bloqueio, você precisa baixar a pilha para o sistema operacional para mediar o bloqueio, que é uma enorme sobrecarga. Muitas vezes, se você sabe o que está fazendo, você pode contornar os bloqueios através da compreensão do modelo de memória da JVM, C ++ 11 ou Go.


Async, tanto quanto possível.


Qualquer processamento e particularmente qualquer E / S que não seja absolutamente necessário para a construção da resposta deve ser feito fora do caminho crítico.


Paralelize o máximo possível.


Qualquer processamento e particularmente qualquer E / S que possa acontecer em paralelo deve ser feito em paralelo. Por exemplo, se sua estratégia de alta disponibilidade inclui o log de transações para o disco e o envio de transações para um servidor secundário, essas ações podem acontecer em paralelo.


Quase tudo isso vem de seguir o que o LMAX está fazendo com seu projeto Disruptor. Leia sobre isso e siga tudo o que Martin Thompson faz.


Compartilhar isso:


Relacionados.


Publicado por.


Benjamin Darfler.


29 pensamentos sobre & ldquo; 11 melhores práticas para sistemas de baixa latência & rdquo;


E feliz em estar na sua lista 🙂


Bom artigo. One beef: Go doesn & # 8217; t tem um modelo de memória sofisticado como Java ou C ++ 11. Se o seu sistema se encaixa com a rotina da rotina e a arquitetura dos canais, é bom demais, sem sorte. O AFAIK não é possível excluir o agendador de tempo de execução, portanto, não há falhas de sistema operacional nativas e a capacidade de criar suas próprias estruturas de dados livres de bloqueio como (colunas SPSC / anejadores) também faltam severamente.


Obrigado pela resposta. Embora o modelo de memória Go (golang / ref / mem) possa não ser tão robusto quanto o Java ou o C ++ 11, tive a impressão de que você ainda poderia criar estruturas de dados sem bloqueio usando isso. Por exemplo https: // github / textnode / gringo, https: // github / scryner / lfreequeue e https: // github / mocchira / golfhash. Talvez eu estivesse faltando alguma coisa? É certo que eu sei muito menos sobre o Go do que a JVM.


Benjamin, o modelo de memória Go detalhado aqui: golang / ref / mem é principalmente em termos de canais e mutexes. Eu olhei através dos pacotes que você listou e enquanto as estruturas de dados existem & # 8220; lock free & # 8221; eles não são equivalentes ao que um pode construir em Java / C ++ 11. O pacote de sincronização a partir de agora, não tem suporte para átomos relaxados ou a semântica de aquisição / lançamento do C ++ 11. Sem esse suporte, é difícil construir estruturas de dados SPSC tão eficientes quanto as possíveis em C ++ / Java. Os projetos que você liga usam atomic. Add & # 8230; que é um átomo consistente consecutivamente. Ele é construído com XADD como deveria ser # 8211; https: //github/tonnerre/golang/blob/master/src/pkg/sync/atomic/asm_amd64.s.


Eu não estou tentando derrubar Ir para baixo. É preciso um esforço mínimo para escrever IO assíncrono e concorrente.


código suficientemente rápido para a maioria das pessoas. A biblioteca std também está altamente ajustada para o desempenho. A Golang também tem suporte para estruturas que estão faltando em Java. Mas, como está, penso que o modelo de memória simplista e o tempo de execução da rotina estão no caminho da construção do tipo de sistemas de que você está falando.


Obrigado pela resposta em profundidade. Espero que as pessoas achem isso útil.


Enquanto um & # 8216; native & # 8217; O idioma provavelmente é melhor, não é estritamente necessário. O Facebook nos mostrou que pode ser feito em PHP. Concedido eles usam o PHP pré-compilado com sua máquina HHVM. Mas é possível!


Infelizmente, o PHP ainda não possui um modelo de memória aceitável, mesmo que o HHVM melhore significativamente a velocidade de execução.


Enquanto eu lutarei para usar linguagens de nível superior, tanto quanto o próximo cara, acho que a única maneira de alcançar os aplicativos de baixa latência que as pessoas estão procurando é deslizar para um idioma como C. Parece que a mais difícil é escrever em um idioma, mais rápido ele executa.


Eu recomendo que você olhe para o trabalho que está sendo feito nos projetos e blogs aos quais eu liguei. A JVM está rapidamente se tornando o ponto quente para esses tipos de sistemas porque fornece um modelo de memória forte e uma coleta de lixo que permitem a programação sem bloqueio quase impossivel com um modelo de memória fraco ou indefinido e contadores de referência para gerenciamento de memória.


Olharei, Benjamin. Obrigado por apontá-los.


A coleta de lixo para programação sem bloqueio é um pouco de um deus ex machina. As filas MPMC e SPSC podem ser criadas sem necessidade de GC. Há também muitas maneiras de fazer programação sem bloqueio sem coleta de lixo e a contagem de referências não é a única maneira. Os ponteiros de perigo, RCU, Proxy-Collectors, etc, fornecem suporte para recuperação diferida e geralmente são codificados em suporte de um algoritmo (não genérico), portanto, eles geralmente são muito mais fáceis de construir. É claro que o trade-off reside no fato de que os GCs de qualidade de produção têm muito trabalho colocado neles e ajudarão o programador menos experiente a escrever algoritmos sem bloqueio (eles deveriam estar fazendo isso?) Sem codificação de esquemas de recuperação diferida . Alguns links sobre o trabalho realizado neste campo: cs. toronto. edu/


Sim C / C ++ recentemente ganhou um modelo de memória, mas isso não significa que eles eram completamente inadequados para o código sem bloqueio anteriormente. O GCC e outros compiladores de alta qualidade tinham diretrizes específicas do compilador para fazer programação gratuita de bloqueio em plataformas suportadas por um tempo realmente grande # 8211; não era padronizado na língua. Linux e outras plataformas forneceram essas primitivas por algum tempo também. A posição única de Java foi que forneceu um modelo de memória formalizado que garantiu trabalhar em todas as plataformas suportadas. Embora, em princípio, isso seja incrível, a maioria dos desenvolvedores do lado do servidor trabalham em uma plataforma (Linux / Windows). Eles já tinham as ferramentas para criar código sem bloqueio para sua plataforma.


GC é uma ótima ferramenta, mas não é necessária. Tem um custo tanto em termos de desempenho como em complexidade (todos os truques necessários para evitar STW GC). C ++ 11 / C11 já possui suporte para modelos de memória adequados. Não vamos esquecer que as JVMs não têm responsabilidade em suportar a API insegura no futuro. O código inseguro é & # 8220; unsafe & # 8221; então você perde os benefícios das características de segurança da Java. Finalmente, o código inseguro usado para criar memória e simular estruturas em Java parece muito mais feio do que as estruturas C / C ++ onde o compilador está fazendo isso funciona de maneira confiável. C e C ++ também fornecem acesso a todas as ferramentas elétricas específicas de plataforma de baixo nível, como PAUSE ins, SSE / AVX / NEON etc. Você pode até ajustar seu layout de código através de scripts de linker! O poder fornecido pela cadeia de ferramentas C / C ++ é realmente incomparável pela JVM. O Java é uma ótima plataforma, no entanto, acho que a maior vantagem é que a lógica comercial comum (90% do seu código?) Ainda pode depender do GC e dos recursos de segurança e fazer uso de bibliotecas altamente sintonizadas e testadas escritas com inseguro. Este é um grande trade-off entre obter os últimos 5% de perf e ser produtivo. Um trade-off que faz sentido para muitas pessoas, mas um trade-off, no entanto. Escrever um código de aplicação complicado em C / C ++ é um pesadelo depois de tudo.


No dia 10 de março de 2018 às 12:52, CodeDependents escreveu:


& gt; Graham Swan comentou: "Tenho uma olhada, Benjamin. Obrigado por & gt; apontando para fora. & # 8221;


Falta o 12: Não use linguagens coletadas Garbadge. GC é um gargalo na piora. Provavelmente, interrompe todos os tópicos. É um global. Isso distrai o arquiteto para gerenciar um dos recursos mais craterais (CPU-near memory).


Na verdade, muito deste trabalho vem diretamente de Java. Para fazer uma programação livre de bloqueio, você precisa de um modelo de memória claro, que c ++ recentemente ganhou recentemente. Se você sabe trabalhar com GC e não contra isso, você pode criar sistemas de baixa latência com muita facilidade.


Eu tenho que concordar com Ben aqui. Houve muitos progressos no paralelismo do GC na última década, ou seja, com o coletor G1 sendo o incantation mais recente. Pode levar um pouco de tempo para sintonizar o heap e vários botões para obter o GC para coletar com quase nenhuma pausa, mas isso contrasta em comparação com o tempo de desenvolvimento necessário para não ter GC.


Você pode até dar um passo adiante e criar sistemas que produzem tão pouco lixo que você pode facilmente empurrar o seu GC fora da sua janela de operação. É assim que todas as lojas comerciais de alta freqüência o fazem quando são executados na JVM.


A coleta de lixo para programação sem bloqueio é um pouco de um deus ex machina. As filas MPMC e SPSC podem ser criadas sem necessidade de GC. Há também muitas maneiras de fazer programação sem bloqueio sem coleta de lixo e a contagem de referências não é a única maneira. Os ponteiros de perigo, RCU, Proxy-Collectors, etc, fornecem suporte para recuperação diferida e são codificados em suporte de um algoritmo (não genérico), portanto, eles são muito mais fáceis de construir. É claro que o trade-off reside no fato de que os GCs de qualidade de produção têm muito trabalho colocado neles e ajudarão o programador menos experiente a escrever algoritmos sem bloqueio (eles deveriam estar fazendo isso?) Sem codificação de esquemas de recuperação diferida . Alguns links sobre o trabalho realizado neste campo: cs. toronto. edu/


Sim C / C ++ recentemente ganhou um modelo de memória, mas isso não significa que eles eram completamente inadequados para o código sem bloqueio anteriormente. O GCC e outros compiladores de alta qualidade tinham diretrizes específicas do compilador para fazer programação gratuita de bloqueio em plataformas suportadas por um tempo realmente grande # 8211; não era padronizado na língua. Linux e outras plataformas forneceram essas primitivas por algum tempo também. A posição única de Java foi que forneceu um modelo de memória formalizado que garantiu trabalhar em todas as plataformas suportadas. Embora, em princípio, isso seja incrível, a maioria dos desenvolvedores do lado do servidor trabalham em uma plataforma (Linux / Windows). Eles já tinham as ferramentas para criar código sem bloqueio para sua plataforma.


GC é uma ótima ferramenta, mas não é necessária. Tem um custo tanto em termos de desempenho quanto em complexidade (todos os truques necessários para atrasar e evitar STW GC). C ++ 11 / C11 já possui suporte para modelos de memória adequados. Não vamos esquecer que as JVMs não têm responsabilidade em suportar a API insegura no futuro. O código inseguro é & # 8220; unsafe & # 8221; então você perde os benefícios das características de segurança da Java. Finalmente, o código inseguro usado para criar memória e simular estruturas em Java parece muito mais feio do que as estruturas C / C ++ onde o compilador está fazendo isso funciona de maneira confiável. C e C ++ também fornecem acesso a todas as ferramentas elétricas específicas de plataforma de baixo nível, como PAUSE ins, SSE / AVX / NEON etc. Você pode até ajustar seu layout de código através de scripts de linker! O poder fornecido pela cadeia de ferramentas C / C ++ é realmente incomparável pela JVM. O Java é uma ótima plataforma, no entanto, acho que a maior vantagem é que a lógica comercial comum (90% do seu código?) Ainda pode depender do GC e dos recursos de segurança e fazer uso de bibliotecas altamente sintonizadas e testadas escritas com inseguro. Este é um grande trade-off entre obter os últimos 5% de perf e ser produtivo. Um trade-off que faz sentido para muitas pessoas, mas um trade-off, no entanto. Escrever um código de aplicação complicado em C / C ++ é um pesadelo depois de tudo.


& gt; Não use linguagens coletadas garbadge.


Ou, pelo menos, & # 8220; tradicional & # 8221; Lixo coletado línguas. Porque eles são diferentes & # 8211; enquanto Erlang também tem um colecionador, não criou gargalos porque não pára o mundo & # 8217; t & # 8220; pára o mundo & # 8221; como Java, enquanto colecionava lixo e # 8211; em vez disso, interrompe os microcréditos pequenos individuais & # 8220; & # 8221; em uma escala de microssegunda, portanto, não é visível no grande.


Reescreva isso para & # 8220; tradicional & # 8221; algoritmos [i] de coleta de lixo [/ i]. Na LMAX usamos o Azul Zing, e apenas usando uma JVM diferente com uma abordagem diferente para a coleta de lixo, vimos grandes melhorias no desempenho, porque os GCs maiores e menores são ordens de magnitude mais baratas.


Existem outros custos que compensam isso, é claro: você usa um monte muito mais, e o Zing não é barato.


Reblogged this em Java Prorgram Exemplos e comentou:


Um dos artigos de leitura obrigatória para programadores Java, é a lição que você aprenderá depois de passar um tempo considerável de afinação e desenvolver sistemas de baixa latência em Java em 10 minutos.


Revivendo um tópico antigo, mas (incrivelmente) isso deve ser apontado:


1) Linguagens de nível superior (por exemplo, Java) não desejam a funcionalidade do hardware que não está disponível para idiomas de nível inferior (por exemplo, C); declarar que assim e assim é completamente impossível & # 8221; em C, facilmente acessível em Java, é um lixo completo sem reconhecer que o Java é executado em hardware virtual onde a JVM deve sintetizar a funcionalidade exigida pelo Java, mas não fornecida pelo hardware físico. Se uma JVM (por exemplo, escrita em C) pode sintetizar a funcionalidade X, então também pode um programador C.


2) & # 8220; Lock free & # 8221; não é o que as pessoas pensam, exceto quase por coincidência em certas circunstâncias, como o único núcleo x86; multicore x86 não pode ser executado sem bloqueio sem barreiras de memória, que tem complexidades e custos semelhantes ao bloqueio regular. De acordo com 1 acima, se o Lock Free funcionar em um determinado ambiente, é porque ele é suportado pelo hardware, ou emulado / sintetizado por software em um ambiente virtual.


Great Points Julius. O ponto que eu estava tentando (talvez sem sucesso) é que é proibitivamente difícil aplicar muitos desses padrões em C, pois eles dependem do GC. Isso vai além do simples uso de barreiras de memória. Você também deve considerar a liberação de memória, o que fica particularmente difícil quando você está lidando com algoritmos livres de segurança e sem espera. É aqui que o GC adiciona uma grande vitória. Dito isto, eu ouço que Rust tenha algumas idéias muito interessantes sobre a propriedade da memória que possam começar a abordar algumas dessas questões.


Como funcionam os sistemas comerciais.


A negociação automatizada algorítmica ou a negociação algorítmica foi no centro do mundo comercial há mais de uma década. A porcentagem de volumes atribuídos à negociação automatizada algorítmica teve um aumento significativo na última década. Como resultado, tornou-se um mercado altamente competitivo que é fortemente dependente da tecnologia. Conseqüentemente, a arquitetura básica de sistemas de negociação automatizados que executam estratégias algorítmicas sofreu grandes mudanças ao longo da última década e continua a fazê-lo. Para as empresas, especialmente aquelas que utilizam sistemas de negociação de alta freqüência, tornou-se uma necessidade de inovar em tecnologia para competir no mundo do comércio algorítmico, tornando assim a comercialização de algoritmos um foco de avanços nas tecnologias de computadores e redes.


Nesta publicação, desmistificaremos a arquitetura por trás dos sistemas de negociação automatizada para nossos leitores. Comparamos a nova arquitetura dos sistemas de negociação automatizados com a arquitetura comercial tradicional e compreendemos alguns dos principais componentes por trás desses sistemas.


Arquitetura Tradicional.


Qualquer sistema comercial, conceitualmente, não passa de um bloco computacional que interage com a troca em dois fluxos diferentes.


Recebe dados de mercado Envia solicitações de pedidos e recebe respostas da troca.


Os dados de mercado que são recebidos geralmente informam o sistema do último livro de pedidos. Pode conter algumas informações adicionais, como o volume negociado até o momento, o último preço e quantidade negociada para um script. No entanto, para tomar uma decisão sobre os dados, o comerciante pode precisar analisar valores antigos ou derivar determinados parâmetros do histórico. Para atender a isso, um sistema convencional teria um banco de dados histórico para armazenar os dados do mercado e as ferramentas para usar esse banco de dados. A análise também envolveria um estudo das tradições passadas pelo comerciante. Daí, outro banco de dados para armazenar as decisões comerciais também. Por último, mas não menos importante, uma interface GUI para o comerciante visualizar todas essas informações na tela.


Todo o sistema comercial pode agora ser dividido em.


A troca (s) - o mundo externo O servidor Mercado Data receptor Comercializar dados do mercado Armazenar ordens geradas pelo usuário Aplicação Pegue as entradas do usuário, incluindo as decisões de negociação Interface para visualizar as informações, incluindo os dados e ordens Um gerente de pedidos enviando ordens para o troca.


Nova arquitetura.


A arquitetura tradicional não pôde aumentar as necessidades e demandas do comércio automatizado com DMA. A latência entre a origem do evento para a geração da ordem foi além da dimensão do controle humano e entrou nos reinos de milissegundos e microssegundos. Assim, as ferramentas para lidar com dados do mercado e sua análise precisava se adaptar de acordo. O gerenciamento de pedidos também precisa ser mais robusto e capaz de lidar com mais pedidos por segundo. Uma vez que o período de tempo é tão pequeno em comparação com o tempo de reação humano, o gerenciamento de riscos também precisa lidar com pedidos em tempo real e de forma completamente automática.


Por exemplo, mesmo que o tempo de reação para uma ordem seja de 1 milissegundo (o que é bastante comparado às latências que vemos hoje), o sistema ainda é capaz de fazer 1000 decisões comerciais em um único segundo. Isso significa que cada uma dessas 1000 decisões comerciais deve passar pelo gerenciamento de riscos no mesmo segundo para alcançar a troca. Este é apenas um problema de complexidade. Uma vez que a arquitetura agora envolve lógica automatizada, 100 comerciantes agora podem ser substituídos por um único sistema de negociação automatizado. Isso adiciona escala ao problema. Então, cada uma das unidades lógicas gera 1000 pedidos e 100 dessas unidades significam 100.000 pedidos a cada segundo. Isso significa que a tomada de decisão e a peça de envio de pedidos precisam ser muito mais rápidas do que o receptor de dados de mercado, de modo a combinar a taxa de dados.


Por isso, o nível de infra-estrutura que este módulo exige deve ser muito superior em comparação com o de um sistema tradicional (discutido na seção anterior). Daí o motor que executa a lógica da tomada de decisão, também conhecido como o mecanismo "Processamento de eventos complexos", ou CEP, mudou-se do aplicativo para o servidor. A camada de aplicação, agora, é pouco mais do que uma interface de usuário para visualizar e fornecer parâmetros para o CEP.


O problema da escala também leva a uma situação interessante. Digamos que 100 lógicas diferentes estão sendo executadas em um evento de dados de mercado único (como discutido no exemplo anterior). No entanto, pode haver peças comuns de cálculos complexos que precisam ser executados para a maioria das 100 unidades lógicas. Por exemplo, cálculo de gregos para opções. Se cada lógica funcionasse de forma independente, cada unidade faria o mesmo cálculo grega que iria desnecessariamente usar os recursos do processador. Para otimizar a redundância do cálculo, os cálculos redundantes complexos geralmente são mantidos em um mecanismo de cálculo separado que fornece os gregos como uma entrada para o CEP.


Embora a camada de aplicação seja principalmente uma visão, algumas das verificações de risco (que agora são operações com fome de recursos devido ao problema da escala), podem ser descarregadas para a camada de aplicação, especialmente aquelas que estão relacionadas com sanidade de entradas de usuários como o dedo gordo erros. O resto das verificações de risco são realizadas agora por um Sistema de Gerenciamento de Risco (RMS) separado no Gerenciador de Pedidos (OM), imediatamente antes de liberar um pedido. O problema da escala também significa que, quando anteriormente, havia 100 comerciantes diferentes gerenciando seus riscos, agora existe apenas um sistema RMS para gerenciar riscos em todas as unidades / estratégias lógicas. No entanto, algumas verificações de risco podem ser específicas para certas estratégias e alguns talvez precisem ser feitos em todas as estratégias. Daí o próprio RMS envolve, RMS de nível de estratégia (SLRMS) e RMS global (GRMS). Também pode envolver uma UI para visualizar o SLRMS e o GRMS.


Emergência de protocolos para sistemas de negociação automatizados.


Com inovações, as necessidades são necessárias. Uma vez que a nova arquitetura foi capaz de dimensionar para muitas estratégias por servidor, surgiu a necessidade de se conectar a vários destinos a partir de um único servidor. Assim, o gerenciador de pedidos hospedou vários adaptadores para enviar pedidos para vários destinos e receber dados de várias trocas. Cada adaptador atua como um intérprete entre o protocolo que é entendido pela troca e o protocolo de comunicação dentro do sistema. Intercâmbios múltiplos significam adaptadores múltiplos.


No entanto, para adicionar uma nova troca ao sistema, um novo adaptador deve ser projetado e conectado à arquitetura, uma vez que cada troca segue seu protocolo apenas otimizado para recursos fornecidos pela troca. Para evitar esse incômodo de adição de adaptador, os protocolos padrão foram projetados. O mais proeminente entre eles é o protocolo FIX (Financial Information Exchange) (veja nossa publicação na introdução ao protocolo FIX). Isso não só torna gerenciável conectar-se a destinos diferentes, mas também reduzir drasticamente o mercado para quando se conectar a um novo destino. Para leitura adicional: Conectando o FXCM ao FIX, um tutorial detalhado.


A presença de protocolos padrão facilita a integração com fornecedores de terceiros, também para análises ou feeds de dados de mercado. Como resultado, o mercado torna-se muito eficiente, pois a integração com um novo destino / fornecedor não é mais uma restrição.


Além disso, a simulação torna-se muito fácil, pois receber dados do mercado real e enviar ordens para um simulador é apenas uma questão de usar o protocolo FIX para se conectar a um simulador. O próprio simulador pode ser construído internamente ou adquirido de um fornecedor de terceiros. Os dados gravados de forma semelhante apenas podem ser reproduzidos com os adaptadores sendo agnósticos para saber se os dados estão sendo recebidos do mercado ao vivo ou de um conjunto de dados gravados.


Emergência de arquiteturas de baixa latência.


Com os blocos de construção de um sistema de negociação algorítmica no local, as estratégias otimizadas na capacidade de processar enormes quantidades de dados em tempo real e tomar decisões comerciais rápidas. Mas com o advento de protocolos de comunicação padrão como FIX, a barreira de entrada de tecnologia para configurar uma mesa de negociação algorítmica, tornou-se menor e, portanto, mais competitivo. À medida que os servidores obtiveram mais memória e freqüências de clock mais altas, o foco mudou para reduzir a latência para a tomada de decisões. Ao longo do tempo, reduzir a latência tornou-se uma necessidade por muitas razões, como:


A estratégia faz sentido apenas em um ambiente de baixa latência. Sobrevivência dos mais aptos - os concorrentes escolhem você se você não for rápido o suficiente.


O problema, no entanto, é que a latência é realmente um termo abrangente que engloba vários atrasos diferentes. Para quantificar todos eles em um termo genérico, geralmente não faz muito sentido. Embora seja muito fácil de entender, é bastante difícil quantificar. Por isso, torna-se cada vez mais importante como o problema da redução da latência é abordado.


Se olharmos para o ciclo de vida básico,


Um pacote de dados de mercado é publicado pela troca O pacote viaja pelo fio O pacote chega a um roteador do lado do servidor. O roteador encaminha o pacote pela rede do lado do servidor. O pacote chega na porta Ethernet do servidor. Dependendo se este é processamento UDP / TCP ocorre e o pacote despojado de seus cabeçalhos e trailers faz o caminho para a memória do adaptador. O adaptador então analisa o pacote e o converte em um formato interno para a plataforma de negociação algorítmica. Este pacote agora viaja através dos vários módulos do sistema - CEP, tick shop, etc. O CEP analisa e envia uma solicitação de pedido. através do reverso do ciclo como o pacote de dados do mercado.


Alta latência em qualquer uma dessas etapas garante uma latência alta durante todo o ciclo. Assim, a otimização de latência geralmente começa com o primeiro passo neste ciclo que está no nosso controle, ou seja, "o pacote viaja através do fio". A coisa mais fácil de fazer aqui seria encurtar a distância até o destino, tanto quanto possível. Colocações são instalações fornecidas por trocas para hospedar o servidor de negociação nas proximidades da troca. O diagrama a seguir ilustra os ganhos que podem ser feitos cortando a distância.


Para qualquer tipo de estratégia de alta freqüência envolvendo um único destino, Colocation tornou-se um facto deve. No entanto, as estratégias que envolvem múltiplos destinos precisam de um planejamento cuidadoso. Vários fatores, como o tempo gasto pelo destino para responder pedidos de pedidos e sua comparação com o tempo de ping entre os dois destinos, devem ser considerados antes de tomar essa decisão. A decisão também pode depender da natureza da estratégia.


A latência da rede geralmente é o primeiro passo na redução da latência geral de um sistema de comércio algorítmico. No entanto, existem muitos outros locais onde a arquitetura pode ser otimizada.


Latência de propagação.


A latência de propagação significa o tempo necessário para enviar os bits ao longo do fio, limitados pela velocidade da luz, é claro.


Foram introduzidas várias otimizações para reduzir a latência de propagação além de reduzir a distância física. Por exemplo, o tempo estimado de ida e volta para um cabo comum entre Chicago e Nova York é de 13,1 milissegundos. As redes de propagação, em outubro de 2018, anunciaram melhorias de latência que trouxeram o tempo estimado de ida e volta para 12,98 milissegundos. A comunicação por microondas foi adotada ainda mais por empresas como Tradeworx, trazendo o tempo estimado de ida e volta para 8,5 milissegundos. Observe que o mínimo teórico é de cerca de 7,5 milissegundos. As inovações contínuas estão empurrando os limites da ciência e alcançando rapidamente o limite teórico da velocidade da luz. Os últimos desenvolvimentos em comunicação a laser, adotados anteriormente em tecnologias de defesa, afugentaram ainda mais uma latência já diluída por nanosegundos em curtas distâncias.


Latência de processamento de rede.


Latência de processamento de rede significa latência introduzida por roteadores, switches, etc.


O próximo nível de otimização na arquitetura de um sistema de negociação algorítmico seria o número de lúpulos que um pacote levaria para viajar do ponto A ao ponto B. Um salto é definido como uma parte do caminho entre a fonte e o destino durante o qual um pacote não passa por um dispositivo físico como um roteador ou um switch. Por exemplo, um pacote pode percorrer a mesma distância através de dois caminhos diferentes. Mas pode ter dois saltos no primeiro caminho versus 3 saltos no segundo. Supondo que o atraso de propagação seja o mesmo, os roteadores e switches introduzem sua própria latência e geralmente como uma regra de polegar, mais o lúpulo é a latência adicionada.


A latência do processamento de rede também pode ser afetada pelo que chamamos de microbursas. Microbursts são definidos como um aumento súbito da taxa de transferência de dados que pode não afetar necessariamente a taxa média de transferência de dados. Uma vez que os sistemas de negociação algorítmica são baseados em regras, todos esses sistemas reagirão ao mesmo evento da mesma maneira. Como resultado, muitos sistemas participantes podem enviar ordens que levam a uma onda repentina de transferência de dados entre os participantes e o destino que leva a um microburst. O diagrama a seguir representa o que é um microburst.


A primeira figura mostra uma visão de 1 segundo da taxa de transferência de dados. Podemos ver que a taxa média está bem abaixo da largura de banda disponível de 1Gbps. No entanto, se mergulhar mais profundamente e olhar a imagem de segundos (a vista de 5 milissegundos), vemos que a taxa de transferência aumentou acima da largura de banda disponível várias vezes por segundo. Como resultado, os buffers de pacotes na pilha de rede, tanto nos pontos de extremidade da rede quanto nos roteadores e switches, podem transbordar. Para evitar isso, normalmente uma largura de banda muito superior à taxa média observada é geralmente alocada para um sistema de comércio algorítmico.


Latência de serialização.


A latência de serialização significa o tempo necessário para puxar os bits para dentro e fora do fio.


Um tamanho de pacote de 1500 bytes transmitidos em uma linha T1 (1.544.000 bps) produziria um atraso de serialização de cerca de 8 milissegundos. No entanto, o mesmo pacote de 1500 bytes usando um modem de 56K (57344bps) levaria 200 milissegundos. Uma linha Ethernet 1G reduziria essa latência para cerca de 11 microssegundos.


Latência de interrupção.


A latência de interrupção significa uma latência introduzida por interrupções ao receber os pacotes em um servidor.


A latência de interrupção é definida como o tempo decorrido entre quando uma interrupção é gerada quando a fonte da interrupção é atendida. Quando é gerada uma interrupção? Interrupções são sinais para o processador emitido por hardware ou software, indicando que um evento precisa de atenção imediata. O processador, por sua vez, responde suspendendo sua atividade atual, salvando seu estado e manipulando a interrupção. Sempre que um pacote é recebido no NIC, uma interrupção é enviada para lidar com os bits que foram carregados no buffer de recebimento da NIC. O tempo necessário para responder a esta interrupção não afeta apenas o processamento da nova carga útil, mas também a latência dos processos existentes no processador.


Solarflare introduziu onload aberto em 2018, que implementa uma técnica conhecida como bypass do kernel, onde o processamento do pacote não é deixado para o kernel do sistema operacional, mas para o próprio espaço de usuários. Todo o pacote é diretamente mapeado para o espaço do usuário pela NIC e é processado lá. Como resultado, as interrupções são completamente evitadas.


Como resultado, a taxa de processamento de cada pacote é acelerada. O diagrama a seguir demonstra claramente as vantagens do bypass do kernel.


Latência da aplicação.


A latência da aplicação significa o tempo gasto pelo processo para processar.


Isso depende dos vários pacotes, do processamento alocado para a lógica do aplicativo, da complexidade do cálculo envolvido, da eficiência da programação, etc. O aumento do número de processadores no sistema, em geral, reduzirá a latência da aplicação. O mesmo ocorre com o aumento da frequência do relógio. Muitos sistemas de negociação algorítmica aproveitam a dedicação de núcleos de processadores para elementos essenciais da aplicação, como a lógica de estratégia, por exemplo. Isso evita a latência introduzida pela troca do processo entre os núcleos.


Da mesma forma, se a programação da estratégia foi feita, tenha em mente os tamanhos de cache e a localização do acesso à memória, então haveria muitos hits no cache da memória resultando em uma redução adicional da latência. Para facilitar isso, muitos sistemas usam linguagens de programação de nível muito baixo para otimizar o código para a arquitetura específica dos processadores. Algumas empresas chegaram até a extensão da queima de cálculos complexos em hardware usando matrizes de portas totalmente programáveis ​​(FPGA). Com a crescente complexidade vem o custo crescente e o diagrama a seguir ilustra isso.


Níveis de sofisticação.


O mundo do comércio algorítmico de alta freqüência entrou em uma era de competição intensa. Com cada participante adotando novos métodos de expulsão da concorrência, a tecnologia progrediu aos trancos e barrancos. As arquiteturas de negociação algorítmica modernas são bastante complexas em comparação com as suas partes anteriores. Consequentemente, os sistemas avançados são mais caros de construir tanto em termos de tempo e dinheiro.


Conclusão:


Esta foi uma publicação detalhada sobre a arquitetura do sistema de negociação algorítmica, com certeza nós damos um conhecimento muito profundo dos componentes envolvidos e também dos vários desafios que os desenvolvedores de arquitetura precisam lidar / superar para construir sistemas de negociação automatizados robustos.


Se você quiser aprender vários aspectos da negociação algorítmica, consulte o Programa Executivo em Negociação Algorítmica (EPAT ™). O curso abrange módulos de treinamento como Statistics & amp; Econometria, Computação Financeira e Tecnologia e Algorítmica e Negociação quantitativa. EPAT ™ equipa você com os conjuntos de habilidades necessárias para construir uma carreira promissora na negociação algorítmica. Inscreva-se agora!


Posts Relacionados:


2 pensamentos sobre "Como os sistemas comerciais funcionam"


15 de dezembro de 2017.


Postagem muito boa. Eu simplesmente tropecei em seu blog e queria dizer que eu realmente gostei de navegar em suas postagens no blog. Afinal, vou me inscrever no seu feed e espero que você escreva novamente em breve!


15 de dezembro de 2017.


Estamos realmente satisfeitos por você gostar de nossas postagens. A apreciação é o que nos mantém em pé.


Certifique-se de continuar adicionando conteúdo fresco periodicamente. Compartilhe nossas postagens e ajude-nos a espalhar a palavra sobre como as pessoas podem aproveitar a partir de negociação algorítmica e quantitativa.


A Arquitetura LMAX.


LMAX é uma nova plataforma de comércio financeiro de varejo. Como resultado, tem que processar muitos negócios com baixa latência. O sistema é construído na plataforma JVM e centra-se em um Processador de lógica comercial que pode lidar com 6 milhões de pedidos por segundo em um único segmento. O Business Logic Processor é executado inteiramente na memória usando o sourcing de eventos. O Business Logic Processor é cercado por Disruptores - um componente de concorrência que implementa uma rede de filas que operam sem necessidade de bloqueios. Durante o processo de design, a equipe concluiu que as orientações recentes em modelos de concorrência de alto desempenho usando filas estão fundamentalmente em desacordo com o design moderno da CPU.


Ao longo dos últimos anos, continuamos ouvindo que "o almoço grátis acabou" [1] - não podemos esperar aumentos na velocidade individual da CPU. Então, para escrever código rápido, precisamos usar explicitamente vários processadores com software concorrente. Esta não é uma boa notícia - escrever código concorrente é muito difícil. Locks e semáforos são difíceis de argumentar e difíceis de testar - o que significa que estamos gastando mais tempo nos preocupando com a satisfação do computador do que estamos resolvendo o problema do domínio. Vários modelos de concorrência, como atores e memória transacional de software, visam tornar isso mais fácil - mas ainda há um fardo que apresenta erros e complexidade.


Então fiquei fascinado por falar sobre uma conversa no QCon London, em março do ano passado, da LMAX. LMAX é uma nova plataforma de comércio financeiro de varejo. A inovação comercial é que é uma plataforma de varejo - permitindo que qualquer pessoa troque em uma variedade de produtos derivados financeiros [2]. Uma plataforma de negociação como essa precisa uma latência muito baixa - os negócios devem ser processados ​​rapidamente porque o mercado está se movendo rapidamente. Uma plataforma de varejo acrescenta complexidade porque tem que fazer isso para muitas pessoas. Então, o resultado é mais usuários, com muitos negócios, todos os quais precisam ser processados ​​rapidamente. [3]


Dada a mudança para o pensamento multi-core, esse tipo de desempenho exigente naturalmente sugeriria um modelo de programação explicitamente concorrente - e, de fato, esse era seu ponto de partida. Mas o que chamou a atenção das pessoas no QCon foi que não era onde eles acabaram. Na verdade, eles acabaram fazendo toda a lógica de negócios para sua plataforma: todas as negociações, de todos os clientes, em todos os mercados - em um único tópico. Um segmento que processará 6 milhões de pedidos por segundo usando hardware de commodities. [4]


Processando muitas transações com baixa latência e nenhuma das complexidades do código concorrente - como posso resistir a cavar nisso? Felizmente, outra diferença que a LMAX tem para outras empresas financeiras é que eles estão bastante felizes em falar sobre suas decisões tecnológicas. Então, agora, a LMAX está em produção por um tempo, é hora de explorar seu design fascinante.


Estrutura geral.


Figura 1: arquitetura do LMAX em três blobs.


Em um nível superior, a arquitetura possui três partes.


processador de lógica de negócios [5] interruptores de saída do disruptor de entrada.


Como o próprio nome indica, o processador de lógica de negócios lida com toda a lógica do negócio na aplicação. Como eu indiquei acima, ele faz isso como um programa java single-threaded que reage a chamadas de método e produz eventos de saída. Consequentemente, é um programa java simples que não requer qualquer estrutura de plataforma para executar além da JVM em si, o que permite que ele seja executado facilmente em ambientes de teste.


Embora o Business Logic Processor possa ser executado em um ambiente simples para testes, há uma coreografia bastante mais envolvente para executá-lo em uma configuração de produção. As mensagens de entrada precisam ser retiradas de um gateway de rede e desmarcadas, replicadas e registradas no diário. As mensagens de saída precisam ser empacotadas para a rede. Essas tarefas são tratadas pelos disruptores de entrada e saída. Ao contrário do Business Logic Processor, estes são componentes concorrentes, uma vez que envolvem operações IO que são lentas e independentes. Eles foram projetados e construídos especialmente para LMAX, mas eles (como a arquitetura geral) são aplicáveis ​​em outros lugares.


Business Logic Processor.


Mantendo tudo na memória.


O Business Logic Processor recebe mensagens de entrada sequencialmente (na forma de uma invocação de método), executa lógica de negócios e emite eventos de saída. Ele opera totalmente na memória, não há banco de dados ou outra loja persistente. Manter todos os dados na memória tem dois benefícios importantes. Em primeiro lugar, é rápido - não há banco de dados para fornecer IO lento para acessar, nem há nenhum comportamento transacional a ser executado, já que todo o processamento é feito sequencialmente. A segunda vantagem é que simplifica a programação - não há mapeamento objeto / relacional para fazer. Todo o código pode ser escrito usando o modelo de objeto de Java sem ter que comprometer o mapeamento para um banco de dados.


Usar uma estrutura na memória tem uma conseqüência importante - o que acontece se tudo falhar? Mesmo os sistemas mais resilientes são vulneráveis ​​a alguém que puxa o poder. O coração de lidar com isso é Event Sourcing - o que significa que o estado atual do Business Logic Processor é inteiramente derivável processando os eventos de entrada. Enquanto o fluxo de eventos de entrada for mantido em uma loja durável (que é um dos trabalhos do disruptor de entrada), você sempre pode recriar o estado atual do mecanismo de lógica de negócios, repetindo os eventos.


Uma boa maneira de entender isso é pensar em um sistema de controle de versão. Os sistemas de controle de versão são uma seqüência de compromissos, em qualquer momento você pode construir uma cópia de trabalho aplicando esses compromissos. Os VCSs são mais complicados do que o Business Logic Processor porque eles devem suportar ramificações, enquanto o Business Logic Processor é uma seqüência simples.


Então, em teoria, você sempre pode reconstruir o estado do Business Logic Processor reprocessando todos os eventos. Na prática, no entanto, isso levaria muito tempo se você precisasse girar um. Assim, assim como nos sistemas de controle de versão, o LMAX pode fazer instantâneos do status Business Logic Processor e restaurar a partir dos instantâneos. Eles tomam um instantâneo todas as noites durante períodos de baixa atividade. Reiniciar o processador de lógica de negócios é rápido, um reinício completo - incluindo reiniciar a JVM, carregar um instantâneo recente e reproduzir um dia de revistas - demora menos de um minuto.


Os instantâneos tornam a criação de um novo Processador de lógica comercial mais rápido, mas não é suficientemente rápido se um processador de lógica comercial falhar às 2pm. Como resultado, o LMAX mantém múltiplos processadores de lógica de negócios funcionando o tempo todo [6]. Cada evento de entrada é processado por vários processadores, mas todos, exceto um processador, têm sua saída ignorada. Se o processador ao vivo falhar, o sistema muda para outro. Essa capacidade de lidar com o failover é outro benefício do uso do Sourcing de eventos.


Por causa do evento em réplicas, eles podem alternar entre os processadores em questão de micro-segundos. Além de tirar instantâneos todas as noites, eles também reinician os Processadores de lógica de negócios todas as noites. A replicação permite que eles façam isso sem tempo de inatividade, então eles continuam a processar negócios 24/7.


Para obter mais informações sobre Event Sourcing, veja o padrão de rascunho no meu site há alguns anos atrás. O artigo está mais focado em lidar com relacionamentos temporais do que com os benefícios que o LMAX usa, mas explica a idéia central.


O Sourcing de eventos é valioso porque permite que o processador funcione inteiramente na memória, mas tem outra vantagem considerável para o diagnóstico. Se ocorrer algum comportamento inesperado, a equipe copia a seqüência de eventos para o ambiente de desenvolvimento e os reproduz. Isso permite que eles examinem o que aconteceu muito mais facilmente do que é possível na maioria dos ambientes.


Esta capacidade de diagnóstico se estende ao diagnóstico de negócios. Existem algumas tarefas comerciais, como no gerenciamento de riscos, que requerem cálculos significativos que não são necessários para processar ordens. Um exemplo é obter uma lista dos 20 maiores clientes por perfil de risco com base em suas atuais posições de negociação. A equipe lida com isso girando um modelo de domínio replicado e executando a computação lá, onde não interferirá com o processamento da ordem do núcleo. Esses modelos de domínio de análise podem ter modelos de dados variantes, manter conjuntos de dados diferentes na memória e executar em máquinas diferentes.


Ajuste do desempenho.


Até agora eu expliquei que a chave para a velocidade do Business Logic Processor está fazendo tudo de forma seqüencial, na memória. Apenas fazendo isso (e nada realmente estúpido) permite aos desenvolvedores escrever código que pode processar 10K TPS [7]. Eles então descobriram que concentrar-se nos elementos simples de um bom código poderia trazer isso para a gama de 100K TPS. Isso só precisa de um código bem-feito e de pequenos métodos - essencialmente isso permite que o Hotspot faça um melhor trabalho de otimização e que as CPUs sejam mais eficientes no armazenamento em cache do código à medida que ele está sendo executado.


Demorou um pouco mais de astúcia para subir de outra ordem de grandeza. Há várias coisas que a equipe LMAX encontrou útil para chegar lá. Um deles era escrever implementações personalizadas das coleções java que foram projetadas para serem compatíveis com cache e cuidadoso com lixo [8]. Um exemplo disto é o uso de longas java primitivas como chaves hashmap com uma implementação de mapa com suporte de matriz especialmente escrita (LongToObjectHashMap). Em geral, descobriram que a escolha das estruturas de dados muitas vezes faz uma grande diferença, a maioria dos programadores apenas agarra qualquer lista que usou na última vez em vez de pensar qual implementação é a certa para este contexto. [9]


Outra técnica para alcançar esse nível superior de desempenho é colocar a atenção no teste de desempenho. Percebi há muito tempo que as pessoas falam muito sobre técnicas para melhorar o desempenho, mas a única coisa que realmente faz a diferença é testá-lo. Mesmo os bons programadores são muito bons na construção de argumentos de desempenho que acabam sendo errados, então os melhores programadores preferem perfilers e casos de teste para a especulação. [10] A equipe LMAX também descobriu que os testes de escrita em primeiro lugar é uma disciplina muito eficaz para testes de desempenho.


Modelo de programação.


Esse estilo de processamento apresenta alguns constrangimentos na forma como você escreve e organiza a lógica do negócio. O primeiro deles é que você deve provocar qualquer interação com serviços externos. Uma chamada de serviço externo será lenta e, com um único segmento, interromperá toda a máquina de processamento de pedidos. Como resultado, você não pode fazer chamadas para serviços externos dentro da lógica de negócios. Em vez disso, você precisa finalizar essa interação com um evento de saída e aguarde outro evento de entrada para recuperá-lo novamente.


Vou usar um exemplo simples não-LMAX para ilustrar. Imagine que você está fazendo um pedido de geléia com cartão de crédito. Um sistema de varejo simples levaria as informações do seu pedido, usaria um serviço de validação de cartão de crédito para verificar seu número de cartão de crédito e depois confirmaria seu pedido - tudo dentro de uma única operação. O processo que processa o seu pedido seria bloqueado enquanto esperava que o cartão de crédito fosse verificado, mas esse bloco não seria muito longo para o usuário, e o servidor sempre pode executar outro segmento no processador enquanto aguarda.


Na arquitetura LMAX, você dividiria essa operação em duas. A primeira operação capturaria as informações da ordem e terminaria pela saída de um evento (validação do cartão de crédito solicitado) para a empresa de cartão de crédito. O Business Logic Processor continuaria processando eventos para outros clientes até receber um evento validado por cartão de crédito em seu fluxo de eventos de entrada. Ao processar esse evento, realizaria as tarefas de confirmação para essa ordem.


Trabalhar neste tipo de estilo assíncrono orientado por eventos, é algo incomum - embora o uso de assíncrono para melhorar a capacidade de resposta de uma aplicação seja uma técnica familiar. Isso também ajuda o processo de negócios a ser mais resiliente, pois você precisa ser mais explícito ao pensar sobre as diferentes coisas que podem acontecer com o aplicativo remoto.


Uma segunda característica do modelo de programação reside no tratamento de erros. The traditional model of sessions and database transactions provides a helpful error handling capability. Should anything go wrong, it's easy to throw away everything that happened so far in the interaction. Session data is transient, and can be discarded, at the cost of some irritation to the user if in the middle of something complicated. If an error occurs on the database side you can rollback the transaction.


LMAX's in-memory structures are persistent across input events, so if there is an error it's important to not leave that memory in an inconsistent state. However there's no automated rollback facility. As a consequence the LMAX team puts a lot of attention into ensuring the input events are fully valid before doing any mutation of the in-memory persistent state. They have found that testing is a key tool in flushing out these kinds of problems before going into production.


Input and Output Disruptors.


Although the business logic occurs in a single thread, there are a number tasks to be done before we can invoke a business object method. The original input for processing comes off the wire in the form of a message, this message needs to be unmarshaled into a form convenient for Business Logic Processor to use. Event Sourcing relies on keeping a durable journal of all the input events, so each input message needs to be journaled onto a durable store. Finally the architecture relies on a cluster of Business Logic Processors, so we have to replicate the input messages across this cluster. Similarly on the output side, the output events need to be marshaled for transmission over the network.


Figure 2: The activities done by the input disruptor (using UML activity diagram notation)


The replicator and journaler involve IO and therefore are relatively slow. After all the central idea of Business Logic Processor is that it avoids doing any IO. Also these three tasks are relatively independent, all of them need to be done before the Business Logic Processor works on a message, but they can done in any order. So unlike with the Business Logic Processor, where each trade changes the market for subsequent trades, there is a natural fit for concurrency.


To handle this concurrency the LMAX team developed a special concurrency component, which they call a Disruptor [11].


The LMAX team have released the source code for the Disruptor with an open source licence.


At a crude level you can think of a Disruptor as a multicast graph of queues where producers put objects on it that are sent to all the consumers for parallel consumption through separate downstream queues. When you look inside you see that this network of queues is really a single data structure - a ring buffer. Each producer and consumer has a sequence counter to indicate which slot in the buffer it's currently working on. Each producer/consumer writes its own sequence counter but can read the others' sequence counters. This way the producer can read the consumers' counters to ensure the slot it wants to write in is available without any locks on the counters. Similarly a consumer can ensure it only processes messages once another consumer is done with it by watching the counters.


Figure 3: The input disruptor coordinates one producer and four consumers.


Output disruptors are similar but they only have two sequential consumers for marshaling and output.[12] Output events are organized into several topics, so that messages can be sent to only the receivers who are interested in them. Each topic has its own disruptor.


The disruptors I've described are used in a style with one producer and multiple consumers, but this isn't a limitation of the design of the disruptor. The disruptor can work with multiple producers too, in this case it still doesn't need locks.[13]


A benefit of the disruptor design is that it makes it easier for consumers to catch up quickly if they run into a problem and fall behind. If the unmarshaler has a problem when processing on slot 15 and returns when the receiver is on slot 31, it can read data from slots 16-30 in one batch to catch up. This batch read of the data from the disruptor makes it easier for lagging consumers to catch up quickly, thus reducing overall latency.


I've described things here, with one each of the journaler, replicator, and unmarshaler - this indeed is what LMAX does. But the design would allow multiple of these components to run. If you ran two journalers then one would take the even slots and the other journaler would take the odd slots. This allows further concurrency of these IO operations should this become necessary.


The ring buffers are large: 20 million slots for input buffer and 4 million slots for each of the output buffers. The sequence counters are 64bit long integers that increase monotonically even as the ring slots wrap.[14] The buffer is set to a size that's a power of two so the compiler can do an efficient modulus operation to map from the sequence counter number to the slot number. Like the rest of the system, the disruptors are bounced overnight. This bounce is mainly done to wipe memory so that there is less chance of an expensive garbage collection event during trading. (I also think it's a good habit to regularly restart, so that you rehearse how to do it for emergencies.)


The journaler's job is to store all the events in a durable form, so that they can be replayed should anything go wrong. LMAX does not use a database for this, just the file system. They stream the events onto the disk. In modern terms, mechanical disks are horribly slow for random access, but very fast for streaming - hence the tag-line "disk is the new tape".[15]


Earlier on I mentioned that LMAX runs multiple copies of its system in a cluster to support rapid failover. The replicator keeps these nodes in sync. All communication in LMAX uses IP multicasting, so clients don't need to know which IP address is the master node. Only the master node listens directly to input events and runs a replicator. The replicator broadcasts the input events to the slave nodes. Should the master node go down, it's lack of heartbeat will be noticed, another node becomes master, starts processing input events, and starts its replicator. Each node has its own input disruptor and thus has its own journal and does its own unmarshaling.


Even with IP multicasting, replication is still needed because IP messages can arrive in a different order on different nodes. The master node provides a deterministic sequence for the rest of the processing.


The unmarshaler turns the event data from the wire into a java object that can be used to invoke behavior on the Business Logic Processor. Therefore, unlike the other consumers, it needs to modify the data in the ring buffer so it can store this unmarshaled object. The rule here is that consumers are permitted to write to the ring buffer, but each writable field can only have one parallel consumer that's allowed to write to it. This preserves the principle of only having a single writer. [16]


Figure 4: The LMAX architecture with the disruptors expanded.


The disruptor is a general purpose component that can be used outside of the LMAX system. Usually financial companies are very secretive about their systems, keeping quiet even about items that aren't germane to their business. Not just has LMAX been open about its overall architecture, they have open-sourced the disruptor code - an act that makes me very happy. Not just will this allow other organizations to make use of the disruptor, it will also allow for more testing of its concurrency properties.


Queues and their lack of mechanical sympathy.


The LMAX architecture caught people's attention because it's a very different way of approaching a high performance system to what most people are thinking about. So far I've talked about how it works, but haven't delved too much into why it was developed this way. This tale is interesting in itself, because this architecture didn't just appear. It took a long time of trying more conventional alternatives, and realizing where they were flawed, before the team settled on this one.


Most business systems these days have a core architecture that relies on multiple active sessions coordinated through a transactional database. The LMAX team were familiar with this approach, and confident that it wouldn't work for LMAX. This assessment was founded in the experiences of Betfair - the parent company who set up LMAX. Betfair is a betting site that allows people to bet on sporting events. It handles very high volumes of traffic with a lot of contention - sports bets tend to burst around particular events. To make this work they have one of the hottest database installations around and have had to do many unnatural acts in order to make it work. Based on this experience they knew how difficult it was to maintain Betfair's performance and were sure that this kind of architecture would not work for the very low latency that a trading site would require. As a result they had to find a different approach.


Their initial approach was to follow what so many are saying these days - that to get high performance you need to use explicit concurrency. For this scenario, this means allowing orders to be processed by multiple threads in parallel. However, as is often the case with concurrency, the difficulty comes because these threads have to communicate with each other. Processing an order changes market conditions and these conditions need to be communicated.


The approach they explored early on was the Actor model and its cousin SEDA. The Actor model relies on independent, active objects with their own thread that communicate with each other via queues. Many people find this kind of concurrency model much easier to deal with than trying to do something based on locking primitives.


The team built a prototype exchange using the actor model and did performance tests on it. What they found was that the processors spent more time managing queues than doing the real logic of the application. Queue access was a bottleneck.


When pushing performance like this, it starts to become important to take account of the way modern hardware is constructed. The phrase Martin Thompson likes to use is "mechanical sympathy". The term comes from race car driving and it reflects the driver having an innate feel for the car, so they are able to feel how to get the best out of it. Many programmers, and I confess I fall into this camp, don't have much mechanical sympathy for how programming interacts with hardware. What's worse is that many programmers think they have mechanical sympathy, but it's built on notions of how hardware used to work that are now many years out of date.


One of the dominant factors with modern CPUs that affects latency, is how the CPU interacts with memory. These days going to main memory is a very slow operation in CPU-terms. CPUs have multiple levels of cache, each of which of is significantly faster. So to increase speed you want to get your code and data in those caches.


At one level, the actor model helps here. You can think of an actor as its own object that clusters code and data, which is a natural unit for caching. But actors need to communicate, which they do through queues - and the LMAX team observed that it's the queues that interfere with caching.


The explanation runs like this: in order to put some data on a queue, you need to write to that queue. Similarly, to take data off the queue, you need to write to the queue to perform the removal. This is write contention - more than one client may need to write to the same data structure. To deal with the write contention a queue often uses locks. But if a lock is used, that can cause a context switch to the kernel. When this happens the processor involved is likely to lose the data in its caches.


The conclusion they came to was that to get the best caching behavior, you need a design that has only one core writing to any memory location[17]. Multiple readers are fine, processors often use special high-speed links between their caches. But queues fail the one-writer principle.


This analysis led the LMAX team to a couple of conclusions. Firstly it led to the design of the disruptor, which determinedly follows the single-writer constraint. Secondly it led to idea of exploring the single-threaded business logic approach, asking the question of how fast a single thread can go if it's freed of concurrency management.


The essence of working on a single thread, is to ensure that you have one thread running on one core, the caches warm up, and as much memory access as possible goes to the caches rather than to main memory. This means that both the code and the working set of data needs to be as consistently accessed as possible. Also keeping small objects with code and data together allows them to be swapped between the caches as a unit, simplifying the cache management and again improving performance.


An essential part of the path to the LMAX architecture was the use of performance testing. The consideration and abandonment of an actor-based approach came from building and performance testing a prototype. Similarly much of the steps in improving the performance of the various components were enabled by performance tests. Mechanical sympathy is very valuable - it helps to form hypotheses about what improvements you can make, and guides you to forward steps rather than backward ones - but in the end it's the testing gives you the convincing evidence.


Performance testing in this style, however, is not a well-understood topic. Regularly the LMAX team stresses that coming up with meaningful performance tests is often harder than developing the production code. Again mechanical sympathy is important to developing the right tests. Testing a low level concurrency component is meaningless unless you take into account the caching behavior of the CPU.


One particular lesson is the importance of writing tests against null components to ensure the performance test is fast enough to really measure what real components are doing. Writing fast test code is no easier than writing fast production code and it's too easy to get false results because the test isn't as fast as the component it's trying to measure.


Should you use this architecture?


At first glance, this architecture appears to be for a very small niche. After all the driver that led to it was to be able to run lots of complex transactions with very low latency - most applications don't need to run at 6 million TPS.


But the thing that fascinates me about this application, is that they have ended up with a design which removes much of the programming complexity that plagues many software projects. The traditional model of concurrent sessions surrounding a transactional database isn't free of hassles. There's usually a non-trivial effort that goes into the relationship with the database. Object/relational mapping tools can help much of the pain of dealing with a database, but it doesn't deal with it all. Most performance tuning of enterprise applications involves futzing around with SQL.


These days, you can get more main memory into your servers than us old guys could get as disk space. More and more applications are quite capable of putting all their working set in main memory - thus eliminating a source of both complexity and sluggishness. Event Sourcing provides a way to solve the durability problem for an in-memory system, running everything in a single thread solves the concurrency issue. The LMAX experience suggests that as long as you need less than a few million TPS, you'll have enough performance headroom.


There is a considerable overlap here with the growing interest in CQRS. An event sourced, in-memory processor is a natural choice for the command-side of a CQRS system. (Although the LMAX team does not currently use CQRS.)


So what indicates you shouldn't go down this path? This is always a tricky questions for little-known techniques like this, since the profession needs more time to explore its boundaries. A starting point, however, is to think of the characteristics that encourage the architecture.


One characteristic is that this is a connected domain where processing one transaction always has the potential to change how following ones are processed. With transactions that are more independent of each other, there's less need to coordinate, so using separate processors running in parallel becomes more attractive.


LMAX concentrates on figuring the consequences of how events change the world. Many sites are more about taking an existing store of information and rendering various combinations of that information to as many eyeballs as they can find - eg think of any media site. Here the architectural challenge often centers on getting your caches right.


Another characteristic of LMAX is that this is a backend system, so it's reasonable to consider how applicable it would be for something acting in an interactive mode. Increasingly web application are helping us get used to server systems that react to requests, an aspect that does fit in well with this architecture. Where this architecture goes further than most such systems is its absolute use of asynchronous communications, resulting in the changes to the programming model that I outlined earlier.


These changes will take some getting used to for most teams. Most people tend to think of programming in synchronous terms and are not used to dealing with asynchrony. Yet it's long been true that asynchronous communication is an essential tool for responsiveness. It will be interesting to see if the wider use of asynchronous communication in the javascript world, with AJAX and node. js, will encourage more people to investigate this style. The LMAX team found that while it took a bit of time to adjust to asynchronous style, it soon became natural and often easier. In particular error handling was much easier to deal with under this approach.


The LMAX team certainly feels that the days of the coordinating transactional database are numbered. The fact that you can write software more easily using this kind of architecture and that it runs more quickly removes much of the justification for the traditional central database.


For my part, I find this a very exciting story. Much of my goal is to concentrate on software that models complex domains. An architecture like this provides good separation of concerns, allowing people to focus on Domain-Driven Design and keeping much of the platform complexity well separated. The close coupling between domain objects and databases has always been an irritation - approaches like this suggest a way out.


For articles on similar topics…


…take a look at the following tags:


1: The Free Lunch is Over.


This is the title of a famous essay by Herb Sutter. He describes the "free lunch" as the ever increasing clock speed of processors that regularly gave us more CPU performance every year. His point was that such clock cycle increases were no longer going to happen, instead performance increases would come in terms of multiple cores. But to take advantage of multiple cores, you need software that is capable of working concurrently - so without a shift in programming style people would no longer get the performance lunch for free.


2: I shall remain silent on what I think about the value of this innovation.


3: User Base.


All trading systems need low latency, since one trade can affect later trades and there's a lot of competition based on rapid reaction. Most trading platforms are for professionals - banks, brokers, etc - and typically have hundreds of users. A retail system has the potential for many more users, Betfair has millions of users and LMAX is designed for that scale. (The LMAX team isn't allowed to disclose its actual volumes.)


As it turns out, although a retail system has a lot of users, most of the activity in comes from market makers. During volatile periods an instrument can get hundreds of updates per second, with unusual micro-bursts of hundreds of transactions within a single microsecond.


4: Hardware.


The 6 million TPS benchmark was measured on a 3Ghz dual-socket quad-core Nehalem based Dell server with 32GB RAM.


5: The team does not use the name Business Logic Processor, in fact they have no name for that component, just referring to it as the business logic or core services. I've given it a name to make it easier to talk about in this article.


6: Currently LMAX runs two Business Logic Processors in its main data center and a third at a disaster recovery site. All three process input events.


7: What's in a transaction.


When people talk about transaction timing, one of the problems is what exactly is in a transaction. In some cases it's little more than inserting a new record in a database. LMAX's transactions are reasonably complex, more complex than a typical retail sale.


Placing an order in an exchange involves:


checking the target market is open to take orders checking the order is valid for that market choosing the right matching policy for the type of order sequencing the order so that each order is matched at the best possible price and matched with the right liquidity creating and publicizing the trades made as a consequence of the match updating prices based on the new trades.


8: At this scale of latency, you have to be aware of the garbage collector. For almost all systems these days, a modern GC compaction isn't going to have any noticeable effect on performance. However when you are trying to process millions of transactions per second with minimum jitter, a GC pause becomes a problem. The thing to remember is that short lived objects are ok, as they get collected quickly. So are objects that are permanent, since they will live for ever. The problematic objects are those that will get promoted to an older generation, but will eventually die. As this fragments the older generation region, it will trigger the compaction.


9: I rarely think about which collection implementation to use. This is perfectly reasonable when you're not in performance critical code. Different contexts suggest different behavior.


10: An interesting side-note. While the LMAX team shares much of the current interest in functional programming, they believe that the OO approach provides a better approach for this kind of problem. They've noticed that as they work to write faster code, they move away from a functional style towards OO style. Partly this because of the copying of data that functional styles require to maintain immutability. But it's also because objects provide a better model of a complex domain with a richer choice of data structures.


11: The name "disruptor" was inspired from a couple of sources. One is the the fact that the LMAX team sees this component as something that disrupts current thinking on concurrency. The other is a response to the fact that Java is introducing a phaser, so it's natural to include disruptors too.


12: It would be possible to journal the output events too. This would have the advantage of not needing to recalculate them should they need to be replayed for downstream services. In practice, however, this isn't worthwhile. The business logic is deterministic and very fast, so there's no gain from storing the results.


13: Although it does need to use CAS instructions in this case. See the disruptor technical paper for more information.


14: This does mean that if they process a billion transactions per second the counter will wrap in 292 years, causing some hell to break loose. They have decided that fixing this is not a high priority.


15: SSDs are better at random access, but a disk-like IO system slows them down.


16: Another complication when writing fields is you have to ensure that any fields being written to are separated into different cache lines.


17: Ensuring a single writer to a memory location.


A complication in following the single-writer principle is that processors don't grab memory one location at a time. Rather they sweep up multiple contiguous locations, called a cache line , into cache in one go. Accessing memory in cache line chunks is obviously more efficient, but also means that you have to ensure you don't have locations within that cache line that are written by different cores. So, for example, the Disruptor's sequence counter are padded to ensure they appear in separate cache lines.


Agradecimentos.


Financial institutions are usually secretive with their technical work, usually with little reason. This is a problem as it hampers the ability for the profession to learn from experience. So I'm especially thankful for LMAX's openness in discussing their experiences - both with this article and in their other material.


The main creators of the Disruptor are Martin Thompson, Mike Barker, and Dave Farley.


Martin Thompson and Dave Farley gave me a detailed walk-through of the LMAX architecture that served as the basis for this article. They also responded swiftly to email questions to improve my early drafts.


Concurrent programming is a tricky field that requires lots of attention to be competent at - and I have not put that effort in. As a result I'm entirely dependent upon others for understanding on concurrency and am thankful for their patient advice.


Leitura adicional.


If you'd prefer a video description of the LMAX architecture from LMAX team members, your best bet is the QCon presentation given in San Francisco 2018 by Martin Thompson and Michael Barker.


The source code for the Disruptor is available as open source. There is also a good technical paper (pdf) that goes into more depth as well as a collection of blogs and articles on it.


Various members of the LMAX team have their own blogs: Martin Thompson, Michael Barker, and Trisha Gee.


Arquitetura do sistema de negociação de baixa latência.


Bem-vindo ao Indian Overseas Bank.


Low latency trading system architecture fibonacci indicator in forex.


27 de janeiro de 2018. O que se segue é uma caminhada aleatória através de uma variedade de práticas recomendadas para ter em mente ao projetar sistemas de baixa latência. A maioria dessas sugestões são levadas ao extremo lógico, mas é claro que podem ser feitas compensações. (Obrigado a um usuário anônimo por fazer esta pergunta no Quora e me fazer colocar meu.


Arquitetura de negociação de baixa latência. Sam Adams. QC em Londres, março de 2017. Arquitetura do sistema. Criando aplicações de baixa latência. UDP Multicast: baixa latência, escalável, não confiável. - Serviços publicar / assinar tópicos. - tópico = grupo multicast exclusivo. - Informatica UMS (também conhecido como 29 West LBM) fornece * alguma confiabilidade *. 12 Jan 2018 . O artigo explica como os sistemas de negociação automatizados evoluíram, sistemas comerciais tradicionais, arquiteturas modernas de sistemas de baixa latência e protocolos de rede.


Muitas organizações e empresas usam as palavras "latência ultra baixa" para descrever latências de menos de 1 milissegundo, mas é uma definição em evolução, com a quantidade de tempo considerado "baixo" em constante encolhimento. Existem muitos fatores que influenciam o tempo que leva um sistema comercial para detectar uma oportunidade e sucesso. 4 de abril de 2017. Michael Barker explora como a arquitetura da plataforma de negociação FX da LMAX Exchange evoluiu em face da mudança comercial significativa e uma redução de dez vezes na latência e aumento da taxa de transferência.1 Jul 2018. . nível de arquitetura, que pode ajudar os aplicativos Java a alcançar um desempenho ultra-alto. Também propomos duas arquiteturas eficientes para sistemas de troca comercial que permitem latências ultra baixas e alto débito. Palavras-chave: sistemas de negociação, arquiteturas de software, alto desempenho, baixa latência, alta.

Comments

Popular Posts