
O Reindexer é um banco de dados orientado a documentos incorporável, na memória e com uma interface de consultor de consultas de alto nível.
O objetivo do Reindexer é fornecer pesquisas rápidas com consultas complexas. Nós, na Recream, não ficamos felizes com o Elasticsearch e criamos o Reindexer como uma alternativa mais performante.
O núcleo está escrito no C ++ e a API no nível do aplicativo está em Go.
Este documento descreve o conector Go e sua API. Para obter informações sobre o Reindexer Server e a API HTTP, consulte a documentação do Reindexer
Existem duas versões de reindexer disponíveis: v3.xx e v4.xx
3.xx é atualmente nossa filial convencional e 4.xx (release/ramificação 4) é a versão beta com suporte experimental de cluster de jangada e sharding. Os armazenamentos são compatíveis entre essas versões, no entanto, as configurações de replicação são totalmente diferentes. As versões 3 e 4 estão obtendo as mesmas bugs e recursos (exceto os relacionados à replicação).
Principais recursos:
O desempenho tem sido nossa principal prioridade desde o início, e achamos que conseguimos conseguir muito bem. Os benchmarks mostram que o desempenho do Reindexer está em pé de igualdade com um banco de dados de valor-chave típico. Em um único núcleo de CPU, obtemos:
SELECT * FROM items WHERE id='?'SELECT * FROM items WHERE year > 2010 AND name = 'string' AND id IN (....)SELECT * FROM items WHERE year > 2010 AND name = 'string' JOIN subitems ON ...Consulte Resultados de benchmarking e mais detalhes no repositório de benchmarking
O Reindexer pretende consumir o mínimo de memória possível; A maioria das consultas é processada sem qualquer alocação de memória.
Para conseguir isso, várias otimizações são empregadas, tanto no nível C ++ quanto para GO:
Documentos e índices são armazenados em estruturas densas C ++ binárias, para que não imporem nenhuma carga no coletor de lixo do Go.
As duplicatas de string são mescladas.
A sobrecarga da memória é de cerca de 32 bytes por documento + ≈4-16 bytes por cada índice de pesquisa.
Existe um cache de objetos no nível GO para documentos desserializados produzidos após a execução da consulta. As consultas futuras usam documentos pré-dessertados, que reduzem os custos repetidos de deseralização e alocação
A interface de consulta usa sync.Pool para reutilizar estruturas e buffers internos. A combinação dessas tecnologias permite que o Reindexer lide com a maioria das consultas sem alocações.
O Reindexer possui mecanismo interno de pesquisa de texto completo. Pesquisa de texto completo A documentação de uso e exemplos estão aqui
O reindexer pode armazenar documentos e carregar documentos do disco via LevelDB. Os documentos são gravados no back -end de armazenamento de forma assíncrona por grandes lotes automaticamente em segundo plano.
Quando um espaço para nome é criado, todos os seus documentos são armazenados na RAM; portanto, as consultas nesses documentos são executadas inteiramente no modo de memória.
Aqui está um exemplo completo de uso básico de reindexer:
package main
// Import package
import (
"fmt"
"math/rand"
"github.com/restream/reindexer/v3"
// choose how the Reindexer binds to the app (in this case "builtin," which means link Reindexer as a static library)
_ "github.com/restream/reindexer/v3/bindings/builtin"
// OR use Reindexer as standalone server and connect to it via TCP or unix domain socket (if available).
// _ "github.com/restream/reindexer/v3/bindings/cproto"
// OR link Reindexer as static library with bundled server.
// _ "github.com/restream/reindexer/v3/bindings/builtinserver"
// "github.com/restream/reindexer/v3/bindings/builtinserver/config"
)
// Define struct with reindex tags. Fields must be exported - private fields can not be written into reindexer
type Item struct {
ID int64 `reindex:"id,,pk"` // 'id' is primary key
Name string `reindex:"name"` // add index by 'name' field
Articles [] int `reindex:"articles"` // add index by articles 'articles' array
Year int `reindex:"year,tree"` // add sortable index by 'year' field
}
func main () {
// Init a database instance and choose the binding (builtin)
db := reindexer . NewReindex ( "builtin:///tmp/reindex/testdb" )
// OR - Init a database instance and choose the binding (connect to server via TCP sockets)
// Database should be created explicitly via reindexer_tool or via WithCreateDBIfMissing option:
// If server security mode is enabled, then username and password are mandatory
// db := reindexer.NewReindex("cproto://user:[email protected]:6534/testdb", reindexer.WithCreateDBIfMissing())
// OR - Init a database instance and choose the binding (connect to server via unix domain sockets)
// Unix domain sockets are available on the unix systems only (socket file has to be explicitly set on the server's side with '--urpcaddr' option)
// Database should be created explicitly via reindexer_tool or via WithCreateDBIfMissing option:
// If server security mode is enabled, then username and password are mandatory
// db := reindexer.NewReindex("ucproto://user:pass@/tmp/reindexer.socket:/testdb", reindexer.WithCreateDBIfMissing())
// OR - Init a database instance and choose the binding (builtin, with bundled server)
// serverConfig := config.DefaultServerConfig ()
// If server security mode is enabled, then username and password are mandatory
// db := reindexer.NewReindex("builtinserver://user:pass@testdb",reindexer.WithServerConfig(100*time.Second, serverConfig))
// Create new namespace with name 'items', which will store structs of type 'Item'
db . OpenNamespace ( "items" , reindexer . DefaultNamespaceOptions (), Item {})
// Generate dataset
for i := 0 ; i < 100000 ; i ++ {
err := db . Upsert ( "items" , & Item {
ID : int64 ( i ),
Name : "Vasya" ,
Articles : [] int { rand . Int () % 100 , rand . Int () % 100 },
Year : 2000 + rand . Int () % 50 ,
})
if err != nil {
panic ( err )
}
}
// Query a single document
elem , found := db . Query ( "items" ).
Where ( "id" , reindexer . EQ , 40 ).
Get ()
if found {
item := elem .( * Item )
fmt . Println ( "Found document:" , * item )
}
// Query multiple documents
query := db . Query ( "items" ).
Sort ( "year" , false ). // Sort results by 'year' field in ascending order
WhereString ( "name" , reindexer . EQ , "Vasya" ). // 'name' must be 'Vasya'
WhereInt ( "year" , reindexer . GT , 2020 ). // 'year' must be greater than 2020
WhereInt ( "articles" , reindexer . SET , 6 , 1 , 8 ). // 'articles' must contain one of [6,1,8]
Limit ( 10 ). // Return maximum 10 documents
Offset ( 0 ). // from 0 position
ReqTotal () // Calculate the total count of matching documents
// Execute the query and return an iterator
iterator := query . Exec ()
// Iterator must be closed
defer iterator . Close ()
fmt . Println ( "Found" , iterator . TotalCount (), "total documents, first" , iterator . Count (), "documents:" )
// Iterate over results
for iterator . Next () {
// Get the next document and cast it to a pointer
elem := iterator . Object ().( * Item )
fmt . Println ( * elem )
}
// Check the error
if err := iterator . Error (); err != nil {
panic ( err )
}
}Existem também algumas amostras básicas para C ++ e vá aqui
Como alternativa ao consultor Reindexer, fornece interface de consulta compatível com SQL. Aqui está a amostra do uso da interface SQL:
...
iterator := db . ExecSQL ( "SELECT * FROM items WHERE name='Vasya' AND year > 2020 AND articles IN (6,1,8) ORDER BY year LIMIT 10" )
...Observe que a interface do construtor de consultas é preferida: ela tem mais recursos e é mais rápida que a interface SQL
Os literais de cordas devem ser fechados em citações únicas.
Os índices compostos devem ser anexados em cotações duplas.
SELECT * FROM items WHERE " field1+field2 " = ' Vasya 'Se o nome do campo não começar com Alpha, '_' ou '#', ele deve ser fechado em citações duplas, exemplos:
UPDATE items DROP " 123 " SELECT * FROM ns WHERE " 123 " = ' some_value ' SELECT * FROM ns WHERE " 123abc " = 123 DELETE FROM ns WHERE " 123abc123 " = 111As junções simples podem ser feitas via sintaxe padrão SQL:
SELECT * FROM ns INNER JOIN ns2 ON ns2 . id = ns . fk_id WHERE a > 0Junta-se com a condição no espaço para nome esquerdo deve usar sintaxe do tipo subconsiva:
SELECT * FROM ns WHERE a > 0 AND INNER JOIN ( SELECT * FROM ns2 WHERE b > 10 AND c = 1 ) ON ns2 . id = ns . fk_idA subconsulta também pode fazer parte da Condição:
SELECT * FROM ns WHERE ( SELECT * FROM ns2 WHERE id < 10 LIMIT 0 ) IS NOT NULL SELECT * FROM ns WHERE id = ( SELECT id FROM ns2 WHERE id < 10 ) SELECT * FROM ns WHERE ( SELECT COUNT ( * ) FROM ns2 WHERE id < 10 ) > 18 O reindexer pode ser executado em 3 modos diferentes:
embedded (builtin) é incorporado ao aplicativo como biblioteca estática e não requer processo de servidor separado.embedded with server (builtinserver) O reindexer está incorporado ao aplicativo como biblioteca estática e servidor inicial. Nesse modo, outros clientes podem se conectar ao aplicativo via CProto, UCProto ou HTTP.standalone é executado como servidor independente, o aplicativo se conecta ao reindexer por meio de soquetes de rede ou domínio Unix.Neste modo, a ligação do Go-Indexer não depende da biblioteca estática do Reindexer.
A maneira mais simples de obter o servidor Reindexer é puxar e executar a imagem do docker do DockerHub.
docker run -p9088:9088 -p6534:6534 -it reindexer/reindexerDockerfile
O núcleo do Reindexer é escrito no C ++ 17 e usa o LevelDB como back -end de armazenamento; portanto, o CMake, C ++ 17 Chain e o LevelDB deve ser instalado antes de instalar o reindexer.
Para construir o reindexer, é necessário G ++ 8+, Clang 7+ ou Mingw64.
Nesses modos, a ligação do Goindexer depende das bibliotecas estáticas do Reindexer (núcleo, servidor e recurso).
Desta forma, é recomendado e se encaixará no máximo cenários.
Vá módulos com go.mod não permitem criar bibliotecas C ++ nos diretórios dos módulos. A ligação do GoS usará o PKG-Config para detectar os diretórios das bibliotecas.
As bibliotecas do Reindexer devem ser instaladas a partir de fontes ou a partir do pacote pré -edificado via gerenciador de pacotes.
Em seguida, pegue o módulo:
go get -a github.com/restream/reindexer/v3 Se você precisar de fontes de reindexer modificadas, poderá usar replace assim.
# Clone reindexer via git. It's also possible to use 'go get -a github.com/restream/reindexer/v3', but it's behavior may vary depending on Go's version
git clone https://github.com/restream/reindexer.git $GOPATH /src/reindexer
bash $GOPATH /src/reindexer/dependencies.sh
# Generate builtin binding
cd $GOPATH /src/reindexer
go generate ./bindings/builtin
# Optional (build builtin server binding)
go generate ./bindings/builtinserver # Go to your app's directory
cd /your/app/path
go get -a github.com/restream/reindexer/v3
go mod edit -replace github.com/restream/reindexer/v3= $GOPATH /src/reindexerNesse caso, a ligação do GoCleará a lista explícita de bibliotecas e caminhos e não usará o PKG-Config.
Se você não estiver usando go.mod, é possível obter e construir reindexer de fontes desta maneira:
export GO111MODULE=off # Disable go1.11 modules
# Go to your app's directory
cd /your/app/path
# Clone reindexer via git. It's also possible to use 'go get -a github.com/restream/reindexer', but it's behavior may vary depending on Go's version
git clone --branch master https://github.com/restream/reindexer.git vendor/github.com/restream/reindexer/v3
# Generate builtin binding
go generate -x ./vendor/github.com/restream/reindexer/v3/bindings/builtin
# Optional (build builtin server binding)
go generate -x ./vendor/github.com/restream/reindexer/v3/bindings/builtinserverO GO não suporta a fornecedora adequada para o código do CGO (Golang/Go#26366); no entanto, é possível usar o Vend para copiar as fontes do Reindexer no diretório do fornecedor.
Com vend , você poderá chamar go generate -mod=vendor para builtin e builtinserver , colocado em seu diretório de fornecedores.
Também é possível copiar simplesmente copiar as fontes do Reindexer no projeto da juventude, usando git clone .
Nesses casos, todas as dependências do Reindexer Go.mod devem ser instaladas manualmente com versões adequadas.
Internamente, as estruturas são divididas em duas partes:
reindex As consultas são possíveis apenas nos campos indexados, marcados com a tag reindex . A tag reindex contém o nome do índice, o tipo e as opções adicionais:
reindex:"<name>[[,<type>],<opts>]"
name - Nome do índice.type - Tipo de índice:hash - Selecione Fast By Eq e Set Combk. Usado por padrão. Permite classificação lenta e ineficiente por campo.tree - Selecione Fast By Range, GT e LT correspondem. Um pouco mais lento para o EQ e o conjunto de correspondências do que o índice hash . Permite resultados de classificação rápida por campo.text - índice de pesquisa de texto completo. Detalhes de uso da pesquisa completa de texto são descritos aqui- - Índice de coluna. Não é possível executar o Fast Select porque é implementado com a técnica de varredura completa. Tem a menor memória acima.ttl - TTL que funciona apenas com campos INT64. Esses índices são bastante convenientes para a representação dos campos de data (armazenados como registros de data e hora do UNIX) que expiram após a quantidade especificada de segundos.rtree - Disponível apenas Dwithin Match. Aceitável apenas para [2]float64 (ou reindexer.Point ) tipo de campo. Para detalhes, consulte a subseção de geometria.opts - Opções adicionais de índice:pk - O campo faz parte de uma chave primária. Struct deve ter pelo menos 1 campo marcado com pkcomposite - Crie índice composto. O tipo de campo deve ser uma estrutura vazia: struct{} .joined - Field é um destinatário para ingressar. O tipo de campo deve ser []*SubitemType .dense - reduza o tamanho do índice. Para hash e tree , ele economizará 8 bytes por valor de chave exclusivo. Para - economizará 4-8 bytes por cada elemento. Útil para índices com alta seletividade, mas para índices tree e hash com baixa seletividade, pode diminuir seriamente o desempenho da atualização. Também dense diminuirá as consultas fullsCan amplas em - índices, devido à falta de otimização do cache da CPU.sparse - Linha (documento) contém um valor do índice esparso apenas se ele estiver definido de propósito - não há registros vazios (ou padrão) desse tipo de índices na linha (documento). Ele permite salvar RAM, mas custará o desempenho - funciona um pouco mais lento que os índices regulares.collate_numeric - Crie um índice de string que forneça a ordem dos valores na sequência numérica. O tipo de campo deve ser uma string.collate_ascii - Crie o índice de string insensível a casos funciona com ascii. O tipo de campo deve ser uma string.collate_utf8 - Crie o índice de string insensível com o caso funciona com o UTF8. O tipo de campo deve ser uma string.collate_custom=<ORDER> - Crie índice de string de pedido personalizado. O tipo de campo deve ser uma string. <ORDER> é uma sequência de letras, que define a ordem de classificação.linear , quadratic , greene ou rstar - Especifique o algoritmo para a construção do índice rtree (por padrão rstar ). Para detalhes, consulte a subseção de geometria.uuid - armazene esse valor como UUID. Isso é muito mais eficaz do ponto de vista da RAM/RAME da consumação de rede para os UUIDs do que as strings. Somente os tipos de hash e - índice são suportados para UUIDs. Pode ser usado com qualquer variante UUID, exceto variante 0 Campos com índices regulares não são anuláveis. A condição is NULL é suportada apenas por índices sparse e array .
Por padrão, o reindexer digitaliza todas as estruturas aninhadas e adiciona seus campos ao espaço para nome (além de índices especificados). Durante os índices, digitalize privados (campos não transportados), os campos marcados com reindex:"-" e os campos marcados com json:"-" serão ignorados.
type Actor struct {
Name string `reindex:"actor_name"`
Age int `reindex:"age"`
}
type BaseItem struct {
ID int64 `reindex:"id,hash,pk"`
UUIDValue string `reindex:"uuid_value,hash,uuid"`
}
type ComplexItem struct {
BaseItem // Index fields of BaseItem will be added to reindex
Actor [] Actor // Index fields ("name" and "age") of Actor will be added to reindex as array-indexes
Name string `reindex:"name"` // Hash-index for "name"
Year int `reindex:"year,tree"` // Tree-index for "year"
Value int `reindex:"value,-"` // Store(column)-index for "value"
Metainfo int `json:"-"` // Field "MetaInfo" will not be stored in reindexer
Parent * Item `reindex:"-"` // Index fields of "Parent" will NOT be added to reindex and all of the "Parent" exported content will be stored as non-indexed data
ParentHidden * Item `json:"-"` // Indexes and fields of "ParentHidden" will NOT be added to reindexer
privateParent * Item // Indexes and fields of "ParentHidden" will NOT be added to reindexer (same as with `json:"-"`)
AnotherActor Actor `reindex:"actor"` // Index fields of "AnotherActor" will be added to reindexer with prefix "actor." (in this example two indexes will be created: "actor.actor_name" and "actor.age")
}O reindexer pode classificar documentos por campos (incluindo campos aninhados e campos dos namespaces unidos) ou por expressões em ordem ascendente ou decrescente.
Para classificar por campos não índices, todos os valores devem ser conversíveis entre si, ou seja, ou seja, os mesmos tipos ou sejam um dos tipos numéricos ( bool , int , int64 ou float ).
Expressões de classificação podem conter:
bool , int , int64 , Tipos float ou string . Todos os valores devem ser conversíveis para os números que ignoram os espaços de liderança e acabamento;rank() , abs() e ST_Distance() ;+ , - (unário e binário), * e / . Se o nome do campo seguido por + eles devem ser separados por espaço para distinguir o nome do índice composto. Os campos dos namespaces unidos devem ser escritos assim: joined_namespace.field .
Abs() significa valor absoluto de um argumento.
Rank() significa classificação completa da partida e é aplicável apenas na consulta FullText.
ST_Distance() significa distância entre pontos de geometria (consulte a subseção de geometria). Os pontos podem ser colunas em namespaces atuais ou unidos ou ponto fixo no formato ST_GeomFromText('point(1 -3)')
Na SQL, a expressão de classificação de consulta deve ser citada.
type Person struct {
Name string `reindex:"name"`
Age int `reindex:"age"`
}
type City struct {
Id int `reindex:"id"`
NumberOfPopulation int `reindex:"population"`
Center reindexer. Point `reindex:"center,rtree,linear"`
}
type Actor struct {
ID int `reindex:"id"`
PersonData Person `reindex:"person"`
Price int `reindex:"price"`
Description string `reindex:"description,text"`
BirthPlace int `reindex:"birth_place_id"`
Location reindexer. Point `reindex:"location,rtree,greene"`
}
... .
query := db . Query ( "actors" ). Sort ( "id" , true ) // Sort by field
... .
query = db . Query ( "actors" ). Sort ( "person.age" , true ) // Sort by nested field
... .
// Sort by joined field
// Works for inner join only, when each item from left namespace has exactly one joined item from right namespace
query = db . Query ( "actors" ).
InnerJoin ( db . Query ( "cities" )). On ( "birth_place_id" , reindexer . EQ , "id" ).
Sort ( "cities.population" , true )
... .
// Sort by expression:
query = db . Query ( "actors" ). Sort ( "person.age / -10 + price / 1000 * (id - 5)" , true )
... .
query = db . Query ( "actors" ). Where ( "description" , reindexer . EQ , "ququ" ).
Sort ( "rank() + id / 100" , true ) // Sort with fulltext rank
... .
// Sort by geometry distance
query = db . Query ( "actors" ).
Join ( db . Query ( "cities" )). On ( "birth_place_id" , reindexer . EQ , "id" ).
SortStPointDistance ( cities . center , reindexer. Point { 1.0 , - 3.0 }, true ).
SortStFieldDistance ( "location" , "cities.center" , true )
... .
// In SQL query:
iterator := db . ExecSQL ( "SELECT * FROM actors ORDER BY person.name ASC" )
... .
iterator := db . ExecSQL ( "SELECT * FROM actors WHERE description = 'ququ' ORDER BY 'rank() + id / 100' DESC" )
... .
iterator := db . ExecSQL ( "SELECT * FROM actors ORDER BY 'ST_Distance(location, ST_GeomFromText( ' point(1 -3) ' ))' ASC" )
... .
iterator := db . ExecSQL ( "SELECT * FROM actors ORDER BY 'ST_Distance(location, cities.center)' ASC" )Também é possível definir um pedido de classificação personalizado como este
type SortModeCustomItem struct {
ID int `reindex:"id,,pk"`
InsItem string `reindex:"item_custom,hash,collate_custom=a-zA-Z0-9"`
}ou assim
type SortModeCustomItem struct {
ID int `reindex:"id,,pk"`
InsItem string `reindex:"item_custom,hash,collate_custom=АаБбВвГгДдЕеЖжЗзИиКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭ-ЯAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0-9ЁёЙйэ-я"`
}O primeiro caractere desta lista tem a maior prioridade, a prioridade do último personagem é a menor. Isso significa que o algoritmo de classificação colocará itens que começam com o primeiro personagem diante de outros. Se alguns caracteres forem ignorados, suas prioridades teriam seus valores habituais (de acordo com os caracteres da lista).
Para um padrão de texto de pesquisa simples em campos de string, a condição LIKE pode ser usada. Ele procura strings que correspondam ao padrão. No padrão _ significa qualquer char e % significa qualquer sequência de chars.
Vá Exemplo:
query := db . Query ( "items" ).
Where ( "field" , reindexer . LIKE , "pattern" )Exemplo SQL:
SELECT * FROM items WHERE fields LIKE ' pattern ''me_t' corresponde a 'meet', 'carne', 'derretimento' e assim por diante '%tion' corresponde a 'ção', 'condição', 'criação' e assim por diante
CUIDADO : Condição como usa o método de varredura. Pode ser usado para fins de depuração ou dentro de consultas com outras boas condições seletivas.
Geralmente, para pesquisa de texto completo com velocidade razoável que recomendamos usar o FullText Index.
As consultas de atualização são usadas para modificar os itens existentes de um espaço para nome. Existem vários tipos de consultas de atualização: atualizando os campos existentes, adicionando novos campos e lançando campos não indexados existentes.
Atualize SQL-Syntax
UPDATE nsName
SET field1 = value1, field2 = value2, ..
WHERE condition;Também é possível usar expressões aritméticas com +, -, /, * e colchetes
UPDATE NS SET field1 = field2 + field3 - (field4 + 5 ) / 2 incluindo funções como now() , sec() e serial() . Para usar expressões do método do código de Golang SetExpression() precisa ser chamado em vez de Set() .
Para fazer um campo de matriz vazio
UPDATE NS SET arrayfield = [] WHERE id = 100e defina -o como nulo
UPDATE NS SET field = NULL WHERE id > 100No caso de campos não indexados, definir seu valor como um valor de um tipo diferente o substituirá completamente; No caso de campos indexados, só é possível convertê -lo do tipo adjacente (tipos integrais e BOOL), seqüências numéricas (como "123456") em tipos integrais e voltas. Definir o campo indexado como nulo o redefine para um valor padrão.
É possível adicionar novos campos aos itens existentes
UPDATE NS SET newField = ' Brand new! ' WHERE id > 100e até adicione um novo campo por um caminho aninhado complexo como este
UPDATE NS SET nested . nested2 . nested3 . nested4 .newField = ' new nested field! ' WHERE id > 100Criará os seguintes objetos aninhados: aninhados, aninhados2, aninhados3, aninhados4 e Newfield como membro do objeto Nested4.
Exemplo de uso de consultas de atualização no código de Golang:
db . Query ( "items" ). Where ( "id" , reindexer . EQ , 40 ). Set ( "field1" , values ). Update ()O reindexer permite atualizar e adicionar campos de objeto. O objeto pode ser definido por uma estrutura, um mapa ou uma matriz de bytes (que é uma versão JSON da representação de objetos).
type ClientData struct {
Name string `reindex:"name" json:"name"`
Age int `reindex:"age" json:"age"`
Address int `reindex:"year" json:"year"`
Occupation string `reindex:"occupation" json:"occupation"`
TaxYear int `reindex:tax_year json:"tax_year"`
TaxConsultant string `reindex:tax_consultant json:"tax_consultant"`
}
type Client struct {
ID int `reindex:"id" json:"id"`
Data ClientData `reindex:"client_data" json:"client_data"`
...
}
clientData := updateClientData ( clientId )
db . Query ( "clients" ). Where ( "id" , reindexer . EQ , 100 ). SetObject ( "client_data" , clientData ). Update () Nesse caso, Map em Golang só pode funcionar com a string como uma chave. map[string]interface{} é uma escolha perfeita.
Atualização do campo Objeto por instrução SQL:
UPDATE clients SET client_data = { " Name " : " John Doe " , " Age " : 40 , " Address " : " Fifth Avenue, Manhattan " , " Occupation " : " Bank Manager " , " TaxYear " : 1999 , " TaxConsultant " : " Jane Smith " } WHERE id = 100 ;Atualize o SQL-Syntax de consultas que lançam campos não indexados existentes:
UPDATE nsName
DROP field1, field2, ..
WHERE condition; db . Query ( "items" ). Where ( "id" , reindexer . EQ , 40 ). Drop ( "field1" ). Update ()O mecanismo de atualização do reindexer permite modificar os campos de matriz: modificar um determinado item de uma matriz existente ou mesmo substituir um campo inteiro.
Para atualizar uma sintaxe do operador de assinatura de item, é usado:
UPDATE NS SET array[ * ].prices[ 0 ] = 9999 WHERE id = 5 onde * significa todos os itens.
Para atualizar a matriz inteira, o seguinte é usado:
UPDATE NS SET prices = [ 999 , 1999 , 2999 ] WHERE id = 9Qualquer campo não indexado pode ser facilmente convertido em matriz usando esta sintaxe.
O Reindexer também permite atualizar itens de matrizes de objetos:
UPDATE NS SET extra . objects [ 0 ] = { " Id " : 0 , " Description " : " Updated! " } WHERE id = 9também assim
db . Query ( "clients" ). Where ( "id" , reindexer . EQ , 100 ). SetObject ( "extra.objects[0]" , updatedValue ). Update ()O Reindexer suporta matrizes heterogêneas:
UPDATE NS SET info = [ " hi " , " bro " , 111 , 2 . 71 ] WHERE id = 9 q := DB . Query ( ns ). Where ( "id" , reindexer . EQ , 1 ). Set ( "array" , [] interface {}{ "whatsup" , 777 , "bro" })
res , err := q . Update (). FetchAll ()Os campos de matriz de índice suportam valores que podem ser convertidos apenas em um tipo de índice. Quando salvos, esses valores podem alterar a precisão devido à conversão.
UPDATE NS SET prices_idx = [ 11 , ' 2 ' , 3 ]Para remover o item por índice, você deve fazer o seguinte:
UPDATE NS DROP array[ 5 ]Para adicionar itens a uma matriz existente, a seguinte sintaxe é suportada:
UPDATE NS SET integer_array = integer_array || [ 5 , 6 , 7 , 8 ]e
UPDATE NS SET integer_array = [ 1 , 2 , 3 , 4 , 5 ] || integer_array O primeiro adiciona elementos ao final do integer_array , o segundo adiciona 5 itens à frente. Para fazer com que este código funcione no Golang SetExpression() deve ser usado em vez de Set() .
Para remover itens por valor em uma matriz existente, a seguinte sintaxe é suportada:
UPDATE NS SET integer_array = array_remove(integer_array, [ 5 , 6 , 7 , 8 ])e
UPDATE NS SET integer_array = array_remove_once(integer_array, [ 5 , 6 , 7 , 8 , 6 ]) O primeiro remove todas as ocorrências dos valores listados em integer_array , o segundo exclui apenas a primeira ocorrência encontrada. Para fazer com que este código funcione no Golang SetExpression() deve ser usado em vez de Set() . Se você precisar remover um valor, poderá usar colchetes [5] ou valor simples 5 .
UPDATE NS SET integer_array = array_remove(integer_array, [ 5 ]) update ns set integer_array = array_remove(integer_array, 5 )Remover comando pode ser combinado com concatenado de matriz:
UPDATE NS SET integer_array = array_remove_once(integer_array, [ 5 , 6 , 7 , 8 ]) || [ 1 , 2 , 3 ]também assim
db . Query ( "main_ns" ). SetExpression ( "integer_array" , "array_remove(integer_array, [5,6,7,8]) || [1,2,3]" ). Update ()É possível remover os valores do segundo campo dos valores do primeiro campo. E também adicione novos valores etc. Nota: Espera-se que o primeiro parâmetro nos comandos seja uma matriz/matriz de campo, o segundo parâmetro pode ser um matriz/escalar/campo de campo/escalar de campo. Para valores, compatibilidade/conversibilidade necessária
UPDATE NS SET integer_array = [ 3 ] || array_remove(integer_array, integer_array2) || integer_array3 || array_remove_once(integer_array, [ 8 , 1 ]) || [ 2 , 4 ] UPDATE NS SET integer_array = array_remove(integer_array, integer_array2) || array_remove(integer_array, integer_array3) || array_remove_once(integer_array, [ 33 , 777 ]) db . Query ( "main_ns" ). SetExpression ( "integer_array" , "[3] || array_remove(integer_array, integer_array2) || integer_array3 || array_remove(integer_array, [8,1]) || [2,4]" ). Update () O reindexer suporta transações. A transação é executa a atualização de namespace atômica. Existem transações síncronas e assíncronas disponíveis. Para iniciar o método de transação db.BeginTx() é usado. Este método cria objeto de transação, que fornece interface usual de atualização/upsert/insert/exclusão para aplicação. Para os clientes RPC, há limitação de contagem de transações - cada conexão não pode ter mais de 1024 transações abertas ao mesmo tempo.
// Create new transaction object
tx , err := db . BeginTx ( "items" );
if err != nil {
panic ( err )
}
// Fill transaction object
tx . Upsert ( & Item { ID : 100 })
tx . Upsert ( & Item { ID : 101 })
tx . Query (). WhereInt ( "id" , reindexer . EQ , 102 ). Set ( "Name" , "Petya" ). Update ()
// Apply transaction
if err := tx . Commit (); err != nil {
panic ( err )
}Para velocidade, a inserção dos registros em massa do modo assíncrono pode ser usada.
// Create new transaction object
tx , err := db . BeginTx ( "items" );
if err != nil {
panic ( err )
}
// Prepare transaction object async.
tx . UpsertAsync ( & Item { ID : 100 }, func ( err error ) {})
tx . UpsertAsync ( & Item { ID : 100 }, func ( err error ) {})
// Wait for async operations done, and apply transaction.
if err := tx . Commit (); err != nil {
panic ( err )
} O segundo argumento do UpsertAsync é a função de conclusão, que será chamada após o recebimento da resposta do servidor. Além disso, se ocorrer algum erro durante o processo de preparação, tx.Commit deve retornar um erro. Portanto, basta verificar o erro retornado pelo tx.Commit - com certeza, que todos os dados foram comprometidos ou não com sucesso.
Dependendo da quantidade de mudanças na transação, existem 2 estratégias de compromisso possíveis:
A quantidade de dados para selecionar uma estratégia de confirmação pode ser selecionada na configuração do espaço para nome. Verifique os campos StartCopyPolicyTxSize , CopyPolicyMultiplier e TxSizeToAlwaysCopy em struct DBNamespacesConfig (descrente.go)
Se o tamanho da transação for menor que TxSizeToAlwaysCopy , o Reindexer usa heurística extra e tentando evitar a cópia do namespace, se não houvesse nenhuma consulta de seleção vista para este espaço para nome. Em alguns casos, essa heurística pode aumentar a latência seleciona, portanto, pode ser desativada definindo a variável Env REINDEXER_NOTXHEURISTIC para qualquer valor não vazio.
tx.Query("ns").Exec() ... ;O Reindexer pode ingressar em documentos de vários espaços para nome em um único resultado:
type Actor struct {
ID int `reindex:"id"`
Name string `reindex:"name"`
IsVisible bool `reindex:"is_visible"`
}
// Fields, marked with 'joined' must also be exported - otherwise reindexer's binding will not be able to put data in those fields
type ItemWithJoin struct {
ID int `reindex:"id"`
Name string `reindex:"name"`
ActorsIDs [] int `reindex:"actors_ids"`
ActorsNames [] int `reindex:"actors_names"`
Actors [] * Actor `reindex:"actors,,joined"`
}
... .
query := db . Query ( "items_with_join" ). Join (
db . Query ( "actors" ).
WhereBool ( "is_visible" , reindexer . EQ , true ),
"actors"
). On ( "actors_ids" , reindexer . SET , "id" )
it := query . Exec ()Neste exemplo, o Reindexer usa a reflexão sob o capô para criar fatia de ator e copiar a estrutura do ator.
A consulta de junção pode ter de um a On condições relacionadas And (por padrão) Or ou Not operadores:
query := db . Query ( "items_with_join" ).
Join (
db . Query ( "actors" ).
WhereBool ( "is_visible" , reindexer . EQ , true ),
"actors" ).
On ( "actors_ids" , reindexer . SET , "id" ).
Or ().
On ( "actors_names" , reindexer . SET , "name" ) Um InnerJoin combina dados de dois espaços para nome, onde há uma correspondência nos campos de união em ambos os namespaces. Um LeftJoin retorna todos os itens válidos dos namespaces no lado esquerdo da palavra -chave LeftJoin , juntamente com os valores da tabela no lado direito, ou nada se um item correspondente não existir. Join é um pseudônimo para LeftJoin .
InnerJoins pode ser usado como uma condição em Where a cláusula:
query1 := db . Query ( "items_with_join" ).
WhereInt ( "id" , reindexer . RANGE , [] int { 0 , 100 }).
Or ().
InnerJoin ( db . Query ( "actors" ). WhereString ( "name" , reindexer . EQ , "ActorName" ), "actors" ).
On ( "actors_ids" , reindexer . SET , "id" ).
Or ().
InnerJoin ( db . Query ( "actors" ). WhereInt ( "id" , reindexer . RANGE , [] int { 100 , 200 }), "actors" ).
On ( "actors_ids" , reindexer . SET , "id" )
query2 := db . Query ( "items_with_join" ).
WhereInt ( "id" , reindexer . RANGE , [] int { 0 , 100 }).
Or ().
OpenBracket ().
InnerJoin ( db . Query ( "actors" ). WhereString ( "name" , reindexer . EQ , "ActorName" ), "actors" ).
On ( "actors_ids" , reindexer . SET , "id" ).
InnerJoin ( db . Query ( "actors" ). WhereInt ( "id" , reindexer . RANGE , [] int { 100 , 200 }), "actors" ).
On ( "actors_ids" , reindexer . SET , "id" ).
CloseBracket ()
query3 := db . Query ( "items_with_join" ).
WhereInt ( "id" , reindexer . RANGE , [] int { 0 , 100 }).
Or ().
InnerJoin ( db . Query ( "actors" ). WhereInt ( "id" , reindexer . RANGE , [] int { 100 , 200 }), "actors" ).
On ( "actors_ids" , reindexer . SET , "id" ).
Limit ( 0 ) Observe que geralmente Or o operador implementa curto-circuito para Where as condições: se a condição anterior for verdadeira, a próxima não será avaliada. Mas, no caso de InnerJoin , ele funciona de maneira diferente: no query1 (do exemplo acima), ambas as condições InnerJoin são avaliadas, apesar do resultado da WhereInt . Limit(0) como parte do InnerJoin ( query3 do exemplo acima) não ingressa em nenhum dado - ele funciona como um filtro apenas para verificar as condições.
O Reindexer não suporta a construção ANTI JOIN SQL, no entanto, suporta operações lógicas com junções. De fato, NOT (INNER JOIN ...) é totalmente equivalente ao ANTI JOIN :
query := db . Query ( "items_with_join" ).
Not ().
OpenBracket (). // Brackets are essential here for NOT to work
InnerJoin (
db . Query ( "actors" ).
WhereBool ( "is_visible" , reindexer . EQ , true ),
"actors" ).
On ( "id" , reindexer . EQ , "id" )
CloseBracket () SELECT * FROM items_with_join
WHERE
NOT (
INNER JOIN (
SELECT * FROM actors WHERE is_visible = true
) ON items_with_join . id = actors . id
) Para evitar o uso de reflexão, Item pode implementar a interface Joinable . Se isso implementou, o Reindexer usa isso em vez da implementação lenta baseada em reflexão. Isso aumenta o desempenho geral em 10 a 20%e reduz a quantidade de alocações.
// Joinable interface implementation.
// Join adds items from the joined namespace to the `ItemWithJoin` object.
// When calling Joinable interface, additional context variable can be passed to implement extra logic in Join.
func ( item * ItemWithJoin ) Join ( field string , subitems [] interface {}, context interface {}) {
switch field {
case "actors" :
for _ , joinItem := range subitems {
item . Actors = append ( item . Actors , joinItem .( * Actor ))
}
}
}Uma condição pode ser aplicada para resultado de outra consulta (subconstrução) incluída na consulta atual. A condição pode estar em linhas resultantes da subconsulta:
query := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 ), reindexer . GE , 100 )ou entre um campo de namespace da principal consulta e o resultado da subconsência:
query := db . Query ( "main_ns" ).
Where ( "id" , reindexer . EQ , db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 )) O resultado da subconsulta pode ser um determinado campo apontado pelo método Select (neste caso, ele deve definir o filtro de campo único):
query1 := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 ), reindexer . GE , 100 )
query2 := db . Query ( "main_ns" ).
Where ( "id" , reindexer . EQ , db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 )) ou contagem de itens satisfatórios para a subconsulta exigida pelos métodos ReqTotal ou CachedTotal :
query1 := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Where ( "age" , reindexer . GE , 18 ). ReqTotal (), reindexer . GE , 100 )
query2 := db . Query ( "main_ns" ).
Where ( "id" , reindexer . EQ , db . Query ( "second_ns" ). Where ( "age" , reindexer . GE , 18 ). CachedTotal ())ou agregação:
query1 := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Where ( "age" , reindexer . GE , 18 ). AggregateMax ( "age" ), reindexer . GE , 33 )
query2 := db . Query ( "main_ns" ).
Where ( "age" , reindexer . GE , db . Query ( "second_ns" ). Where ( "age" , reindexer . GE , 18 ). AggregateAvg ( "age" )) As agregações Min , Max , Avg , Sum , Count e CountCached são permitidas apenas. A subconsulta não pode conter várias agregações ao mesmo tempo.
A subconsulta pode ser aplicada ao mesmo espaço para nome ou ao outro.
A subconsulta não pode conter outra subconsulta, unir ou mesclar.
Se você deseja verificar se pelo menos um dos itens é satisfatório para as subconsações, você pode usar ANY condição vazia ou EMPTY :
query1 := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Where ( "age" , reindexer . GE , 18 ), reindexer . ANY , nil )
query2 := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Where ( "age" , reindexer . LE , 18 ), reindexer . EMPTY , nil )Um documento pode ter vários campos como chave primária. Para ativar esse recurso, adicione índice composto à estrutura. O índice composto é um índice que envolve vários campos, ele pode ser usado em vez de vários índices separados.
type Item struct {
ID int64 `reindex:"id"` // 'id' is a part of a primary key
SubID int `reindex:"sub_id"` // 'sub_id' is a part of a primary key
// Fields
// ....
// Composite index
_ struct {} `reindex:"id+sub_id,,composite,pk"`
}OU
type Item struct {
ID int64 `reindex:"id,-"` // 'id' is a part of primary key, WITHOUT personal searchable index
SubID int `reindex:"sub_id,-"` // 'sub_id' is a part of a primary key, WITHOUT a personal searchable index
SubSubID int `reindex:"sub_sub_id,-"` // 'sub_sub_id' is a part of a primary key WITHOUT a personal searchable index
// Fields
// ....
// Composite index
_ struct {} `reindex:"id+sub_id+sub_sub_id,,composite,pk"`
}Além disso, os índices compostos são úteis para classificar os resultados de vários campos:
type Item struct {
ID int64 `reindex:"id,,pk"`
Rating int `reindex:"rating"`
Year int `reindex:"year"`
// Composite index
_ struct {} `reindex:"rating+year,tree,composite"`
}
...
// Sort query results by rating first, then by year
query := db . Query ( "items" ). Sort ( "rating+year" , true )
// Sort query results by rating first, then by year, and put item where rating == 5 and year == 2010 first
query := db . Query ( "items" ). Sort ( "rating+year" , true ,[] interface {}{ 5 , 2010 }) Para fazer consulta ao índice composto, passe [] interface {} para .WhereComposite função do consultor de consultas:
// Get results where rating == 5 and year == 2010
query := db . Query ( "items" ). WhereComposite ( "rating+year" , reindexer . EQ ,[] interface {}{ 5 , 2010 }) Todos os campos em índice composto regular (não cheio de integridade) devem ser indexados. Ou seja, para poder criar rating+year , é necessário criar algum tipo de índice para raiting e year primeiro:
type Item struct {
ID int64 `reindex:"id,,pk"`
Rating int `reindex:"rating,-"` // this field must be indexed (using index type '-' in this example)
Year int `reindex:"year"` // this field must be indexed (using index type 'hash' in this example)
_ struct {} `reindex:"rating+year,tree,composite"`
}O reindexer permite recuperar resultados agregados. Atualmente, é suportado por contagem, countcached, soma, mínima, máxima, faceta e distintas.
Count - Obtenha o número total de documentos que atendem às condições da QuerieCountCached - Obtenha o número total de documentos que atendem às condições da Querie. O valor do resultado será armazenado em cache e poderá ser reutilizado pelas outras consultas com agregação do CountCachedAggregateMax - obtenha o valor máximo de campoAggregateMin - obtenha valor mínimo de campoAggregateSum - obtenha o valor do campo da somaAggregateAvg - Obtenha o valor médio do campoAggregateFacet - obtenha o valor da faceta dos camposDistinct - obtenha uma lista de valores exclusivos do campo Para apoiar a agregação, Query possui métodos AggregateAvg , AggregateSum , AggregateMin , AggregateMax , AggregateFacet e Distinct que devem ser chamados antes da execução Query : isso solicitará ao Reindexer que calcule as agregações de dados. A faceta de agregação é aplicável a várias colunas de dados e o resultado disso pode ser classificado por qualquer coluna de dados ou 'contagem' e cortado por deslocamento e limite. Para suportar esse método de funcionalidade, AggregateFacet retorna AggregationFacetRequest , que possui métodos Sort , Limit e Offset .
As consultas com a fusão aplicarão agregações da consulta principal a todas as subconesas mescladas. As subconhecimento não podem ter suas próprias agregações. As agregações disponíveis para mesclagem de mesclagem são: Count, CountCached, Sum, Min e Max.
Para obter resultados de agregação, Iterator possui AggResults de método: está disponível após a execução da consulta e retorna fatia de resultados.
Exemplo Código para items agregados por price e name
query := db . Query ( "items" )
query . AggregateMax ( "price" )
query . AggregateFacet ( "name" , "price" ). Sort ( "name" , true ). Sort ( "count" , false ). Offset ( 10 ). Limit ( 100 )
iterator := query . Exec ()
// Check the error
if err := iterator . Error (); err != nil {
panic ( err )
}
defer iterator . Close ()
aggMaxRes := iterator . AggResults ()[ 0 ]
if aggMaxRes . Value != nil {
fmt . Printf ( "max price = %d n " , * aggMaxRes . Value )
} else {
fmt . Println ( "no data to aggregate" )
}
aggFacetRes := iterator . AggResults ()[ 1 ]
fmt . Printf ( "'name' 'price' -> count" )
for _ , facet := range aggFacetRes . Facets {
fmt . Printf ( "'%s' '%s' -> %d" , facet . Values [ 0 ], facet . Values [ 1 ], facet . Count )
} query := db . Query ( "items" )
query . Distinct ( "name" ). Distinct ( "price" )
iterator := query . Exec ()
// Check the error
if err := iterator . Error (); err != nil {
panic ( err )
}
defer iterator . Close ()
aggResults := iterator . AggResults ()
distNames := aggResults [ 0 ]
fmt . Println ( "names:" )
for _ , name := range distNames . Distincts {
fmt . Println ( name )
}
distPrices := aggResults [ 1 ]
fmt . Println ( "prices:" )
for _ , price := range distPrices . Distincts {
fmt . Println ( price )
} A classificação pelos campos agregados da FACET tem sintaxe distinta na versão SQL:
SELECT FACET(name, price ORDER BY " name " ASC , " count " DESC ) FROM itemsO reindexer permite pesquisar dados nos campos de matriz quando os valores correspondentes têm as mesmas posições de índices. Por exemplo, temos uma variedade de estruturas:
type Elem struct {
F1 int `reindex:"f1"`
F2 int `reindex:"f2"`
}
type A struct {
Elems [] Elem
}Tentativa comum de pesquisar valores nesta matriz
db . Query ( "Namespace" ). Where ( "f1" , EQ , 1 ). Where ( "f2" , EQ , 2 ) Encontra todos os itens de matriz Elem[] onde f1 é igual a 1 e f2 é igual a 2.
A função EqualPosition permite pesquisar em campos de matriz com índices iguais. Consultas como esta:
db . Query ( "Namespace" ). Where ( "f1" , reindexer . GE , 5 ). Where ( "f2" , reindexer . EQ , 100 ). EqualPosition ( "f1" , "f2" )ou
SELECT * FROM Namespace WHERE f1 >= 5 AND f2 = 100 EQUAL_POSITION(f1,f2); encontrará todos os itens da matriz Elem[] com índices equal de matriz, onde f1 é maior ou igual a 5 e f2 é igual a 100 (por exemplo, a consulta retornou 5 itens em que apenas 3º elementos de ambas as matrizes têm valores apropriados).
Com expressões complexas (expressões com colchetes) igual_position () pode estar dentro de um suporte:
SELECT * FROM Namespace WHERE (f1 >= 5 AND f2 = 100 EQUAL_POSITION(f1,f2)) OR (f3 = 3 AND f4 < 4 AND f5 = 7 EQUAL_POSITION(f3,f4,f5));
SELECT * FROM Namespace WHERE (f1 >= 5 AND f2 = 100 AND f3 = 3 AND f4 < 4 EQUAL_POSITION(f1,f3) EQUAL_POSITION(f2,f4)) OR (f5 = 3 AND f6 < 4 AND f7 = 7 EQUAL_POSITION(f5,f7));
SELECT * FROM Namespace WHERE f1 >= 5 AND (f2 = 100 AND f3 = 3 AND f4 < 4 EQUAL_POSITION(f2,f3)) AND f5 = 3 AND f6 < 4 EQUAL_POSITION(f1,f5,f6); equal_position não funciona com as seguintes condições: é nulo, está vazio e dentro (com lista de parâmetros vazios).
Existem funções atômicas, que são executadas no bloqueio do espaço para nome e, portanto, garante a consistência dos dados:
Essas funções podem ser transmitidas para aumentar/inserir/atualizar nos argumentos 3-RD e nos próximos.
Se essas funções forem fornecidas, o item de referência passado será alterado para o valor atualizado
// set ID field from serial generator
db . Insert ( "items" , & item , "id=serial()" )
// set current timestamp in nanoseconds to updated_at field
db . Update ( "items" , & item , "updated_at=now(NSEC)" )
// set current timestamp and ID
db . Upsert ( "items" , & item , "updated_at=now(NSEC)" , "id=serial()" )A expiração dos dados é útil para algumas classes de informações, incluindo dados de eventos gerados por máquina, logs e informações de sessão que só precisam persistir por um período limitado de tempo.
O Reindexer possibilita definir TTL (hora de viver) para itens de namespace. Adicionar o TTLIndex ao namespace remove automaticamente os itens após um número especificado de segundos.
Os índices TTL funcionam apenas com campos INT64 e armazenam dados do UNIX Timestamp. Os itens que contêm o índice TTL expirarem após expire_after segundos. Exemplo de declarar ttlindex em Golang:
type NamespaceExample struct {
ID int `reindex:"id,,pk" json:"id"`
Date int64 `reindex:"date,ttl,,expire_after=3600" json:"date"`
}
...
ns . Date = time . Now (). Unix ()Nesse caso, os itens de namespace de namespaceExample expirarem em 3600 segundos após o nome do namespaceExample.date o valor do campo (que é o timestamp Unix).
Um índice TTL suporta consultas da mesma maneira que os índices não TTL.
Se os dados de origem estiverem disponíveis no formato JSON, é possível melhorar o desempenho das operações UpSert/Excluir, passando diretamente no JSON para o reindexer. A desserialização do JSON será feita pelo código C ++, sem aloces extras/deserialização no código GO.
Funções de upsert ou exclusão podem processar JSON apenas aproveitando [] byte argumentos com JSON
json := [] byte ( `{"id":1,"name":"test"}` )
db . Upsert ( "items" , json )É apenas mais rápido equivalente a:
item := & Item {}
json . Unmarshal ([] byte ( `{"id":1,"name":"test"}` ), item )
db . Upsert ( "items" , item )Em caso de exigência para serializar os resultados da consulta no formato JSON, é possível melhorar o desempenho, obtendo resultados diretamente no formato JSON do reindexer. A serialização JSON será feita pelo código C ++, sem aloces/serialização extra no código GO.
...
iterator := db . Query ( "items" ).
Select ( "id" , "name" ). // Filter output JSON: Select only "id" and "name" fields of items, another fields will be omitted. This fields should be specified in the same case as the jsonpaths corresponding to them.
Limit ( 1 ).
ExecToJson ( "root_object" ) // Name of root object of output JSON
json , err := iterator . FetchAll ()
// Check the error
if err != nil {
panic ( err )
}
fmt . Printf ( "%s n " , string ( json ))
...Este código imprimirá algo como:
{ "root_object" : [{ "id" : 1 , "name" : " test " }] } Para evitar as condições de corrida, por padrão o cache do objeto é desligado e todos os objetos são alocados e desapealizados do formato interno do reindexer (chamado CJSON ) por cada consulta. A deserialização é a reflexão, portanto, sua velocidade não é ideal (na verdade, a desserialização CJSON é ~ 3-10x mais rápida que JSON e ~ 1,2x mais rápido que GOB ), mas o desempenho ainda é seriamente limitado pela sobrecarga de reflexão.
Existem 2 maneiras de ativar o cache do objeto:
Se o objeto estiver implementa a interface DeepCopy, o reindexer ativará o cache do objeto e usará a interface do DeepCopy para copiar objetos do cache para os resultados da consulta. A interface DeepCopy é responsável por fazer cópia profunda do objeto de origem.
Aqui está uma amostra de implementação da interface do DeepCopy
func ( item * Item ) DeepCopy () interface {} {
copyItem := & Item {
ID : item . ID ,
Name : item . Name ,
Articles : make ([] int , cap ( item . Articles ), len ( item . Articles )),
Year : item . Year ,
}
copy ( copyItem . Articles , item . Articles )
return copyItem
} Para acelerar as consultas e não alocar novos objetos de acordo com cada consulta, é possível solicitar objetos de retorno de consulta diretamente do cache do objeto. Para ativar esse comportamento, ligue para AllowUnsafe(true) no Iterator .
AVISO: Quando usado AllowUnsafe(true) consultas retorna ponteiros compartilhados para estruturas no cache do objeto. Portanto, o aplicativo não deve modificar objetos retornados.
res , err := db . Query ( "items" ). WhereInt ( "id" , reindexer . EQ , 1 ). Exec (). AllowUnsafe ( true ). FetchAll ()
if err != nil {
panic ( err )
}
if len ( res ) > 1 {
// item is SHARED pointer to struct in object cache
item = res [ 0 ].( * Item )
// It's OK - fmt.Printf will not modify item
fmt . Printf ( "%v" , item )
// It's WRONG - can race, and will corrupt data in object cache
item . Name = "new name"
} Por padrão, o tamanho máximo do cache do objeto é de 256000 itens para cada espaço de nome. Para alterar o tamanho máximo, use o método de NamespaceOptions ObjCacheSize , passado para o OpenNamesPace. por exemplo
// Set object cache limit to 4096 items
db . OpenNamespace ( "items_with_huge_cache" , reindexer . DefaultNamespaceOptions (). ObjCacheSize ( 4096 ), Item {})! Este cache não deve ser usado para os espaços para nome, que foram replicados dos outros nós: pode ser inconsistente para os namespaces desses réplicas.
O único tipo de dados de geometria suportado é o ponto 2D, que implementou em Golang como [2]float64 ( reindexer.Point ).
No SQL, um ponto pode ser criado como ST_GeomFromText('point(1 -3)') .
A única solicitação suportada para o campo de geometria é encontrar todos os pontos a uma distância de um ponto. DWithin(field_name, point, distance) como no exemplo abaixo.
A função SQL correspondente é ST_DWithin(field_name, point, distance) .
O índice rtree pode ser criado para pontos. Para fazer isso, rtree e tags linear , quadratic , greene ou rstar devem ser declaradas. linear , quadratic , greene ou rstar significa qual algoritmo de construção de Rtree seria usado. Aqui, os algoritmos estão listados em ordem a partir do otimizado para inserção a otimizado para pesquisa. Mas depende dos dados. Teste que é mais apropriado para você. O algoritmo padrão é rstar .
type Item struct {
id int `reindex:"id,,pk"`
pointIndexed reindexer. Point `reindex:"point_indexed,rtree,linear"`
pointNonIndexed reindexer. Point `json:"point_non_indexed"`
}
query1 := db . Query ( "items" ). DWithin ( "point_indexed" , reindexer. Point { - 1.0 , 1.0 }, 4.0 ) SELECT * FROM items WHERE ST_DWithin(point_non_indexed, ST_GeomFromText( ' point(1 -3.5) ' ), 5 . 0 ); O Reindexer Logger pode ser ativado pelo método db.SetLogger() , assim como neste trecho de código:
type Logger struct {
}
func ( Logger ) Printf ( level int , format string , msg ... interface {}) {
log . Printf ( format , msg ... )
}
...
db . SetLogger ( Logger {}) O reindexer suporta o registro de ações lentas. Ele pode ser configurado através da seção profiling.long_queries_logging do espaço de nome do sistema #config . O registro das próximas ações pode ser configurado:
Selecione consultas:
threshold_us (integer) : o valor do limite (em microssegundos) para execução de consulta selecionada. Se excedido, será feita uma entrada de log do núcleo, se threshold_us for -1 de log estiver desativado.normalized (boolean) : Saia a consulta em uma forma normalizada.Atualize e exclua consultas:
threshold_us (integer) : o valor limite (em microssegundos) para execução da atualização ou exclusão de consulta. Se excedido, será feita uma entrada de log do núcleo, se threshold_us for -1 de log estiver desativado.normalized (boolean) : Saia a consulta em uma forma normalizada.Transações:
threshold_us (integer) : valor limite (em microssegundos) para o tempo total de comprometimento da transação, se threshold_us for -1 de log pelo tempo total de comprometimento da transação estiver desativado.avg_step_threshold_us (integer) : Valor limite (em microssegundos) para o tempo médio de duração da etapa na transação. Se avg_step_threshold_us for -1 O log de duração da etapa da transação média estiver desativado.Outro recurso útil é a impressão de depuração de consultas processadas. Para depurar as consultas impressas Detalhes Existem 2 métodos:
db.SetDefaultQueryDebug(namespace string,level int) - ele permite globalmente detalhes de impressão de todas as consultas por namespace
query.Debug(level int) - Imprimir detalhes do level de execução da consulta é o nível de verbosidade:
reindexer.INFO - imprimirá apenas condições de consulta
reindexer.TRACE - Imprimirá condições de consulta e detalhes de execução com horários
query.Explain () - Calcule e armazene detalhes de execução da consulta.
iterator.GetExplainResults () - Retornar detalhes de execução da consulta
Reindexer has support for TCMalloc (which is also a part of GPerfTools) and JEMalloc allocators (check ENABLE_TCMALLOC and ENABLE_JEMALLOC in CMakeLists.txt).
If you have built standalone server from sources available allocators will be detected and used automatically.
In go:generate builds and prebuilt packages reindexer has TCMalloc support, however none of TCMalloc libraries will be linked automatically. To force allocator's libraries linkage LD_PRELOAD with required library has to be used:
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_and_profiler.so ./my_executable
Custom allocator may be handy to track memory consummation, profile heap/CPU or to improve general performance.
Because reindexer core is written in C++ all calls to reindexer and their memory consumption are not visible for go profiler. To profile reindexer core there are cgo profiler available. cgo profiler now is part of reindexer, but it can be used with any another cgo code.
Usage of cgo profiler is very similar with usage of go profiler.
import _ "github.com/restream/reindexer/v3/pprof" go func () {
log . Println ( http . ListenAndServe ( "localhost:6060" , nil ))
}()HEAPPROFILE=/tmp/pprofpprof -symbolize remote http://localhost:6060/debug/cgo/pprof/heapInternal Reindexer's profiler is based on gperf_tools library and unable to get CPU profile via Go runtime. However, go profiler may be used with symbolizer to retrieve C++ CPU usage.
import _ "net/http/pprof" go func () {
log . Println ( http . ListenAndServe ( "localhost:6060" , nil ))
}()REINDEXER_CGOBACKTRACE=1pprof -symbolize remote http://localhost:6060/debug/pprof/profile ? seconds=10Due to internal Golang's specific it's not recommended to try to get CPU and heap profiles simultaneously, because it may cause deadlock inside the profiler.
Go binding for Reindexer comes with optional support for OpenTelemetry integration.
To enable generation of OpenTelemetry tracing spans for all exported client side calls ( OpenNamespace , Upsert , etc), pass reindexer.WithOpenTelemetry() option when creating a Reindexer DB instance:
db := reindexer . NewReindex ( "cproto://user:[email protected]:6534/testdb" , reindexer . WithOpenTelemetry ()) All client side calls on the db instance will generate OpenTelemetry spans with the name of the performed operation and information about Reindexer DSN, namespace name (if applicable), etc.
For example, a call like this on the db instance above:
db . OpenNamespace ( "items" , reindexer . DefaultNamespaceOptions (), Item {}) will generate an OpenTelemetry span with the span name of Reindexer.OpenNamespace and with span attributes like this:
rx.dsn : cproto://user:[email protected]:6534/testdbrx.ns : items Use opentelemetry-go in your client go application to export the information externally. For example, as a minimum, you will need to configure OpenTelemetry SDK exporter to expose the generated spans externally (see the Getting Started guide for more information).
A list of connectors for work with Reindexer via other program languages (TBC later):
Pyreindexer is official connector, and maintained by Reindexer's team. It supports both builtin and standalone modes. Before installation reindexer-dev (version >= 2.10) should be installed. See installation instructions for details.
For install run:
pip3 install pyreindexerURLs:
Python version >=3.6 is required.
Reindexer for java is official connector, and maintained by Reindexer's team. It supports both builtin and standalone modes. For enable builtin mode support reindexer-dev (version >= 3.1.0) should be installed. See installation instructions for details.
For install reindexer to Java or Kotlin project add the following lines to maven project file
<dependency>
<groupId>com.github.restream</groupId>
<artifactId>rx-connector</artifactId>
<version>[LATEST_VERSION]</version>
</dependency>
URL: https://github.com/Restream/reindexer-java
Note: Java version >= 1.8 is required.
Spring wrapper for Java-connector: https://github.com/evgeniycheban/spring-data-reindexer
URL: https://github.com/Smolevich/reindexer-client
URL: https://github.com/coinrust/reindexer-rs
URL: https://github.com/oruchreis/ReindexerNet
Currently, Reindexer is stable and production ready, but it is still a work in progress, so there are some limitations and issues:
You can get help in several ways:
Landing: https://reindexer.io/
Packages repo: https://repo.reindexer.io/
More documentation (RU): https://reindexer.io/reindexer-docs/