
Reindexer est une base de données orientée documentaire intégrée, en mémoire, avec une interface de générateur de requête de haut niveau.
L'objectif de Reindexer est de fournir une recherche rapide avec des requêtes complexes. Chez Reream, nous n'étions pas satisfaits d'Elasticsearch et avons créé Reindexer comme une alternative plus performante.
Le noyau est écrit en C ++ et l'API du niveau d'application est en Go.
Ce document décrit GO Connecteur et son API. Pour obtenir des informations sur Reindexer Server et API HTTP, reportez-vous à la documentation Reindexer
Il y a deux versions LTS de Reindexer disponibles: V3.xx et V4.xx
3.xx est actuellement notre branche traditionnelle et 4.xx (branche / 4 branche) est une version bêta avec un cluster de radeau expérimental et un support de fragmentation. Les stockages sont compatibles entre ces versions, cependant, les configurations de réplication sont totalement différentes. Les versions 3 et 4 obtiennent toutes les mêmes fiducies et fonctionnalités (sauf celles liées à la réplication).
Caractéristiques clés:
Les performances ont été notre priorité absolue dès le début, et nous pensons que nous avons réussi à l'obtenir assez bien. Les repères montrent que les performances de Reindexer sont à égalité avec une base de données de valeur clé typique. Sur un seul noyau de processeur, nous obtenons:
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 ...Voir les résultats d'analyse comparative et plus de détails dans le repo d'analyse comparative
Reindexer vise à consommer le moins de mémoire possible; La plupart des requêtes sont traitées sans aucune allocation de mémoire.
Pour y parvenir, plusieurs optimisations sont utilisées, à la fois au niveau C ++ et GO:
Les documents et les indices sont stockés dans des structures binaires denses C ++, afin qu'elles n'imposent aucune charge au collecteur des ordures de Go.
Les doublons de chaîne sont fusionnés.
Les frais généraux de mémoire sont d'environ 32 octets par document + ≈4-16 octets par chaque index de recherche.
Il y a un cache d'objet au niveau GO pour les documents désérialisés produits après l'exécution de la requête. Les requêtes futures utilisent des documents présérialisés, ce qui réduit les coûts de désérialisation et d'allocation répétés
L'interface de requête utilise sync.Pool pour réutiliser les structures et tampons internes. La combinaison de ces technologies permet à Reindexer de gérer la plupart des requêtes sans aucune allocation.
Reindexer a un moteur de recherche de texte intégral interne. La documentation d'utilisation de la recherche en texte intégral et des exemples sont ici
Reindexer peut stocker des documents et charger des documents du disque via le niveaudb. Les documents sont écrits sur le backend de stockage de manière asynchrone par de gros lots automatiquement en arrière-plan.
Lorsqu'un espace de noms est créé, tous ses documents sont stockés en RAM, de sorte que les requêtes sur ces documents fonctionnent entièrement en mode en mémoire.
Voici un exemple complet de l'utilisation de base du réindexer:
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 )
}
}Il existe également quelques échantillons de base pour C ++ et allez ici
Comme alternative à Query Builder Reindexer fournit une interface de requête compatible SQL. Voici un échantillon d'utilisation de l'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" )
...Veuillez noter que l'interface de création de requête est préférée: elle a plus de fonctionnalités et est plus rapide que l'interface SQL
Les littéraux de cordes doivent être enfermés en citations uniques.
Les index composites doivent être enfermés en doubles guillemets.
SELECT * FROM items WHERE " field1+field2 " = ' Vasya 'Si le nom de champ ne commence pas par alpha, '_' ou '#' il doit être enfermé en doubles citations, exemples:
UPDATE items DROP " 123 " SELECT * FROM ns WHERE " 123 " = ' some_value ' SELECT * FROM ns WHERE " 123abc " = 123 DELETE FROM ns WHERE " 123abc123 " = 111Les jointures simples peuvent être effectuées via la syntaxe SQL par défaut:
SELECT * FROM ns INNER JOIN ns2 ON ns2 . id = ns . fk_id WHERE a > 0Les joints avec l'état de l'espace de noms de gauche doivent utiliser une syntaxe de type sous-requête:
SELECT * FROM ns WHERE a > 0 AND INNER JOIN ( SELECT * FROM ns2 WHERE b > 10 AND c = 1 ) ON ns2 . id = ns . fk_idLa sous-requête peut également faire partie de la condition par rapport à la 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 peut fonctionner en 3 modes différents:
embedded (builtin) est intégré à l'application en tant que bibliothèque statique et ne nécessite pas de processus de serveur séparé.embedded with server (builtinserver) est intégré à l'application en tant que bibliothèque statique et au serveur de démarrage. Dans ce mode, d'autres clients peuvent se connecter à l'application via CProto, UCProto ou HTTP.standalone s'exécute en tant que serveur autonome, l'application se connecte à Reindexer via des sockets de domaine réseau ou UNIX.Dans ce mode, la contrainte de reindexer ne dépend pas de la bibliothèque statique de Reindexer.
Le moyen le plus simple d'obtenir le serveur Reindexer, tire et exécute l'image Docker à partir de dockerhub.
docker run -p9088:9088 -p6534:6534 -it reindexer/reindexerDockerfile
Le noyau de Reindexer est écrit en C ++ 17 et utilise le niveaudb comme backend de stockage, de sorte que le CMake, la chaîne d'outils C ++ 17 et le niveaudb doivent être installés avant d'installer Reindexer.
Pour construire Reindexer, G ++ 8+, Clang 7+ ou MingW64 est requis.
Dans ces modes, la liaison au reindexer dépend des bibliothèques statiques de Reindexer (noyau, serveur et ressource).
Cette façon est recommandée et s'adaptera au plus grand nombre de scénarios.
Les modules GO avec go.mod ne permettent pas de créer des bibliothèques C ++ dans les répertoires des modules. La liaison au go utilisera PKG-Config pour détecter les répertoires des bibliothèques.
Les bibliothèques de Reindexer doivent être installées à partir de sources ou à partir du package prédéfini via Package Manager.
Puis obtenez le module:
go get -a github.com/restream/reindexer/v3 Si vous avez besoin de sources de Reindexer modifiées, vous pouvez utiliser replace comme ça.
# 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/reindexerDans ce cas, la liaison au go génèrera la liste explicite des bibliothèques et des chemins et n'utilisera pas PKG-Config.
Si vous n'utilisez pas Go.mod, il est possible d'obtenir et de construire Reindexer à partir de sources de cette façon:
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 ne prend pas en charge le fournisseur approprié pour le code CGO (Golang / Go # 26366), cependant, il est possible d'utiliser Vend pour copier les sources de Reindexer dans le vendeur-directeur du fournisseur.
Avec vend , vous pourrez appeler go generate -mod=vendor for builtin et builtinserver , placé dans votre fournisseur-répertoire.
Il est également possible de copier simplement copier les sources de Reindexer dans le projet Youth, en utilisant git clone .
Dans ces cas, toutes les dépendances de Go.mod de Reindexer doivent être installées manuellement avec des versions appropriées.
En interne, les structures sont divisées en deux parties:
reindex Les requêtes ne sont possibles que sur les champs indexés, marqués de la balise reindex . La balise reindex contient le nom d'index, le type et les options supplémentaires:
reindex:"<name>[[,<type>],<opts>]"
name - Nom d'index.type - Type d'index:hash - Sélection rapide par Eq et Set Match. Utilisé par défaut. Permet le tri lent et inefficace par champ.tree - Sélectionnez rapidement par plage, GT et LT Matches. Un peu plus lent pour les correspondances EQ et définies que l'indice hash . Permet des résultats de tri rapide par champ.text - Index de recherche de texte complet. Les détails d'utilisation de la recherche en texte intégral sont décrits ici- - Index de colonne. Impossible d'effectuer une sélection rapide car il est implémenté avec une technique complète. A la plus petite mémoire de mémoire.ttl - TTL qui fonctionne uniquement avec les champs INT64. Ces index sont tout à fait pratiques pour la représentation des champs de date (stockés sous forme d'horodatage UNIX) qui expirent après un montant spécifié de secondes.rtree - Disponible uniquement Dwithin Match. Acceptable uniquement pour [2]float64 (ou reindexer.Point ) Type de champ. Pour plus de détails, voir la sous-section de la géométrie.opts - Options d'index supplémentaires:pk - Field fait partie d'une clé primaire. La structure doit avoir au moins 1 champ marqué avec pkcomposite - Créez un index composite. Le type de champ doit être une structure vide: struct{} .joined - Field est un destinataire de JOIN. Le type de champ doit être []*SubitemType .dense - Réduisez la taille de l'index. Pour hash et tree , il économisera 8 octets par valeur clé unique. Pour - il économisera 4 à 8 octets par chaque élément. Utile pour les index avec une sélectivité élevée, mais pour les indices tree et hash avec une faible sélectivité peut sérieusement réduire les performances de mise à jour. dense ralentira également de larges requêtes Fulscan sur - index, en raison du manque d'optimisation du cache du CPU.sparse - Row (Document) contient une valeur d'index clairsemé uniquement dans le cas où il est défini à des fins - il n'y a pas d'enregistrements vides (ou par défaut) de ce type d'index dans la ligne (document). Il permet d'économiser RAM, mais cela vous coûtera les performances - il fonctionne un peu plus lent que les index réguliers.collate_numeric - Créer un index de chaîne qui fournit des valeurs d'ordre dans la séquence numérique. Le type de champ doit être une chaîne.collate_ascii - CRÉER L'indice de chaîne insensible à la cas fonctionne avec ASCII. Le type de champ doit être une chaîne.collate_utf8 - CRÉER L'indice de chaîne insensible à la cas fonctionne avec UTF8. Le type de champ doit être une chaîne.collate_custom=<ORDER> - Créer un index de chaîne d'ordre personnalisé. Le type de champ doit être une chaîne. <ORDER> est une séquence de lettres, qui définit l'ordre de tri.linear , quadratic , greene ou rstar - Spécifiez l'algorithme pour la construction de l'index rtree (par défaut rstar ). Pour plus de détails, voir la sous-section de la géométrie.uuid - Stockez cette valeur comme uuid. Ceci est beaucoup plus efficace du point de vue de la consommation RAM / réseau pour les UUID que les chaînes. Seuls les types hash et - d'index sont pris en charge pour les Uuids. Peut être utilisé avec n'importe quelle variante UUID, sauf variante 0 Les champs avec des indices réguliers ne sont pas nullables. La condition is NULL n'est prise en charge que par des indices sparse et array .
Par défaut, Reindexer analyse toutes les structures imbriquées et ajoute leurs champs à l'espace de noms (ainsi que les index spécifiés). Pendant les index scanner privés (champs non conformés), champs marqués avec reindex:"-" et champs marqués avec json:"-" sera ignoré.
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 peut trier les documents par champs (y compris les champs et les champs des espaces de noms joints) ou par des expressions dans l'ordre croissant ou descendant.
Pour trier par champs non index, toutes les valeurs doivent être convertibles les unes aux autres, c'est-à-dire avoir les mêmes types ou être l'un des types numériques ( bool , int , int64 ou float ).
Trier les expressions peuvent contenir:
bool , int , int64 , float ou string . Toutes les valeurs doivent être convertibles en nombres ignorant les espaces de leaders et de finition;rank() , abs() et ST_Distance() ;+ , - (unary et binaire), * et / . Si le nom de champ suivi de + ils doivent être séparés par l'espace pour distinguer le nom de l'index composite. Les champs des espaces de noms joints doivent être écrits comme ceci: joined_namespace.field .
Abs() signifie la valeur absolue d'un argument.
Rank() signifie Rank of Match de correspondant et ne s'applique que dans la requête FullText.
ST_Distance() signifie la distance entre les points de géométrie (voir la sous-section géométrique). Les points peuvent être des colonnes dans les espaces de noms actuels ou joints ou le point fixe au format ST_GeomFromText('point(1 -3)')
Dans SQL Query, l'expression de l'expression doit être citée.
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" )Il est également possible de définir une commande de tri personnalisée comme celle-ci
type SortModeCustomItem struct {
ID int `reindex:"id,,pk"`
InsItem string `reindex:"item_custom,hash,collate_custom=a-zA-Z0-9"`
}ou comme ça
type SortModeCustomItem struct {
ID int `reindex:"id,,pk"`
InsItem string `reindex:"item_custom,hash,collate_custom=АаБбВвГгДдЕеЖжЗзИиКкЛлМмНнОоПпРрСсТтУуФфХхЦцЧчШшЩщЪъЫыЬьЭ-ЯAaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0-9ЁёЙйэ-я"`
}Le tout premier personnage de cette liste a la priorité la plus élevée, la priorité du dernier personnage est la plus petite. Cela signifie que l'algorithme de tri mettra des éléments qui commencent par le premier caractère avant les autres. Si certains personnages sont ignorés, leurs priorités auraient leurs valeurs habituelles (selon les personnages de la liste).
Pour la recherche simple de recherche de texte dans les champs de chaînes, LIKE condition peut être utilisée. Il cherche des chaînes correspondant au modèle. Dans le modèle _ signifie tout char et % désigne toute séquence de caractères.
GO EXEMPLE:
query := db . Query ( "items" ).
Where ( "field" , reindexer . LIKE , "pattern" )Exemple SQL:
SELECT * FROM items WHERE fields LIKE ' pattern '`` Me_T '' correspond à «Meet», «Meat», «fondre» et ainsi de suite «% tion» correspond à «tion», «condition», «création» et ainsi de suite
ATTENTION : Condition comme utilise la méthode de numérisation. Il peut être utilisé à des fins de débogage ou dans les requêtes avec d'autres bonnes conditions sélectives.
Généralement pour la recherche de texte intégral à une vitesse raisonnable, nous vous recommandons d'utiliser l'index FullText.
Les requêtes de mise à jour sont utilisées pour modifier les éléments existants d'un espace de noms. Il existe plusieurs types de requêtes de mise à jour: mise à jour des champs existants, ajoutant de nouveaux champs et supprimant les champs non indexés existants.
Mettre à jour SQL-syntax
UPDATE nsName
SET field1 = value1, field2 = value2, ..
WHERE condition;Il est également possible d'utiliser des expressions arithmétiques avec +, -, /, * et des supports
UPDATE NS SET field1 = field2 + field3 - (field4 + 5 ) / 2 y compris des fonctions comme now() , sec() et serial() . Pour utiliser les expressions à partir de la méthode Golang Code SetExpression() doit être appelée au lieu de Set() .
Pour rendre un champ de tableau vide
UPDATE NS SET arrayfield = [] WHERE id = 100et le régler sur null
UPDATE NS SET field = NULL WHERE id > 100En cas de champs non indexés, la définition de sa valeur sur une valeur d'un type différent le remplacera complètement; En cas de champs indexés, il est seulement possible de le convertir du type adjacent (types intégraux et bool), des chaînes numériques (comme "123456") en types intégraux et en retour. Définition du champ indexé sur NULL le réinitialise à une valeur par défaut.
Il est possible d'ajouter de nouveaux champs aux éléments existants
UPDATE NS SET newField = ' Brand new! ' WHERE id > 100et même ajouter un nouveau champ par un chemin imbriqué complexe comme celui-ci
UPDATE NS SET nested . nested2 . nested3 . nested4 .newField = ' new nested field! ' WHERE id > 100Créera les objets imbriqués suivants: imbriqués, imbriqués2, imbriqués3, imbriqués4 et newfield en tant que membre de l'objet Nested4.
Exemple d'utilisation des requêtes de mise à jour dans le code de Golang:
db . Query ( "items" ). Where ( "id" , reindexer . EQ , 40 ). Set ( "field1" , values ). Update ()Reindexer permet de mettre à jour et d'ajouter des champs d'objets. L'objet peut être défini par une structure, une carte ou un tableau d'octets (c'est-à-dire une version JSON de la représentation d'objet).
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 () Dans ce cas, Map dans Golang ne peut fonctionner qu'avec String comme clé. map[string]interface{} est un choix parfait.
Mise à jour du champ d'objet par instruction 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 ;Mettre à jour SQL-Syntax des requêtes qui suppriment les champs non indexés existants:
UPDATE nsName
DROP field1, field2, ..
WHERE condition; db . Query ( "items" ). Where ( "id" , reindexer . EQ , 40 ). Drop ( "field1" ). Update ()Le mécanisme de mise à jour de Reindexer permet de modifier les champs de tableau: pour modifier un certain élément d'un tableau existant ou même pour remplacer un champ entier.
Pour mettre à jour un élément, une syntaxe de l'opérateur d'abonnement est utilisée:
UPDATE NS SET array[ * ].prices[ 0 ] = 9999 WHERE id = 5 où * signifie tous les articles.
Pour mettre à jour le tableau entier, ce qui suit est utilisé:
UPDATE NS SET prices = [ 999 , 1999 , 2999 ] WHERE id = 9Tout champ non indexé peut être facilement converti en tableau à l'aide de cette syntaxe.
Reindexer permet également de mettre à jour les éléments des tableaux d'objets:
UPDATE NS SET extra . objects [ 0 ] = { " Id " : 0 , " Description " : " Updated! " } WHERE id = 9Aussi comme ça
db . Query ( "clients" ). Where ( "id" , reindexer . EQ , 100 ). SetObject ( "extra.objects[0]" , updatedValue ). Update ()Reindexer prend en charge les tableaux hétérogènes:
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 ()Les champs de tableau d'index prennent en charge les valeurs qui peuvent être converties en un type d'index uniquement. Lorsqu'elles sont enregistrées, ces valeurs peuvent modifier la précision due à la conversion.
UPDATE NS SET prices_idx = [ 11 , ' 2 ' , 3 ]Pour supprimer l'élément par index, vous devez effectuer ce qui suit:
UPDATE NS DROP array[ 5 ]Pour ajouter des éléments à un tableau existant, la syntaxe suivante est prise en charge:
UPDATE NS SET integer_array = integer_array || [ 5 , 6 , 7 , 8 ]et
UPDATE NS SET integer_array = [ 1 , 2 , 3 , 4 , 5 ] || integer_array Le premier ajoute des éléments à la fin d' integer_array , le second ajoute 5 éléments à l'avant. Pour que ce code fonctionne dans Golang SetExpression() doit être utilisé à la place de Set() .
Pour supprimer les éléments par valeur dans un tableau existant, la syntaxe suivante est prise en charge:
UPDATE NS SET integer_array = array_remove(integer_array, [ 5 , 6 , 7 , 8 ])et
UPDATE NS SET integer_array = array_remove_once(integer_array, [ 5 , 6 , 7 , 8 , 6 ]) Le premier supprime toutes les occurrences des valeurs répertoriées dans integer_array , la seconde supprime uniquement la première occurrence trouvée. Pour que ce code fonctionne dans Golang SetExpression() doit être utilisé à la place de Set() . Si vous devez supprimer une valeur, vous pouvez utiliser des crochets [5] ou une valeur simple 5 .
UPDATE NS SET integer_array = array_remove(integer_array, [ 5 ]) update ns set integer_array = array_remove(integer_array, 5 )La commande de supprimer peut être combinée avec un tableau de tableau:
UPDATE NS SET integer_array = array_remove_once(integer_array, [ 5 , 6 , 7 , 8 ]) || [ 1 , 2 , 3 ]Aussi comme ça
db . Query ( "main_ns" ). SetExpression ( "integer_array" , "array_remove(integer_array, [5,6,7,8]) || [1,2,3]" ). Update ()Il est possible de supprimer les valeurs du deuxième champ des valeurs du premier champ. Et ajouter également de nouvelles valeurs, etc. Remarque: Le premier paramètre dans les commandes devrait être un tableau / champ de champ, le deuxième paramètre peut être un tableau / scalaire / champ de champ / champ de champ. Pour les valeurs Compatibilité / convertibilité requise
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 prend en charge les transactions. La transaction est effectuée à la mise à jour de l'espace de noms atomiques. Il y a une transaction synchrone et asynchrone disponible. Pour démarrer la méthode de transaction, db.BeginTx() est utilisé. Cette méthode crée un objet de transaction, qui fournit une interface de mise à jour / upsert / insert / supprimer habituelle pour l'application. Pour les clients RPC, il existe une limitation du nombre de transactions - chaque connexion ne peut pas avoir plus de 1024 transactions ouvertes en même temps.
// 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 )
}Pour une accélération de l'insertion des enregistrements en vrac, le mode asynchrone peut être utilisé.
// 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 )
} Le deuxième argument de UpsertAsync est la fonction de complétion, qui sera appelée après avoir reçu la réponse du serveur. De plus, si une erreur s'est produite pendant le processus de préparation, tx.Commit doit renvoyer une erreur. Il suffit donc de vérifier l'erreur renvoyée par tx.Commit - pour être sûr, que toutes les données ont été engagées avec succès ou non.
Selon le montant des modifications de transaction, il existe 2 stratégies de validation possibles:
La quantité de données pour la sélection d'une stratégie de validation peut être sélectionnée dans la configuration de l'espace de noms. Vérifiez les champs StartCopyPolicyTxSize , CopyPolicyMultiplier et TxSizeToAlwaysCopy dans struct DBNamespacesConfig (Describer.go)
Si la taille des transactions est inférieure à TxSizeToAlwaysCopy , Reindexer utilise une heuristique supplémentaire et essayant d'éviter la copie de l'espace de noms, s'il n'y avait pas de sélection de requêtes vues pour cet espace de noms. Dans certains cas, cette heuristique peut augmenter la latence de sélection, donc elle peut être désactivée en définissant la variable Env REINDEXER_NOTXHEURISTIC à n'importe quelle valeur non vide.
tx.Query("ns").Exec() ... ;Reindexer peut rejoindre des documents à partir de plusieurs espaces de noms en un seul résultat:
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 ()Dans cet exemple, Reindexer utilise la réflexion sous le capot pour créer une tranche d'acteur et copier l'acteur struct.
La requête de jointure peut avoir de une à plusieurs On des conditions liées à And (par défaut), Or Not des opérateurs:
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 combine les données de deux espaces de noms où il y a une correspondance sur les champs de jonction dans les deux espaces de noms. Un LeftJoin renvoie tous les éléments valides des espaces de noms sur le côté gauche du mot-clé LeftJoin , ainsi que les valeurs du tableau sur le côté droit, ou rien si un élément correspondant n'existe pas. Join est un alias pour LeftJoin .
InnerJoins peuvent être utilisés comme condition dans la clause 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 ) Notez que l'habitude Or l'opérateur implémente les courts-circuit pour les conditions Where les conditions: si la condition précédente est vraie, la suivante n'est pas évaluée. Mais en cas d' InnerJoin , cela fonctionne différemment: dans query1 (à partir de l'exemple ci-dessus), les deux conditions InnerJoin sont évaluées malgré le résultat du WhereInt . Limit(0) Dans le cadre de InnerJoin ( query3 de l'exemple ci-dessus) ne rejoint aucune donnée - il fonctionne comme un filtre uniquement pour vérifier les conditions.
Reindexer ne prend pas en charge ANTI JOIN SQL Construction, cependant, il prend en charge les opérations logiques avec les jointures. En fait, NOT (INNER JOIN ...) est totalement équivalent à l' 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
) Pour éviter d'utiliser la réflexion, Item peut implémenter l'interface Joinable . Si cela implémenté, Reindexer l'utilise au lieu de l'implémentation basée sur la réflexion lente. Cela augmente les performances globales de 10 à 20% et réduit la quantité d'allocations.
// 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 ))
}
}
}Une condition pourrait être appliquée pour résulter d'une autre requête (sous-requête) incluse dans la requête actuelle. La condition peut être sur des lignes résultant de la sous-requête:
query := db . Query ( "main_ns" ).
WhereQuery ( db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 ), reindexer . GE , 100 )ou entre un champ de l'espace de noms de la requête principale et le résultat de la sous-requête:
query := db . Query ( "main_ns" ).
Where ( "id" , reindexer . EQ , db . Query ( "second_ns" ). Select ( "id" ). Where ( "age" , reindexer . GE , 18 )) Le résultat de la sous-requête peut être un certain champ pointé par la méthode Select (dans ce cas, il doit définir le filtre de champ unique):
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 le nombre d'articles satisfaisant à la sous-requête requise par les méthodes 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 agrégation:
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" )) Les agrégations Min , Max , Avg , Sum , Count et CountCached sont autorisées uniquement. La sous-requête ne peut contenir plusieurs agrégations en même temps.
La sous-requête peut être appliquée au même espace de noms ou à l'autre.
La sous-requête ne peut contenir une autre sous-requête, rejoindre ou fusionner.
Si vous souhaitez vérifier si au moins un des éléments est satisfaisant pour les sous-requêtes, vous pouvez utiliser ANY condition 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 )Un document peut avoir plusieurs champs comme clé primaire. Pour activer cette fonctionnalité, ajoutez un index composite à Struct. L'indice composite est un index qui implique plusieurs champs, il peut être utilisé à la place de plusieurs index distincts.
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"`
}Les index composites sont également utiles pour tri les résultats par plusieurs champs:
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 }) Pour faire une requête à l'index composite, passez [] interface {} à .WhereComposite Fonction of Query Builder:
// Get results where rating == 5 and year == 2010
query := db . Query ( "items" ). WhereComposite ( "rating+year" , reindexer . EQ ,[] interface {}{ 5 , 2010 }) Tous les champs de l'index composite régulier (non-fulText) doivent être indexés. C'est-à-dire pour pouvoir créer rating+year , il est nécessaire de créer une sorte d'index pour raiting et year d'abord:
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 permet de récupérer les résultats agrégés. Actuellement, compter, compter, moyen, somme, minimum, maximum, facette et des agrégations distinctes sont prises en charge.
Count - Obtenez le nombre total de documents qui remplissent les conditions de la questionCountCached - Obtenez le nombre total de documents qui remplissent les conditions de la question. La valeur des résultats sera mise en cache et peut être réutilisée par les autres requêtes avec une agrégation comptéeAggregateMax - obtenez une valeur de champ maximaleAggregateMin - Obtenez une valeur de champ minimumAggregateSum - obtenez la valeur du champ de sommeAggregateAvg - Obtenez la valeur moyenne du champAggregateFacet - Obtenez la valeur des champs des champsDistinct - Obtenez la liste des valeurs uniques du champ Afin de prendre en charge l'agrégation, Query a des méthodes AggregateAvg , AggregateSum , AggregateMin , AggregateMax , AggregateFacet et Distinct celles-ci doivent être appelées avant l'exécution Query : cela demandera à Reindexer de calculer les agrégations de données. La facette d'agrégation est applicable à plusieurs colonnes de données et le résultat pourrait être trié par n'importe quelle colonne de données ou `` compter 'et couper par décalage et limite. Afin de prendre en charge cette méthode de fonctionnalité, AggregateFacet renvoie AggregationFacetRequest qui a des méthodes Sort , Limit et Offset .
Les requêtes avec fusion appliqueront des agrégations de la requête principale à toutes les sous-requêtes fusionnées. Les sous-questionnaires ne peuvent pas avoir leurs propres agrégations. Les agrégations disponibles pour la fusion sont: Count, CountCached, Sum, Min et Max.
Pour obtenir des résultats d'agrégation, Iterator a AggResults la méthode: il est disponible après l'exécution de la requête et renvoie la tranche de résultats.
Exemple de code pour items agrégés par price et 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 )
} Le tri par les champs de FACET agrégés a une syntaxe distincte dans sa version SQL:
SELECT FACET(name, price ORDER BY " name " ASC , " count " DESC ) FROM itemsReindexer permet de rechercher des données dans les champs de tableau lorsque les valeurs de correspondance ont les mêmes positions d'index. Par exemple, nous avons un éventail de structures:
type Elem struct {
F1 int `reindex:"f1"`
F2 int `reindex:"f2"`
}
type A struct {
Elems [] Elem
}Tentative commune de rechercher des valeurs dans ce tableau
db . Query ( "Namespace" ). Where ( "f1" , EQ , 1 ). Where ( "f2" , EQ , 2 ) trouve tous les éléments du tableau Elem[] où f1 est égal à 1 et f2 est égal à 2.
La fonction EqualPosition permet de rechercher dans des champs de tableau avec des index égaux. Des questions comme ceci:
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); trouvera tous les éléments du tableau Elem[] avec des index de tableau equal où f1 est supérieur ou égal à 5 et f2 est égal à 100 (par exemple, la requête a renvoyé 5 éléments où seuls les 3e éléments des deux tableaux ont des valeurs appropriées).
Avec des expressions complexes (expressions avec des supports) Equal_Position () pourraient être dans un support:
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 ne fonctionne pas avec les conditions suivantes: est nul, est vide et dans (avec la liste des paramètres vides).
Il existe des fonctions atomiques, qui s'exécutent sous verrouillage de l'espace de noms, et garantisse donc la cohérence des données:
Ces fonctions peuvent être transmises à UPSERT / INSERT / UPDATE dans les arguments 3-RD et suivants.
Si ces fonctions sont fournies, l'élément de référence transmis sera modifié en valeur mise à jour
// 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()" )L'expiration des données est utile pour certaines classes d'informations, y compris les données d'événements générées par les machines, les journaux et les informations de session qui n'ont besoin de persister que pendant une période limitée.
Reindexer permet de définir TTL (temps de vivre) pour les éléments de l'espace de noms. L'ajout de TTLindex à l'espace de noms supprime automatiquement les éléments après un nombre spécifié de secondes.
Les index TTL fonctionnent uniquement avec les champs INT64 et stockent les données d'horodatage UNIX. Les éléments contenant l'index TTL Expire après expire_after secondes. Exemple de déclaration de TtLindex à 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 ()Dans ce cas, les éléments de l'espace de noms de noms d'espace exemple exemplaient en 3600 secondes après la valeur de champ de noms de noms de noms.date (qui est un horodatage UNIX).
Un index TTL prend en charge les requêtes de la même manière que les index non TTL font.
Si les données source sont disponibles au format JSON, il est possible d'améliorer les performances des opérations Upsert / Supprimer en passant directement JSON à Reindexer. La désérialisation JSON sera effectuée par code C ++, sans allocs / désérialisation supplémentaires dans le code Go.
Les fonctions upsert ou supprimer peuvent traiter JSON simplement en passant un argument d'octet avec JSON
json := [] byte ( `{"id":1,"name":"test"}` )
db . Upsert ( "items" , json )C'est juste un équivalent plus rapide de:
item := & Item {}
json . Unmarshal ([] byte ( `{"id":1,"name":"test"}` ), item )
db . Upsert ( "items" , item )En cas d'exigence de sérialisation des résultats de la requête au format JSON, il est possible d'améliorer les performances en obtenant directement des résultats au format JSON de Reindexer. La sérialisation JSON sera effectuée par le code C ++, sans allocs / sérialisation supplémentaires dans le code 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 ))
...Ce code imprimera quelque chose comme:
{ "root_object" : [{ "id" : 1 , "name" : " test " }] } Pour éviter les conditions de course, le cache d'objet par défaut est désactivé et tous les objets sont alloués et désérialisés à partir du format interne de Reindexer (appelé CJSON ) par chaque requête. La désérialisation utilise la réflexion, donc sa vitesse n'est pas optimale (en fait, la désérialisation CJSON est ~ 3-10x plus rapide que JSON , et ~ 1,2x plus rapide que GOB ), mais les performances sont toujours sérieusement limitées par les frais généraux de réflexion.
Il existe 2 façons d'activer le cache d'objet:
Si l'objet implémente l'interface Deepcopy, ReinIndexer activera le cache d'objet et utilise l'interface DeepCopy pour copier des objets du cache aux résultats de requête. L'interface Deepcopy est responsable de faire une copie profonde de l'objet source.
Voici un échantillon de la mise en œuvre de l'interface 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
} Pour accélérer les requêtes et n'allouez pas de nouveaux objets pour chaque requête, il est possible de demander des objets de retour de requête directement à partir du cache d'objet. Pour activer ce comportement, appelez AllowUnsafe(true) sur Iterator .
AVERTISSEMENT: Lorsqu'il est utilisé, les requêtes AllowUnsafe(true) renvoient des pointeurs partagés vers des structures dans le cache d'objet. Par conséquent, l'application ne doit pas modifier les objets renvoyés.
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"
} Par défaut, la taille maximale du cache d'objet est de 256000 éléments pour chaque espace de noms. Pour modifier la taille maximale, utilisez la méthode ObjCacheSize de NamespaceOptions , passée à OpenNamespace. par exemple
// Set object cache limit to 4096 items
db . OpenNamespace ( "items_with_huge_cache" , reindexer . DefaultNamespaceOptions (). ObjCacheSize ( 4096 ), Item {})! Ce cache ne doit pas être utilisé pour les espaces de noms, qui ont été reproduits à partir des autres nœuds: il peut être incohérent pour les espaces de noms de la réplique.
Le seul type de données de géométrie pris en charge est 2D Point, qui a implémenté dans Golang comme [2]float64 ( reindexer.Point ).
Dans SQL, un point peut être créé comme ST_GeomFromText('point(1 -3)') .
La seule demande prise en charge de champ de géométrie est de trouver tous les points à une distance à un point. DWithin(field_name, point, distance) comme sur l'exemple ci-dessous.
La fonction SQL correspondante est ST_DWithin(field_name, point, distance) .
L'indice Rtree peut être créé pour les points. Pour ce faire, les étiquettes rtree et linear , quadratic , greene ou rstar doivent être déclarées. linear , quadratic , greene ou rstar signifie quel algorithme de construction de Rtree serait utilisé. Ici, les algorithmes sont répertoriés dans l'ordre de l'optimisation pour l'insertion à optimisé pour la recherche. Mais cela dépend des données. Tester ce qui vous convient. L'algorithme par défaut est 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 ); Le journal de reindexer peut être activé par la méthode db.SetLogger() , tout comme dans cet extrait de code:
type Logger struct {
}
func ( Logger ) Printf ( level int , format string , msg ... interface {}) {
log . Printf ( format , msg ... )
}
...
db . SetLogger ( Logger {}) Reindexer prend en charge la journalisation des actions lentes. Il peut être configuré via profiling.long_queries_logging section de l'espace de noms du système #config . La journalisation des actions suivantes peut être configurée:
Sélectionnez les requêtes:
threshold_us (integer) : la valeur de seuil (en microsecondes) pour l'exécution de la requête sélectionnée. Si elle est dépassée, une entrée de core-log sera effectuée, si threshold_us est -1 Logging est désactivé.normalized (boolean) : Sortez la requête sous une forme normalisée.Mettre à jour et supprimer les requêtes:
threshold_us (integer) : la valeur de seuil (en microsecondes) pour l'exécution de la mise à jour ou de la question de la requête. Si elle est dépassée, une entrée de core-log sera effectuée, si threshold_us est -1 Logging est désactivé.normalized (boolean) : Sortez la requête sous une forme normalisée.Transactions:
threshold_us (integer) : valeur de seuil (en microsecondes) pour le temps de validation total de transaction, si threshold_us est -1 journalisation par le délai total de transaction est désactivé.avg_step_threshold_us (integer) : valeur de seuil (en microsecondes) pour la durée moyenne de la durée de la transaction. Si avg_step_threshold_us est -1 La journalisation par la durée de la transaction moyenne est désactivée.Une autre fonctionnalité utile est l'impression de débogage des requêtes traitées. Pour déboguer les détails des requêtes d'impression, il existe 2 méthodes:
db.SetDefaultQueryDebug(namespace string,level int) - Il permet à Globalement les détails d'impression de toutes les requêtes par espace de noms
query.Debug(level int) - Les détails d'impression du level d'exécution de la requête sont le niveau de verbosité:
reindexer.INFO - imprimera uniquement les conditions de requête
reindexer.TRACE - imprimera les conditions de requête et les détails d'exécution avec des horaires
query.Explain () - Calculer et stocker les détails de l'exécution de la requête.
iterator.GetExplainResults () - Retour des détails d'exécution de la requête
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/