Advertência: pode haver imprecisões abaixo. Eu tenho aprendido muitas dessas coisas à medida que avança, então leve com uma pitada de sal. Isso é bastante longo, mas você pode apenas ler os parâmetros com os quais estávamos jogando e pular para a Conclusão no final.
Há várias camadas nas quais você pode se preocupar com o desempenho de gravação do SQLite:
Vimos os destacados em negrito. Os parâmetros particulares foram
- Cache de gravação em disco. Os discos modernos têm cache de RAM, usado para otimizar as gravações de disco em relação ao disco rotativo. Com isso ativado, os dados podem ser gravados em blocos fora de ordem; portanto, se ocorrer uma falha, você poderá acabar com um arquivo parcialmente gravado. Verifique a configuração com hdparm -W / dev / ... e defina-a com hdparm -W1 / dev / ... (para ativá-la e -W0 para desativá-la).
- barreira = (0 | 1). Muitos comentários online dizendo "se você executar com barreira = 0, não terá o cache de gravação em disco ativado". Você pode encontrar uma discussão sobre barreiras em http://lwn.net/Articles/283161/
- data = (diário | pedido | write-back). Consulte http://www.linuxtopia.org/HowToGuides/ext3JournalingFilesystem.html para obter uma descrição dessas opções.
- confirmar = N. Diz ao ext3 para sincronizar todos os dados e metadados a cada N segundos (padrão 5).
- Pragma SQLite síncrono = LIGADO | FORA. Quando ativado, o SQLite garantirá que uma transação seja "gravada no disco" antes de continuar. Desativar isso basicamente torna as outras configurações em grande parte irrelevantes.
- Pragma SQLite cache_size. Controla a quantidade de memória que o SQLite usará para o cache da memória. Tentei dois tamanhos: um em que o banco de dados inteiro caberia no cache e outro em que o cache tinha metade do tamanho máximo do banco de dados.
Leia mais sobre as opções ext3 na documentação ext3 .
Eu executei testes de desempenho em várias combinações desses parâmetros. O ID é um número de cenário, mencionado abaixo.
Comecei executando a configuração padrão em minha máquina como cenário 1. O cenário 2 é o que eu assumo ser o "mais seguro" e tentei várias combinações, quando apropriado / solicitado. Provavelmente é mais fácil de entender com o mapa que acabei usando:
Escrevi um script de teste que executava muitas transações, com inserções, atualizações e exclusões, todas em tabelas com apenas INTEGER, somente TEXTO (com coluna id) ou mistas. Eu executei isso várias vezes em cada uma das configurações acima:
Os dois cenários inferiores são os 6 e 17, que têm "pragma synchronous = off", tão surpreendente que foram os mais rápidos. O próximo cluster de três é # 7, # 11 e # 19. Esses três são destacados em azul no "mapa de configuração" acima. Basicamente, a configuração é cache de gravação em disco, barreira = 0 e conjunto de dados para algo diferente de 'diário'. Alterar a confirmação entre 5 segundos (nº 7) e 60 segundos (nº 11) parece fazer pouca diferença. Nesses testes, não parecia haver muita diferença entre dados = pedidos e dados = write-back, o que me surpreendeu.
O teste de atualização mista é o pico do meio. Há um cluster de cenários que são claramente mais lentos neste teste. Estes são todos aqueles com data = diário . Caso contrário, não há muito entre os outros cenários.
Eu fiz outro teste de temporização, que fez uma mistura mais heterogênea de inserções, atualizações e exclusões nas diferentes combinações de tipos. Isso levou muito mais tempo, e é por isso que não o incluí no gráfico acima:
Aqui você pode ver que a configuração de write-back (nº 19) é um pouco mais lenta que as solicitadas (nº 7 e nº 11). Eu esperava que o write-back fosse um pouco mais rápido, mas talvez dependa dos seus padrões de gravação ou talvez ainda não tenha lido o suficiente no ext3 :-)
Os vários cenários foram um pouco representativos das operações realizadas por nosso aplicativo. Após escolher uma lista de cenários, executamos testes de cronometragem com alguns de nossos conjuntos de testes automatizados. Eles estavam de acordo com os resultados acima.
Conclusão
- O parâmetro commit parecia fazer pouca diferença, então estamos deixando isso em 5s.
- Vamos continuar com o cache de gravação de disco, barreira = 0 e dados = ordenados . Li algumas coisas on-line que consideravam uma configuração ruim e outras que pareciam pensar que esse deveria ser o padrão em muitas situações. Acho que o mais importante é que você tome uma decisão informada, sabendo quais trade-offs você está tomando.
- Não vamos usar o pragma síncrono no SQLite.
- Definir o pragma SQLite cache_size para que o banco de dados caiba na memória melhorou o desempenho em algumas operações, como esperávamos.
- A configuração acima significa que estamos correndo um pouco mais de risco. Usaremos a API de backup do SQLite para minimizar o risco de falha do disco em uma gravação parcial: tirando um instantâneo a cada N minutos e mantendo o último M por perto. Testei essa API durante a execução de testes de desempenho, e isso nos deu confiança para seguir esse caminho.
- Se ainda quiséssemos mais, poderíamos examinar o kernel, mas melhoramos as coisas o suficiente sem ir para lá.
Obrigado a @Huygens por várias dicas e sugestões.