ElasticSearch
Official Website: Elasticsearch: Official Distributed Search and Analytics Engine | Elastic
Elasticsearch is a distributed, RESTful-style search and data analytics engine capable of addressing various emerging use cases. As the core of the Elastic Stack, Elasticsearch centrally stores your data, enabling you to perform lightning-fast searches, fine-tune relevance, conduct powerful analytics, and easily scale. This article explains how to use Go to perform basic operations on Elasticsearch, such as CRUD operations. If you are not familiar with Elasticsearch, please learn it first.
Dependencies
Download the official library:
$ github.com/elastic/go-elasticsearch/v7If you are using ES8, use a different version:
$ github.com/elastic/go-elasticsearch/v8TIP
This article uses ES8 for demonstration.
Connection
Use the function elasticsearch.NewClient to establish a new connection:
func NewClient(cfg Config) (*Client, error)ES8+ defaults to HTTPS connection. When establishing an HTTPS connection, you can use either a CA certificate or a CA fingerprint, both of which are generated on the Elasticsearch server side. An example is as follows:
client, err := elasticsearch.NewClient(elasticsearch.Config{
Addresses: []string{"https://192.168.153.132:9200"},
Username: "elastic",
Password: "TETJ8IY+ifbt8SLc+RRQ",
CertificateFingerprint: "C0E9867C7D446BFF72FE46E7E9FE3455E970A8ADB0D3DF0E1472D55DB2612CD5",
})The Go API provided by elasticsearch is basically option-style functions. For example, you can test if the service is available using the ping API:
pingResp, err := client.Ping(client.Ping.WithPretty(), client.Ping.WithHuman())
if err != nil {
panic(err)
}
fmt.Println(pingResp)Output:
[200 OK]Another example is checking the service status using the Info API:
infoResp, err := client.Info(client.Info.WithHuman())
if err != nil {
panic(err)
}
fmt.Println(infoResp)Output:
[200 OK] {
"name" : "db-debian12",
"cluster_name" : "docker-cluster",
"cluster_uuid" : "OMbDIsNwTFiuyjNF9Xnpbw",
"version" : {
"number" : "8.15.0",
"build_flavor" : "default",
"build_type" : "docker",
"build_hash" : "1a77947f34deddb41af25e6f0ddb8e830159c179",
"build_date" : "2024-08-05T10:05:34.233336849Z",
"build_snapshot" : false,
"lucene_version" : "9.11.1",
"minimum_wire_compatibility_version" : "7.17.0",
"minimum_index_compatibility_version" : "7.0.0"
},
"tagline" : "You Know, for Search"
}TIP
For any questions about ES APIs, please refer to the official documentation ES Restful API.
Indices
Operate indices through the Go API. All index-related operation APIs are located in the esapi.Indices struct:
// Indices contains the Indices APIs
type Indices struct {
AddBlock IndicesAddBlock
Analyze IndicesAnalyze
ClearCache IndicesClearCache
Clone IndicesClone
Close IndicesClose
...
...
ValidateQuery IndicesValidateQuery
}Create
Create an index as shown below:
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
},
"mappings": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "long"
},
"salary": {
"type": "double"
}
}
}
}In actual operation, it's similar to making HTTP requests:
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
dsl := bytes.NewBufferString(`{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
},
"mappings": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "long"
},
"salary": {
"type": "double"
}
}
}
}`)
createIndices := client.Indices.Create
resp, err := createIndices("user", createIndices.WithBody(dsl))
if err != nil {
panic(err)
}
fmt.Println(resp)
}Output:
[200 OK] {"acknowledged":true,"shards_acknowledged":true,"index":"user"}Get
Get information about multiple indices:
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
get := client.Indices.Get
response, err := get([]string{"user"}, get.WithPretty(), get.WithHuman())
if err != nil {
panic(err)
}
fmt.Println(response)
}Output:
[200 OK] {
"user" : {
"aliases" : { },
"mappings" : {
"properties" : {
"age" : {
"type" : "long"
},
"name" : {
"type" : "text"
},
"salary" : {
"type" : "double"
}
}
},
"settings" : {
"index" : {
"creation_date_string" : "2024-09-23T04:35:04.528Z",
"routing" : {
"allocation" : {
"include" : {
"_tier_preference" : "data_content"
}
}
},
"number_of_shards" : "3",
"provided_name" : "user",
"creation_date" : "1727066104528",
"number_of_replicas" : "2",
"uuid" : "AvhhuqV2ShGkRP9z7XbdDA",
"version" : {
"created_string" : "8.14.4-snapshot[8512000]",
"created" : "8512000"
}
}
}
}
}Analyze
Analyze text strings for specified indices and return results. Text as follows:
{
"analyzer": "standard",
"text": ["this is a test", "the second text"]
}Code:
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
analyze := client.Indices.Analyze
dsl := bytes.NewBufferString(`{
"analyzer" : "standard",
"text" : ["this is a test", "the second text"]
}`)
response, err := analyze(analyze.WithIndex("user"), analyze.WithBody(dsl), analyze.WithPretty(), analyze.WithHuman())
if err != nil {
panic(err)
}
fmt.Println(response)
}Output:
[200 OK] {
"tokens" : [
{
"token" : "this",
"start_offset" : 0,
"end_offset" : 4,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "is",
"start_offset" : 5,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "a",
"start_offset" : 8,
"end_offset" : 9,
"type" : "<ALPHANUM>",
"position" : 2
},
{
"token" : "test",
"start_offset" : 10,
"end_offset" : 14,
"type" : "<ALPHANUM>",
"position" : 3
},
{
"token" : "the",
"start_offset" : 15,
"end_offset" : 18,
"type" : "<ALPHANUM>",
"position" : 104
},
{
"token" : "second",
"start_offset" : 19,
"end_offset" : 25,
"type" : "<ALPHANUM>",
"position" : 105
},
{
"token" : "text",
"start_offset" : 26,
"end_offset" : 30,
"type" : "<ALPHANUM>",
"position" : 106
}
]
}Delete
Delete multiple specified indices:
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
indicesDelete := client.Indices.Delete
response, err := indicesDelete([]string{"user"})
if err != nil {
panic(err)
}
fmt.Println(response)
}Output:
[200 OK] {"acknowledged":true}In the above APIs, you can see that the request body needs to be manually serialized. The official API does not map to Go structs, and the response body also needs to be manually processed. These are relatively common APIs, and other APIs are used in a similar manner without much difference.
Documents
Create
Create a document as follows:
{
"name": "jack",
"age": 12,
"salary": 5701.1
}Code:
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
doc := bytes.NewBufferString(`{
"name": "jack",
"age": 12,
"salary": 5701.1
}`)
create := client.Create
response, err := create("user", "1", doc, create.WithPretty())
if err != nil {
panic(err)
}
fmt.Println(response)
}Output:
[201 Created] {
"_index" : "user",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 3,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}Get
Get a document by specified ID:
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
get := client.Get
response, err := get("user", "1", get.WithPretty())
if err != nil {
panic(err)
}
fmt.Println(response)
}Output:
[200 OK] {
"_index" : "user",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"name" : "jack",
"age" : 12,
"salary" : 5701.1
}
}Update
Update document content:
{
"doc": {
"name": "jack",
"age": 35,
"salary": 5701.1
}
}Code:
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
doc := bytes.NewBufferString(`{
"doc": { "name": "jack",
"age": 35,
"salary": 5701.1
}}`)
update := client.Update
response, err := update("user", "1", doc, update.WithPretty())
if err != nil {
panic(err)
}
fmt.Println(response)
}Output:
[200 OK] {
"_index" : "user",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 3,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}The Update API also supports script operations such as upsert. For more information, see Update API.
Delete
Delete a specified document by ID:
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
deleteDoc := client.Delete
response, err := deleteDoc("user", "1", deleteDoc.WithPretty())
if err != nil {
panic(err)
}
fmt.Println(response)
}Output:
[200 OK] {
"_index" : "user",
"_id" : "1",
"_version" : 3,
"result" : "deleted",
"_shards" : {
"total" : 3,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}Search
The most commonly used ES API is the search API. Below is a simple demonstration. First, prepare the data.
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
for i := range 10 {
doc := bytes.NewBufferString(fmt.Sprintf(`{
"name": "%s",
"age": %d,
"salary": %f
}`, randomName(), rand.Intn(18)+18, rand.Float64()))
create := client.Create
response, err := create("user", string('0'+i), doc, create.WithPretty())
if err != nil {
panic(err)
}
fmt.Println(response)
}
}
func randomName() string {
var b []byte
for range 10 {
b = append(b, byte(rand.Intn(26)+'a'))
}
return string(b)
}The Search API works exactly like a regular HTTP API.
Query all documents:
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
dsl := bytes.NewBufferString(`{"query": {"match_all":{}}, "size": 1}`)
search := client.Search
response, err := search(search.WithBody(dsl), search.WithPretty())
if err != nil {
panic(err)
}
fmt.Println(response)
}Match a specific field:
func main() {
client, err := newClient()
if err != nil {
panic(err)
}
dsl := bytes.NewBufferString(`{"query": {"term":{ "age": 22 } }, "size": 1}`)
search := client.Search
response, err := search(search.WithBody(dsl), search.WithPretty())
if err != nil {
panic(err)
}
fmt.Println(response)
}Summary
These are the basic operations. They work exactly like the HTTP API. Once you master ES, using the Go API is no problem. For more advanced operations like cluster, data stream, and other APIs, please explore them on your own.
