Firestoreの単一documentの読み込み速度の検証

結論

単一documentを連続で読み込むと、約 300/s 程度の速度は出せそう。

読み込み速度

前回は書き込み速度を測定しましたが、今回は読み込み速度を調べました。

環境

  • Google Compute Engine、e2-medium (2 vCPU、4GB メモリ)
  • Ubuntu 20.04 LTS
  • Docker 19.03.8
  • Golang 1.14.9
  • cloud.google.com/go/firestore 1.3.0

検証

単一のdocumentを、1秒毎に指定したrateの数だけ取得し、全ての取得処理が完了するまでの時間を測定します。これを指定した回数(今回は60回)繰り返しました。使用したコードは以下です。(createまわりは前回の使い回し)

package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"strconv"
"sync"
"time"
"cloud.google.com/go/firestore"
"google.golang.org/api/iterator"
)
var (
total = flag.Int("total", 0, "total count")
rate = flag.Int("rate", 0, "rate")
duration = flag.Int("duration", 0, "duration")
execName = flag.String("exec", "read", "exec")
sub = flag.Bool("sub", false, "read sub")
)
func main() {
flag.Parse()
ctx := context.Background()
client, err := firestore.NewClient(ctx, os.Getenv("PROJECT_ID"))
if err != nil {
log.Fatal(err)
}
data := generateDocumentData(client)
if *execName == "create" {
exec(ctx, client, data, create)
}
if *execName == "read" {
read(ctx, client.Collection("articles"))
}
}
func panicIf(err error) {
if err != nil {
panic(err)
}
}
func generateDocumentData(client *firestore.Client) []map[*firestore.DocumentRef]map[string]interface{} {
limit := 120
totalCount := *total
count := totalCount/limit + 1
data := make([]map[*firestore.DocumentRef]map[string]interface{}, 0, count)
col := client.Collection("articles")
for j := 0; j < count; j++ {
min := limit * j
max := limit * (j + 1)
if max > totalCount {
max = totalCount
}
if min == max {
break
}
results := make(map[*firestore.DocumentRef]map[string]interface{}, limit)
for i := min; i < max; i++ {
doc := col.NewDoc()
results[doc] = map[string]interface{}{
"title": "title_" + strconv.FormatInt(int64(i), 10),
"tags": []string{"tag_1", "tag_2", "tag_3"},
"author_id": "author_id_" + strconv.FormatInt(int64(i), 10),
"view_count": i,
"is_published": true,
"text": "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
}
subCol := doc.Collection("sections")
for k := 0; k < 3; k++ {
doc := subCol.NewDoc()
results[doc] = map[string]interface{}{
"sort_order": k,
"text": "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
}
}
}
data = append(data, results)
}
return data
}
func exec(ctx context.Context, client *firestore.Client, data []map[*firestore.DocumentRef]map[string]interface{},
execFunc func(ctx context.Context, client *firestore.Client, articles map[*firestore.DocumentRef]map[string]interface{})) {
fmt.Println("start")
start := time.Now()
multi := 3
limit := make(chan struct{}, multi)
wg := &sync.WaitGroup{}
for _, articles := range data {
limit <- struct{}{}
wg.Add(1)
go func(articles map[*firestore.DocumentRef]map[string]interface{}) {
execFunc(ctx, client, articles)
<-limit
wg.Done()
}(articles)
}
wg.Wait()
fmt.Println(time.Now().Sub(start))
fmt.Println("end")
}
func create(ctx context.Context, client *firestore.Client, articles map[*firestore.DocumentRef]map[string]interface{}) {
batch := client.Batch()
for doc, article := range articles {
batch.Create(doc, article)
}
_, err := batch.Commit(ctx)
panicIf(err)
}
func read(ctx context.Context, col *firestore.CollectionRef) {
iter := col.Documents(ctx)
snap, err := iter.Next()
panicIf(err)
id := snap.Ref.ID
duration := *duration
rate := *rate
start := time.Now()
fmt.Println("start")
wgAll := &sync.WaitGroup{}
for j := 0; j < duration; j++ {
wgAll.Add(1)
go func() {
start := time.Now()
wg := &sync.WaitGroup{}
for i := 0; i < rate; i++ {
wg.Add(1)
go func() {
_, err := col.Doc(id).Get(ctx)
panicIf(err)
// log.Printf("%+v", snap.Data())
if *sub {
iter := col.Doc(id).Collection("sections").Documents(ctx)
for {
_, err := iter.Next()
if err == iterator.Done {
break
}
panicIf(err)
// log.Printf("%+v", snap.Data())
}
}
wg.Done()
}()
}
wg.Wait()
fmt.Println(time.Now().Sub(start))
wgAll.Done()
}()
time.Sleep(1 * time.Second)
}
wgAll.Wait()
fmt.Println("end")
fmt.Println(time.Now().Sub(start))
}

結果は以下になります。

----------2022-05-11-10.00.12

300/sまではギリギリ捌ききれていそうです。500/sで急に遅くなっているのは1秒間に捌ききれずにどんどん詰まっていったためと思われます。

追加の検証で、単一documentの取得後にdocumentに紐づくsub collection配下のdocument(今回は3つ)を全取得するような処理を試してみました。結果は以下になります。

----------2022-05-11-10.00.30

100/s はほぼ0.4sで完了していましたが、1回だけ1s越えがありました。それ以外はほぼ変わらない結果になりました。

まとめ

単一アプリケーションからgoroutineで試してもこれくらいの速度が出るなら、懸念していたほど遅くはならなそうなので安心しました。