
Reindexer es una base de datos integrable, en memoria y orientada a documentos con una interfaz de consultas de alto nivel.
El objetivo de Reindexer es proporcionar una búsqueda rápida con consultas complejas. En Restream no estábamos contentos con Elasticsearch y creamos Reindexer como una alternativa más actuante.
El núcleo está escrito en C ++ y la API de nivel de aplicación está en GO.
Este documento describe el conector GO y su API. Para obtener información sobre Reindexer Server y HTTP API, consulte la documentación de Reindexer
Hay dos versiones LTS de Reindexer disponibles: v3.xx y v4.xx
3.xx es actualmente nuestra rama convencional y 4.xx (rama de liberación/4) es la versión beta con el clúster de balsa experimental y el soporte de fragmentos. Los almacenes son compatibles entre esas versiones, sin embargo, las configuraciones de replicación son totalmente diferentes. Las versiones 3 y 4 están obteniendo las mismas correcciones de errores y características (excepto las relacionadas con la replicación).
Características clave:
El rendimiento ha sido nuestra máxima prioridad desde el principio, y creemos que logramos hacerlo bastante bien. Los puntos de referencia muestran que el rendimiento de Reindexer está a la par con una base de datos típica de valor clave. En un solo núcleo de CPU, obtenemos:
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 ...Ver resultados de evaluación comparativa y más detalles en el repositorio de la evaluación comparativa
Reindexer tiene como objetivo consumir la menor memoria posible; La mayoría de las consultas se procesan sin ninguna asignación de memoria.
Para lograr eso, se emplean varias optimizaciones, tanto en el nivel de C ++ como en GO:
Los documentos e índices se almacenan en densas estructuras binarias de C ++, por lo que no imponen ninguna carga en el recolector de basura de GO.
Los duplicados de cadena se fusionan.
La sobrecarga de memoria es de aproximadamente 32 bytes por documento + ≈4-16 bytes por cada índice de búsqueda.
Hay un caché de objetos en el nivel GO para documentos deserializados producidos después de la ejecución de la consulta. Las consultas futuras utilizan documentos predeserializados, que reducen los costos de deserialización y asignación repetidos
La interfaz de consulta utiliza sync.Pool para reutilizar estructuras y buffers internos. La combinación de estas tecnologías permite a Reindexer manejar la mayoría de las consultas sin ninguna asignación.
Reindexer tiene un motor de búsqueda de texto completo interno. La documentación y los ejemplos de uso de la búsqueda de texto completo están aquí
Reindexer puede almacenar documentos y cargar documentos desde el disco a través de LevelDB. Los documentos se escriben en Backend Backend de almacenamiento asincrónicamente por grandes lotes automáticamente en segundo plano.
Cuando se crea un espacio de nombres, todos sus documentos se almacenan en RAM, por lo que las consultas de estos documentos se ejecutan completamente en modo en memoria.
Aquí hay un ejemplo completo de uso básico de reindexero:
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 )
}
}También hay algunas muestras básicas para C ++ y vaya aquí.
Como alternativa al creador de consultas, Reindexer proporciona una interfaz de consulta compatible con SQL. Aquí hay una muestra del uso de la interfaz SQL:
...
iterator := db . ExecSQL ( "SELECT * FROM items WHERE name='Vasya' AND year > 2020 AND articles IN (6,1,8) ORDER BY year LIMIT 10" )
...Tenga en cuenta que se prefiere la interfaz del constructor de consultas: tiene más funciones y es más rápido que la interfaz SQL
Los literales de cadena deben estar encerrados en citas individuales.
Los índices compuestos deben estar encerrados en cotizaciones dobles.
SELECT * FROM items WHERE " field1+field2 " = ' Vasya 'Si el nombre del campo no comienza con alfa, '_' o '#' debe estar encerrado en cotizaciones dobles, ejemplos:
UPDATE items DROP " 123 " SELECT * FROM ns WHERE " 123 " = ' some_value ' SELECT * FROM ns WHERE " 123abc " = 123 DELETE FROM ns WHERE " 123abc123 " = 111Se pueden realizar uniones simples a través de la sintaxis SQL predeterminada:
SELECT * FROM ns INNER JOIN ns2 ON ns2 . id = ns . fk_id WHERE a > 0Se unen con condición en el espacio de nombres de la izquierda debe usar sintaxis similar a la subconsulta:
SELECT * FROM ns WHERE a > 0 AND INNER JOIN ( SELECT * FROM ns2 WHERE b > 10 AND c = 1 ) ON ns2 . id = ns . fk_idSubquery también puede ser parte de Where-Condition:
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 Reindexer puede ejecutarse en 3 modos diferentes:
embedded (builtin) está integrado en la aplicación como biblioteca estática, y no requiere un proceso de servidor separado.embedded with server (builtinserver) está integrado en la aplicación como biblioteca estática, y Start Server. En este modo, otros clientes pueden conectarse a la aplicación a través de CPROTO, UCPROTO o HTTP.standalone Ejecute como servidor independiente, la aplicación se conecta a reindexer a través de sockets de dominio de red o UNIX.En este modo, la unión de Go Reindexer no depende de la biblioteca estática de Reindexer.
La forma más sencilla de obtener el servidor Reindexer es extraer y ejecutar Docker Image de DockerHub.
docker run -p9088:9088 -p6534:6534 -it reindexer/reindexerDockfile
El núcleo de Reindexer está escrito en C ++ 17 y usa LevelDB como el backend de almacenamiento, por lo que la cadena de herramientas Cmake, C ++ 17 y LevelDB deben instalarse antes de instalar Reindexer.
Para construir Reindexer, se requiere G ++ 8+, Clang 7+ o MingW64.
En esos modos, la unión de GO de Reindexer depende de las bibliotecas estáticas de Reindexer (Core, Server y Recurse).
De esta manera se recomienda y se ajustará a la mayoría de los escenarios.
Los módulos GO con Go.mod no permiten construir bibliotecas C ++ en los directorios de los módulos. La unión de GO usará PKG-config para detectar los directorios de las bibliotecas.
Las bibliotecas de Reindexer deben instalarse desde las fuentes o desde el paquete PreBuilt a través del Administrador de paquetes.
Luego obtenga el módulo:
go get -a github.com/restream/reindexer/v3 Si necesita fuentes de Reindexer modificadas, puede usar replace así.
# 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/reindexerEn este caso, la unión de GO generará una lista explícita de bibliotecas y rutas y no utilizará PKG-config.
Si no está usando Go.mod, es posible obtener y construir Reindexer a partir de fuentes de esta manera:
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/builtinserverGO no admite el proveedor adecuado para el código CGO (Golang/GO#26366), sin embargo, es posible usar VEND para copiar las fuentes de Reindexer en el directorio de proveedores.
Con vend , podrá llamar go generate -mod=vendor para builtin y builtinserver , colocado en su directorio de proveedores.
También es posible copiar simplemente las fuentes de Reindexer en el proyecto juvenil, utilizando git clone .
En estos casos, todas las dependencias de Reindexer's Go.mod debe instalarse manualmente con las versiones adecuadas.
Internamente, las estructuras se dividen en dos partes:
reindex Las consultas son posibles solo en los campos indexados, marcados con etiqueta reindex . La etiqueta reindex contiene el nombre del índice, el tipo y las opciones adicionales:
reindex:"<name>[[,<type>],<opts>]"
name - Nombre del índice.type - Tipo de índice:hash - Seleccionar rápido por EQ y Establecer coincidencia. Utilizado por defecto. Permite una clasificación lenta e ineficiente por campo.tree - Fast Select by Range, GT y LT coincidientes. Un poco más lento para EQ y establecer coincidencias que el índice hash . Permite resultados de clasificación rápida por campo.text - Índice de búsqueda de texto completo. Los detalles de uso de la búsqueda de texto completo se describen aquí- - Índice de columna. No se puede realizar rápidamente seleccionados porque se implementa con Technic de escaneo completo. Tiene la parte superior de la memoria más pequeña.ttl - Índice TTL que funciona solo con los campos INT64. Estos índices son bastante convenientes para la representación de los campos de fecha (almacenados como marcas de tiempo UNIX) que caducan después de una cantidad especificada de segundos.rtree - Disponible solo en la coincidencia. Aceptable solo para [2]float64 (o reindexer.Point ). Para más detalles, consulte la subsección de geometría.opts - Opciones de índice adicionales:pk : el campo es parte de una clave principal. Struct debe tener al menos 1 campo etiquetado con pkcomposite - Crear índice compuesto. El tipo de campo debe ser una estructura vacía: struct{} .joined : Field es un destinatario de unión. El tipo de campo debe ser []*SubitemType .dense : reducir el tamaño del índice. Para hash y tree ahorrará 8 bytes por valor clave único. Para - ahorrará 4-8 bytes por cada elemento. Útil para índices con alta selectividad, pero para los índices tree y hash con baja selectividad pueden disminuir seriamente el rendimiento de la actualización. También dense ralentizará las consultas de granos completos en - índices, debido a la falta de optimización de la caché de la CPU.sparse - FILA (Documento) contiene un valor de índice disperso solo en caso de que se establezca a propósito, no hay registros vacíos (o predeterminados) de este tipo de índices en la fila (documento). Permite ahorrar RAM, pero le costará el rendimiento: funciona un poco más lento que los índices regulares.collate_numeric - Crear índice de cadena que proporcione el orden de valores en secuencia numérica. El tipo de campo debe ser una cadena.collate_ascii - Crear el índice de cadenas insensible a la caja funciona con ASCII. El tipo de campo debe ser una cadena.collate_utf8 - Crear el índice de cadenas insensible a la caja funciona con UTF8. El tipo de campo debe ser una cadena.collate_custom=<ORDER> - Crear índice de cadena de pedido personalizado. El tipo de campo debe ser una cadena. <ORDER> es secuencia de letras, que define el orden de clasificación.linear , quadratic , greene o rstar : especifique el algoritmo para la construcción del índice rtree (por defecto rstar ). Para más detalles, consulte la subsección de geometría.uuid - Guarde este valor como UUID. Esto es mucho más efectivo desde el punto de vista de consumación de RAM/red para UUID, que las cadenas. Solo se admiten los tipos de índice hash y - para UUID. Se puede usar con cualquier variante UUID, excepto la variante 0 Los campos con índices regulares no son anulables. La condición is NULL solo es compatible con los índices sparse y array .
Por defecto, Reindexer escanea todas las estructuras anidadas y agrega sus campos al espacio de nombres (así como los índices especificados). Durante los índices escanean privados (campos no exportados), los campos etiquetados con reindex:"-" y los campos etiquetados con json:"-" se omitirán.
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")
}Reindexer puede ordenar documentos mediante campos (incluidos los campos anidados y de los espacios de nombres unidos) o por expresiones en orden ascendente o descendente.
Para ordenar por campos sin índice, todos los valores deben ser convertibles entre sí, es decir, tener los mismos tipos o ser uno de los tipos numéricos ( bool , int , int64 o float ).
Las expresiones de clasificación pueden contener:
bool , int , int64 , float o tipos string . Todos los valores deben ser convertibles en números que ignoren los espacios de liderazgo y de acabado;rank() , abs() y ST_Distance() ;+ , - (unary and binary), * y / . Si el nombre del campo seguido de + debe estar separado por el espacio para distinguir el nombre del índice compuesto. Los campos de los espacios de nombres unidos deben escribirse así: joined_namespace.field .
Abs() significa valor absoluto de un argumento.
Rank() significa Rango de Match Rank de Match y es aplicable solo en Fulltext Consult.
ST_Distance() significa distancia entre los puntos de geometría (ver subsección de geometría). Los puntos podrían ser columnas en espacios de nombres actuales o unidos o punto fijo en formato ST_GeomFromText('point(1 -3)')
En la consulta SQL se debe citar la expresión.
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" )También es posible establecer un orden de clasificación personalizado como este
type SortModeCustomItem struct {
ID int `reindex:"id,,pk"`
InsItem string `reindex:"item_custom,hash,collate_custom=a-zA-Z0-9"`
}o así
type SortModeCustomItem struct {
ID int `reindex:"id,,pk"`
InsItem string `reindex:"item_custom,hash,collate_custom=АаБбВвГгДдЕеЖжЗзИиКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭ-ЯAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0-9ЁёЙйэ-я"`
}El primer personaje de esta lista tiene la prioridad más alta, la prioridad del último personaje es el más pequeño. Significa que el algoritmo de clasificación pondrá elementos que comienzan con el primer personaje antes que otros. Si se omiten algunos personajes, sus prioridades tendrían sus valores habituales (según los personajes de la lista).
Para un patrón de texto de búsqueda simple en la condición de campos de cadena LIKE se puede usar. Busca cuerdas que coincidan con el patrón. En el patrón _ significa cualquier char y % significa cualquier secuencia de caracteres.
Ejemplo de Go:
query := db . Query ( "items" ).
Where ( "field" , reindexer . LIKE , "pattern" )Ejemplo de SQL:
SELECT * FROM items WHERE fields LIKE ' pattern ''me_t' corresponde a 'reunirse', 'carne', 'fundir' y así sucesivamente en '%ción' corresponde a 'ción', 'condición', 'creación' y así sucesivamente
PRECAUCIÓN : Condición me gusta el método de escaneo. Se puede usar para fines de depuración o en consultas con otras buenas condiciones selectivas.
Generalmente para la búsqueda de texto completo con velocidad razonable, recomendamos usar el índice FullText.
Las consultas de actualización se utilizan para modificar los elementos existentes de un espacio de nombres. Hay varios tipos de consultas de actualización: actualizar los campos existentes, agregar nuevos campos y eliminar los campos no indexados existentes.
Actualizar SQL-Syntax
UPDATE nsName
SET field1 = value1, field2 = value2, ..
WHERE condition;También es posible usar expresiones aritméticas con +, -, /, * y soportes
UPDATE NS SET field1 = field2 + field3 - (field4 + 5 ) / 2 incluyendo funciones como now() , sec() y serial() . Para usar expresiones del método de código Golang SetExpression() debe llamarse en lugar de Set() .
Para hacer un campo de matriz vacío
UPDATE NS SET arrayfield = [] WHERE id = 100y establecerlo en nulo
UPDATE NS SET field = NULL WHERE id > 100En el caso de los campos no indexados, establecer su valor en un valor de un tipo diferente lo reemplazará por completo; En el caso de los campos indexados, solo es posible convertirlo de tipo adyacente (tipos integrales y bool), cadenas numéricas (como "123456") a los tipos integrales y la espalda. Configuración del campo indexado en nulo restablecerlo a un valor predeterminado.
Es posible agregar nuevos campos a los elementos existentes
UPDATE NS SET newField = ' Brand new! ' WHERE id > 100e incluso agregar un nuevo campo por un camino anidado complejo como este
UPDATE NS SET nested . nested2 . nested3 . nested4 .newField = ' new nested field! ' WHERE id > 100creará los siguientes objetos anidados: anidados, anidados2, anidados3, nestado4 y newfield como miembro de objeto anidados4.
Ejemplo del uso de consultas de actualización en el código Golang:
db . Query ( "items" ). Where ( "id" , reindexer . EQ , 40 ). Set ( "field1" , values ). Update ()Reindexer habilita actualizar y agregar campos de objetos. El objeto se puede establecer mediante una estructura, un mapa o una matriz de bytes (que es una versión JSON de la representación del objeto).
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 () En este caso, Map en Golang solo puede funcionar con String como clave. map[string]interface{} es una opción perfecta.
Actualización del campo de objetos por declaración 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 ;Actualizar SQL-Syntax de consultas que eliminan los campos no indexados existentes:
UPDATE nsName
DROP field1, field2, ..
WHERE condition; db . Query ( "items" ). Where ( "id" , reindexer . EQ , 40 ). Drop ( "field1" ). Update ()El mecanismo de actualización de Reindexer permite modificar los campos de matriz: para modificar un cierto elemento de una matriz existente o incluso para reemplazar un campo completo.
Para actualizar una sintaxis del operador de suscripción de elementos:
UPDATE NS SET array[ * ].prices[ 0 ] = 9999 WHERE id = 5 donde * significa todos los elementos.
Para actualizar la matriz completa se usa lo siguiente:
UPDATE NS SET prices = [ 999 , 1999 , 2999 ] WHERE id = 9Cualquier campo no indexado se puede convertir fácilmente en matriz utilizando esta sintaxis.
Reindexer también permite actualizar elementos de matrices de objetos:
UPDATE NS SET extra . objects [ 0 ] = { " Id " : 0 , " Description " : " Updated! " } WHERE id = 9También como esto
db . Query ( "clients" ). Where ( "id" , reindexer . EQ , 100 ). SetObject ( "extra.objects[0]" , updatedValue ). Update ()Reindexer admite matrices 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 ()Los campos de la matriz de índice admiten valores que se pueden convertir solo a un tipo de índice. Cuando se guardan, dichos valores pueden cambiar la precisión debido a la conversión.
UPDATE NS SET prices_idx = [ 11 , ' 2 ' , 3 ]Para eliminar el elemento por índice debe hacer lo siguiente:
UPDATE NS DROP array[ 5 ]Para agregar elementos a una matriz existente, se admite la siguiente sintaxis:
UPDATE NS SET integer_array = integer_array || [ 5 , 6 , 7 , 8 ]y
UPDATE NS SET integer_array = [ 1 , 2 , 3 , 4 , 5 ] || integer_array El primero agrega elementos al final de integer_array , el segundo agrega 5 elementos al frente. Para que este código funcione en Golang SetExpression() debe usarse en lugar de Set() .
Para eliminar los elementos por valor en una matriz existente, se admite la siguiente sintaxis:
UPDATE NS SET integer_array = array_remove(integer_array, [ 5 , 6 , 7 , 8 ])y
UPDATE NS SET integer_array = array_remove_once(integer_array, [ 5 , 6 , 7 , 8 , 6 ]) El primero elimina todos los ocurrencias de los valores enumerados en integer_array , el segundo elimina solo el primer hecho encontrado. Para que este código funcione en Golang SetExpression() debe usarse en lugar de Set() . Si necesita eliminar un valor, puede usar soportes cuadrados [5] o valor simple 5 .
UPDATE NS SET integer_array = array_remove(integer_array, [ 5 ]) update ns set integer_array = array_remove(integer_array, 5 )El comando eliminar se puede combinar con concatenato de matriz:
UPDATE NS SET integer_array = array_remove_once(integer_array, [ 5 , 6 , 7 , 8 ]) || [ 1 , 2 , 3 ]También como esto
db . Query ( "main_ns" ). SetExpression ( "integer_array" , "array_remove(integer_array, [5,6,7,8]) || [1,2,3]" ). Update ()Es posible eliminar los valores del segundo campo de los valores del primer campo. Y también agregue nuevos valores, etc. Nota: Se espera que el primer parámetro en los comandos sea una matriz/matriz de campo, el segundo parámetro puede ser una matriz/escalar/lanosa/fila/escalar. Para los valores de compatibilidad/convertibilidad requerida
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 () Reindexer admite transacciones. La transacción se realiza una actualización del espacio de nombres atómico. Hay transacciones sincrónicas y asíncronas disponibles. Para iniciar el método de transacción db.BeginTx() se utiliza. Este método crea el objeto de transacción, que proporciona interfaz actualización/upsert/insert/delete de actualización habitual para la aplicación. Para los clientes de RPC hay una limitación de conteo de transacciones: cada conexión no puede tener más de 1024 transacciones abiertas al mismo tiempo.
// 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 la aceleración de la inserción del modo Async de registros a granel.
// 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 )
} El segundo argumento de UpsertAsync es la función de finalización, que se llamará después de recibir la respuesta del servidor. Además, si se produjo algún error durante el proceso de preparación, entonces tx.Commit debe devolver un error. Por lo tanto, es suficiente verificar el error devuelto por tx.Commit , sin duda, que todos los datos se hayan cometido con éxito o no.
Dependiendo de la cantidad de cambios en la transacción, existen 2 posibles estrategias de confirmación:
La cantidad de datos para seleccionar una estrategia de confirmación se puede seleccionar en la configuración del espacio de nombres. Verifique los campos StartCopyPolicyTxSize , CopyPolicyMultiplier y TxSizeToAlwaysCopy en struct DBNamespacesConfig (descriper.go)
Si el tamaño de la transacción es inferior al TxSizeToAlwaysCopy , Reindexer usa heurística adicional y tratando de evitar la copia del espacio de nombres, si no se observaron consultas de selección para este espacio de nombres. En algunos casos, esta heurística puede aumentar la latencia selecciona, por lo que puede deshabilitarse configurando REINDEXER_NOTXHEURISTIC env variable a cualquier valor no vacío.
tx.Query("ns").Exec() ... ;Reindexer puede unir documentos desde múltiples espacios de nombres en un solo 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 ()En este ejemplo, Reindexer usa la reflexión debajo del capó para crear la corte del actor y copiar la estructura del actor.
La consulta de unión puede tener de una a varias On relacionadas con And (por defecto), Or 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" ) Un InnerJoin combina datos de dos espacios de nombres donde hay una coincidencia en los campos de unión en ambos espacios de nombres. A LeftJoin devuelve todos los elementos válidos de los espacios de nombres en el lado izquierdo de la palabra clave LeftJoin , junto con los valores de la tabla en el lado derecho, o nada si no existe un elemento coincidente. Join es un alias para LeftJoin .
InnerJoins se puede usar como una condición en la cláusula Where :
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 ) Tenga en cuenta que generalmente Or el operador implementa un cortocircuito para las condiciones Where : si la condición anterior es verdadera, la siguiente no se evalúa. Pero en el caso de InnerJoin funciona de manera diferente: en query1 (del ejemplo anterior), ambas condiciones InnerJoin se evalúan a pesar del resultado de WhereInt . Limit(0) Como parte de InnerJoin ( query3 del ejemplo anterior) no se une a ningún dato: funciona como un filtro solo para verificar las condiciones.
Reindexer no admite la construcción de SQL ANTI JOIN , sin embargo, admite operaciones lógicas con uniones. De hecho, NOT (INNER JOIN ...) es totalmente equivalente al 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 el uso de la reflexión, Item puede implementar la interfaz Joinable . Si eso se implementó, Reindexer usa esto en lugar de la lenta implementación basada en la reflexión. Esto aumenta el rendimiento general en un 10-20%y reduce la cantidad de asignaciones.
// 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 ))
}
}
}Se podría aplicar una condición al resultado de otra consulta (subconsulta) incluida en la consulta actual. La condición puede estar en filas resultantes de la subconsulta:
query := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 ), reindexer . GE , 100 )o entre un campo del espacio de nombres de la consulta principal y el resultado de la subconsulta:
query := db . Query ( "main_ns" ).
Where ( "id" , reindexer . EQ , db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 )) El resultado de la subconsulta puede ser un cierto campo apuntado por el método Select (en este caso debe establecer el 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 )) o recuento de elementos satisfactorios para la subconsulta requerida por los métodos ReqTotal o 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 ())o agregación:
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" )) Las agregaciones Min , Max , Avg , Sum , Count y CountCached están permitidas solo. Subquery no puede contener múltiples agregaciones al mismo tiempo.
La subconsulta se puede aplicar al mismo espacio de nombres o al otro.
La subconsulta no puede contener otra subconsulta, unirse o fusionarse.
Si desea verificar si al menos uno de los elementos es satisfactorio para las subconsules, puede usar ANY condición o 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 )Un documento puede tener múltiples campos como clave primaria. Para habilitar esta característica, agregue el índice compuesto a la estructura. El índice compuesto es un índice que involucra múltiples campos, se puede usar en lugar de varios í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"`
}O
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"`
}También los índices compuestos son útiles para clasificar los resultados de múltiples 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 hacer consulta al índice compuesto, pase [] interfaz {} a la función .WhereComposite
// Get results where rating == 5 and year == 2010
query := db . Query ( "items" ). WhereComposite ( "rating+year" , reindexer . EQ ,[] interface {}{ 5 , 2010 }) Todos los campos en el índice compuesto regular (no completado) deben indexarse. Es decir, para poder crear rating+year , es necesario crear algún tipo de índices tanto para raiting como para year primero:
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"`
}Reindexer permite recuperar resultados agregados. Actualmente se respaldan el recuento, countCached, promedio, suma, mínimo, máximo, faceta y distintas.
Count : obtenga un número total de documentos que cumplan con las condiciones de la consultaCountCached : obtenga un número total de documentos que cumplan con las condiciones de la consulta. El valor del resultado será almacenado en caché y puede ser reutilizado por las otras consultas con agregación CountCachedAggregateMax - Obtener el valor máximo de campoAggregateMin : obtenga un valor de campo mínimoAggregateSum - Obtener valor de campo de sumaAggregateAvg - Obtener valor de campo promedioAggregateFacet - Obtener el valor de la faceta de los camposDistinct - Obtenga una lista de valores únicos del campo Para respaldar Query agregación, Query tiene métodos AggregateAvg , AggregateSum , AggregateMin , AggregateMax , AggregateFacet y Distinct . La faceta de agregación es aplicable a múltiples columnas de datos y el resultado de eso podría ser ordenado por cualquier columna de datos o 'contar' y cortar por desplazamiento y límite. Para respaldar este método de funcionalidad, AggregateFacet devuelve AggregationFacetRequest que tiene métodos Sort , Limit y Offset .
Las consultas con fusión aplicarán agregaciones de la consulta principal a todas las subconsules fusionadas. Las subconsules no pueden tener sus propias agregaciones. Las agregaciones disponibles para las costas de fusión son: Count, CountCached, Sum, Min y Max.
Para obtener resultados de agregación, Iterator tiene AggResults de método: está disponible después de la ejecución de la consulta y devuelve los resultados.
Código de ejemplo para items agregados por price y 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 )
} La clasificación de los campos de FACET agregados tiene una sintaxis distinta en su versión SQL:
SELECT FACET(name, price ORDER BY " name " ASC , " count " DESC ) FROM itemsReindexer permite buscar datos en los campos de matriz cuando los valores coincidentes tienen las mismas posiciones de índices. Por ejemplo, tenemos una variedad de estructuras:
type Elem struct {
F1 int `reindex:"f1"`
F2 int `reindex:"f2"`
}
type A struct {
Elems [] Elem
}Intento común de buscar valores en esta matriz
db . Query ( "Namespace" ). Where ( "f1" , EQ , 1 ). Where ( "f2" , EQ , 2 ) Encuentra todos los elementos de la matriz Elem[] donde f1 es igual a 1 y f2 es igual a 2.
La función EqualPosition permite buscar en campos de matriz con índices iguales. Consultas como esta:
db . Query ( "Namespace" ). Where ( "f1" , reindexer . GE , 5 ). Where ( "f2" , reindexer . EQ , 100 ). EqualPosition ( "f1" , "f2" )o
SELECT * FROM Namespace WHERE f1 >= 5 AND f2 = 100 EQUAL_POSITION(f1,f2); Encontrará todos los elementos de la matriz Elem[] con índices de matriz equal donde f1 es mayor o igual a 5 y f2 es igual a 100 (por ejemplo, la consulta devolvió 5 elementos donde solo los 3º elementos de ambas matrices tienen valores apropiados).
Con expresiones complejas (expresiones con soportes) igual_position () podría estar dentro de un soporte:
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 no funciona con las siguientes condiciones: es nulo, está vacío e IN (con una lista de parámetros vacías).
Hay funciones atómicas que se ejecutan bajo el bloqueo del espacio de nombres y, por lo tanto, garantiza la consistencia de los datos:
Estas funciones se pueden pasar a Upsert/Insert/Update en 3-RD y próximos argumentos.
Si se proporcionan estas funciones, el elemento aprobado por referencia se cambiará a un valor actualizado
// 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()" )La expiración de datos es útil para algunas clases de información, incluidos los datos de eventos generados por máquina, registros e información de sesión que solo necesitan persistir durante un período de tiempo limitado.
Reindexer hace posible establecer TTL (Time To Live) para los elementos del espacio de nombres. Agregar TTLindex al espacio de nombres elimina automáticamente los elementos después de un número especificado de segundos.
Los índices TTL funcionan solo con los campos INT64 y almacenan datos de marca de tiempo UNIX. Los elementos que contienen el índice TTL expiran después de expire_after segundos. Ejemplo de declarar ttlindex en 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 ()En este caso, los elementos del espacio de nombres de nombres de nombres expiran en 3600 segundos después de NamespaceExample.date Field Value (que es una marca de tiempo UNIX).
Un índice TTL admite consultas de la misma manera que lo hacen los índices no TTL.
Si los datos de origen están disponibles en formato JSON, entonces es posible mejorar el rendimiento de las operaciones UPSERT/Eliminar pasando directamente JSON a Reindexer. La deserialización JSON se realizará mediante el código C ++, sin asignaciones/deserialización adicionales en el código GO.
Las funciones de Upsert o Eliminar pueden procesar JSON simplemente pasando [] el argumento de byte con JSON
json := [] byte ( `{"id":1,"name":"test"}` )
db . Upsert ( "items" , json )Es simplemente un equivalente más rápido de:
item := & Item {}
json . Unmarshal ([] byte ( `{"id":1,"name":"test"}` ), item )
db . Upsert ( "items" , item )En caso de requisito para serializar los resultados de la consulta en formato JSON, entonces es posible mejorar el rendimiento al obtener directamente los resultados en formato JSON de Reindexer. La serialización JSON se realizará mediante el código C ++, sin asignaciones/serialización adicionales en el 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 condiciones de carrera, por defecto, el caché de objeto se apaga y todos los objetos se asignan y se deserializan del formato interno del reindexero (llamado CJSON ) por cada consulta. La deserialización es utiliza la reflexión, por lo que su velocidad no es óptima (de hecho, la deserialización CJSON es ~ 3-10x más rápido que JSON , y ~ 1.2x más rápido que GOB ), pero el rendimiento aún está seriamente limitado por la reflexión sobre la sobrecarga.
Hay 2 formas de habilitar el caché de objetos:
Si el objeto implementa la interfaz DeepCopy, entonces Reindexer encenderá el caché de objetos y usará la interfaz DeepCopy para copiar objetos de caché para consultar los resultados. La interfaz DeepCopy es responsable de hacer una copia profunda del objeto de origen.
Aquí hay una muestra de implementación de la interfaz 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 las consultas y no asignar nuevos objetos por cada consulta, es posible solicitar la consulta de devolución de objetos directamente desde el caché de objeto. Para habilitar este comportamiento, llame AllowUnsafe(true) en Iterator .
ADVERTENCIA: cuando se usa las consultas de AllowUnsafe(true) devuelve punteros compartidos a estructuras en caché de objetos. Por lo tanto, la aplicación no debe modificar los objetos devueltos.
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 defecto, el tamaño máximo del caché de objeto es de 256000 elementos para cada espacio de nombres. Para cambiar el tamaño máximo, use el método ObjCacheSize de NamespaceOptions , pasados a OpenNamespace. p.ej
// Set object cache limit to 4096 items
db . OpenNamespace ( "items_with_huge_cache" , reindexer . DefaultNamespaceOptions (). ObjCacheSize ( 4096 ), Item {})! Este caché no debe usarse para los espacios de nombres, que se replicaron desde los otros nodos: puede ser inconsistente para los espacios de nombres de esas réplicas.
El único tipo de datos de geometría compatible es el punto 2D, que se implementó en Golang como [2]float64 ( reindexer.Point ).
En SQL, se puede crear un punto como ST_GeomFromText('point(1 -3)') .
La única solicitud compatible de campo de geometría es encontrar todos los puntos a una distancia desde un punto. DWithin(field_name, point, distance) como en el ejemplo a continuación.
La función SQL correspondiente es ST_DWithin(field_name, point, distance) .
El índice RTree se puede crear para puntos. Para hacerlo, se deben declarar rtree y las etiquetas linear , quadratic , greene o rstar . linear , quadratic , greene o rstar significa qué algoritmo de construcción de Rtree se utilizaría. Aquí los algoritmos se enumeran en orden de optimizado para la inserción a optimizado para la búsqueda. Pero depende de los datos. Prueba que sea más apropiado para usted. El algoritmo predeterminado es 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 ); Reindexer Logger se puede activar mediante el método db.SetLogger() , al igual que en este fragmento de código:
type Logger struct {
}
func ( Logger ) Printf ( level int , format string , msg ... interface {}) {
log . Printf ( format , msg ... )
}
...
db . SetLogger ( Logger {}) Reindexer admite el registro de acciones lentas. Se puede configurar a través de la sección profiling.long_queries_logging del espacio de nombres del sistema #config . Se puede configurar el registro de las siguientes acciones:
Seleccione consultas:
threshold_us (integer) : el valor umbral (en microsegundos) para la ejecución de la consulta SELECT. Si se excede, se realizará una entrada de registro de núcleo, si el registro de threshold_us es -1 está deshabilitado.normalized (boolean) : genere la consulta en forma normalizada.Actualizar y eliminar consultas:
threshold_us (integer) : el valor umbral (en microsegundos) para la ejecución de la actualización o la consulta de eliminación. Si se excede, se realizará una entrada de registro de núcleo, si el registro de threshold_us es -1 está deshabilitado.normalized (boolean) : genere la consulta en forma normalizada.Actas:
threshold_us (integer) : valor umbral (en microsegundos) para el tiempo de confirmación de transacciones totales, si threshold_us es -1 registro por el tiempo de confirmación de transacción total está deshabilitado.avg_step_threshold_us (integer) : valor umbral (en microsegundos) para el tiempo de duración de paso promedio en la transacción. Si avg_step_threshold_us es -1 registro por la duración del paso de la transacción promedio está deshabilitado.Otra característica útil es la impresión de depuración de consultas procesadas. Para depurar los detalles de las consultas de impresión Hay 2 métodos:
db.SetDefaultQueryDebug(namespace string,level int) : globalmente habilita los detalles de impresión de todas las consultas por espacio de nombres
query.Debug(level int) : los detalles de impresión del level de ejecución de la consulta son nivel de verbosidad:
reindexer.INFO - imprimirá solo las condiciones de consulta
reindexer.TRACE : imprimirá las condiciones de consulta y los detalles de ejecución con los horarios
query.Explain () : calcule y almacene detalles de ejecución de consultas.
iterator.GetExplainResults () - Detalles de ejecución de consultas de retorno
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/