Introduction# ACOR is a Go library that implements the Aho-Corasick algorithm with Redis as the backend storage. The latest version introduces four major features:
Index APIs - Provides match position informationRedis Topology Support - Supports Sentinel, Cluster, and RingCommand-Line Tool - Use directly from the terminalServer Adapters - Deploy as HTTP and gRPC servicesThis post covers the usage and features of each.
Index APIs# Comparison with Existing APIs# Previously, Find and Suggest APIs only told you which keywords matched. For text highlighting or position-based analysis, you had to calculate indices separately.
The new Index APIs solve this problem:
1
2
3
4
5
6
// Existing: Returns only matched keywords
func (ac * AhoCorasick) Find (text string ) ([]string , error )
// New: Returns keywords with their start indices
func (ac * AhoCorasick) FindIndex (text string ) (map [string ][]int , error )
func (ac * AhoCorasick) SuggestIndex (input string ) (map [string ][]int , error )
Usage Example# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"fmt"
"github.com/skyoo2003/acor/pkg/acor"
)
func main () {
ac, err := acor.Create (& acor.AhoCorasickArgs{
Addr: "localhost:6379" ,
Name: "sample" ,
})
if err != nil {
panic (err)
}
defer ac.Close ()
keywords := []string {"he" , "her" , "his" , "him" }
for _, k := range keywords {
ac.Add (k)
}
matched, _ := ac.FindIndex ("he is him and she is her" )
fmt.Println (matched)
// Output: map[he:[0] him:[6] her:[21]]
}
Unicode Handling# Index APIs properly handle Unicode. Go’s range statement iterates over strings in rune units, ensuring correct indexing for multi-byte characters like Korean and emojis:
1
2
matched, _ := ac.FindIndex ("가한글" )
// Result: map[string][]int{"한글": {1}} // Character index, not byte index
Index APIs have overhead from storing index information in map[string][]int and calculating rune length at each match. For simple existence checks where index information isn’t needed, use the original Find/Suggest APIs for better efficiency.
Redis Topology Support# Supported Topologies# Various Redis deployment modes are now supported:
1
2
3
4
5
6
7
8
9
type AhoCorasickArgs struct {
Addr string // Standalone
Addrs []string // Sentinel or Cluster
MasterName string // Sentinel master name
RingAddrs map [string ]string // Ring shards
Password string
DB int
Name string
}
Standalone
1
2
3
4
5
6
args := & acor.AhoCorasickArgs{
Addr: "localhost:6379" ,
Password: "" ,
DB: 0 ,
Name: "sample" ,
}
Redis Sentinel
1
2
3
4
5
6
7
args := & acor.AhoCorasickArgs{
Addrs: []string {"localhost:26379" , "localhost:26380" },
MasterName: "mymaster" ,
Password: "" ,
DB: 0 ,
Name: "sample" ,
}
Redis Cluster
1
2
3
4
5
args := & acor.AhoCorasickArgs{
Addrs: []string {"localhost:7000" , "localhost:7001" , "localhost:7002" },
Password: "" ,
Name: "sample" ,
}
Redis Ring
1
2
3
4
5
6
7
8
9
args := & acor.AhoCorasickArgs{
RingAddrs: map [string ]string {
"shard-1" : "localhost:7000" ,
"shard-2" : "localhost:7001" ,
},
Password: "" ,
DB: 0 ,
Name: "sample" ,
}
Cluster-Safe Key Design# In Redis Cluster, keys are distributed across multiple shards. ACOR uses hash tags to ensure all keys belonging to a single collection are stored on the same shard:
{ { c c o o l l l l e e c c t t i i o o n n - - n n a a m m e e } } : : p o r u e t f p i u x t : : s k t e a y t w e o r d
Error Handling# All Redis-related APIs now explicitly return errors:
1
2
3
4
5
6
7
8
9
10
11
12
ac, err := acor.Create (args)
if err != nil {
log.Fatalf ("Redis connection failed: %v" , err)
}
defer ac.Close ()
matched, err := ac.Find ("he is him" )
if err != nil {
log.Printf ("Error during search: %v" , err)
return
}
fmt.Println (matched)
The Add method performs rollback on failure to ensure data consistency.
Installation# Binary Download
1
2
3
4
5
6
7
8
9
# macOS (Apple Silicon)
curl -LO https://github.com/skyoo2003/acor/releases/latest/download/acor_darwin_arm64.tar.gz
tar xzf acor_darwin_arm64.tar.gz
sudo mv acor /usr/local/bin/
# Linux (x86_64)
curl -LO https://github.com/skyoo2003/acor/releases/latest/download/acor_linux_amd64.tar.gz
tar xzf acor_linux_amd64.tar.gz
sudo mv acor /usr/local/bin/
Build from Source
1
2
3
git clone https://github.com/skyoo2003/acor.git
cd acor
make build
Basic Usage# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Add keywords
acor -addr localhost:6379 -name sample add "he"
acor -addr localhost:6379 -name sample add "her"
acor -addr localhost:6379 -name sample add "him"
# Search text
acor -addr localhost:6379 -name sample find "he is him"
# Output: he
# him
# Search with position info
acor -addr localhost:6379 -name sample find-index "he is him"
# Output: he: [0]
# him: [6]
# Autocomplete suggestions
acor -addr localhost:6379 -name sample suggest "he"
# Output: he
# her
Main Commands# Command Description addAdd keyword removeRemove keyword findSearch text find-indexSearch with position info suggestAutocomplete suggestions suggest-indexAutocomplete with position info infoCollection info flushDelete collection
Common Options# Option Description Default -addrSingle Redis address localhost:6379-addrsSentinel/Cluster addresses -master-nameSentinel master name -ring-addrsRing shards -passwordRedis password -dbRedis database number 0-nameACOR collection name (required)
Server Adapters# Architecture# The pkg/server package exposes the existing pkg/acor APIs over HTTP JSON and gRPC:
┌ │ └ ─ ─ ─ ─ ─ ─ ─ C ─ ─ l ─ ─ i ─ ─ e ─ ─ n ─ ─ t ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┘ ─ ─ ─ ─ ▶ ┌ │ │ └ ┌ │ └ ┌ │ └ ─ ─ ─ ─ ─ ─ ─ H ─ ─ ─ ─ ─ ─ T ─ ─ p ─ ─ ─ ─ T S ─ ─ k ─ ─ ─ ─ P e ─ ─ g ─ ─ R ─ ─ / r ─ ─ / ─ ─ e ─ ─ g v ┬ │ ▼ a ┬ │ ▼ d ─ ─ R e ─ ─ c ─ ─ i ─ ─ P r ─ ─ o ─ ─ s ─ ─ C ─ ─ r ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ ┘ ┐ │ ┘ ┐ │ ┘
HTTP Server# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main
import (
"log"
"net/http"
"github.com/skyoo2003/acor/pkg/acor"
"github.com/skyoo2003/acor/pkg/server"
)
func main () {
ac, err := acor.Create (& acor.AhoCorasickArgs{
Addr: "localhost:6379" ,
Name: "sample" ,
})
if err != nil {
log.Fatal (err)
}
defer ac.Close ()
httpHandler := server.NewHTTPHandler (ac)
http.Handle ("/" , httpHandler)
log.Println ("HTTP server listening on :8080" )
log.Fatal (http.ListenAndServe (":8080" , nil ))
}
API Endpoints
Method Path Description POST /addAdd keyword POST /removeRemove keyword POST /findSearch text POST /find-indexSearch with position info POST /suggestAutocomplete suggestions POST /suggest-indexAutocomplete with position info GET /infoCollection info POST /flushDelete collection
Request Example
1
2
3
curl -X POST http://localhost:8080/find \
-H "Content-Type: application/json" \
-d '{"text": "he is him"}'
1
2
3
{
"matched" : ["he" , "him" ]
}
gRPC Server# Server Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
import (
"log"
"net"
"github.com/skyoo2003/acor/pkg/acor"
"github.com/skyoo2003/acor/pkg/server"
"google.golang.org/grpc"
)
func main () {
ac, err := acor.Create (& acor.AhoCorasickArgs{
Addr: "localhost:6379" ,
Name: "sample" ,
})
if err != nil {
log.Fatal (err)
}
defer ac.Close ()
lis, err := net.Listen ("tcp" , ":50051" )
if err != nil {
log.Fatal (err)
}
grpcServer := grpc.NewServer ()
server.RegisterGRPCServer (grpcServer, ac)
log.Println ("gRPC server listening on :50051" )
grpcServer.Serve (lis)
}
Client Example
1
2
3
4
5
6
conn, _ := grpc.Dial ("localhost:50051" , grpc.WithInsecure ())
defer conn.Close ()
client := pb.NewAhoCorasickClient (conn)
resp, _ := client.Find (context.Background (), & pb.FindRequest{Text: "he is him" })
fmt.Println (resp.Matched) // [he, him]
HTTP vs gRPC Selection Guide# Criteria HTTP gRPC Protocol HTTP/1.1 + JSON HTTP/2 + Protobuf Performance Moderate High Debugging Easy with curl Requires tools Streaming Not supported Supported
HTTP is suitable for debugging and quick prototyping, while gRPC is ideal for high-performance production environments.
Deployment# Docker
1
2
3
4
5
6
7
8
9
10
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o server ./cmd/server
FROM alpine:latest
WORKDIR /app
COPY --from= builder /app/server .
EXPOSE 8080 50051
CMD ["./server" ]
1
2
docker build -t acor-server .
docker run -p 8080:8080 -p 50051:50051 acor-server
Kubernetes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion : apps/v1
kind : Deployment
metadata :
name : acor-server
spec :
replicas : 3
selector :
matchLabels :
app : acor-server
template :
spec :
containers :
- name : acor-server
image : acor-server:latest
ports :
- containerPort : 8080
name : http
- containerPort : 50051
name : grpc
env :
- name : REDIS_ADDR
value : "redis-service:6379"
Conclusion# This update significantly expands ACOR’s capabilities:
Index APIs : Enable text highlighting and position-based analysisRedis Topology Support : Provides high availability and scalability in productionCLI : Enables automation with scripts and quick testingServer Adapters : Easy integration into microservice architecturesFor more details, visit the ACOR GitHub repository and official documentation .