/
score_store.go
120 lines (108 loc) · 3.14 KB
/
score_store.go
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package score
import (
"context"
"fmt"
"math/rand"
"time"
"cloud.google.com/go/spanner"
"google.golang.org/grpc/codes"
)
// ScoreStore is 固定行数で更新をひたすら行うTable
type ScoreStore struct {
sc *spanner.Client
}
func NewScoreStore(ctx context.Context, sc *spanner.Client) (*ScoreStore, error) {
return &ScoreStore{
sc: sc,
}, nil
}
type Score struct {
ID string `spanner:"Id"` // 0 ~ 10億
ClassRank int64 // 6:10億以上,5:1億以上,4:1000万以上,3:100万以上,2:10万以上,1:10000以上,0:10000未満
CircleID string `spanner:"CircleId"` // 所属しているサークル,100000種類ぐらい
Score int64
Shard int64 // 0 ~ 9
MaxScore int64 // 過去最高スコア
CommitedAt time.Time
}
func (s *ScoreStore) CircleID(id int64) string {
return fmt.Sprintf("circle%08d", id)
}
// InsertBatch is 最初に一気にデータを作るためのもの
func (s *ScoreStore) InsertBatch(ctx context.Context, ids []string) error {
_, err := s.sc.ReadWriteTransaction(ctx, func(ctx context.Context, tx *spanner.ReadWriteTransaction) error {
var ms []*spanner.Mutation
for _, id := range ids {
m := spanner.InsertMap("Score", map[string]interface{}{
"Id": id,
"CommitedAt": spanner.CommitTimestamp,
})
ms = append(ms, m)
}
return tx.BufferWrite(ms)
})
if err != nil {
return err
}
return nil
}
// Upsert is Scoreを更新する
func (s *ScoreStore) Upsert(ctx context.Context, e *Score) error {
ctx, span := startSpan(ctx, "ScoreStore/upsert")
defer span.End()
_, err := s.sc.ReadWriteTransaction(ctx, func(ctx context.Context, tx *spanner.ReadWriteTransaction) error {
var m *spanner.Mutation
circleID := e.CircleID
row, err := tx.ReadRow(ctx, "Score", spanner.Key{e.ID}, []string{"Id", "MaxScore"})
if err != nil {
if spanner.ErrCode(err) == codes.NotFound {
// Rowがない場合はMaxScoreはZeroと考える
m = spanner.InsertMap("Score", map[string]interface{}{
"Id": e.ID,
"CircleId": circleID,
"Score": e.Score,
"MaxScore": e.Score,
"Shard": rand.Int63n(9),
"CommitedAt": spanner.CommitTimestamp,
})
} else {
return err
}
} else {
ce := &Score{}
if err := row.ToStruct(ce); err != nil {
return err
}
maxScore := ce.MaxScore
if e.Score > maxScore {
maxScore = e.Score
}
m = spanner.UpdateMap("Score", map[string]interface{}{
"Id": e.ID,
"Score": e.Score,
"MaxScore": maxScore,
"Shard": rand.Int63n(9), // データがすべて埋まったら、これは消していい
"CommitedAt": spanner.CommitTimestamp,
})
}
return tx.BufferWrite([]*spanner.Mutation{m})
})
if err != nil {
return err
}
return nil
}
func (s *ScoreStore) Get(ctx context.Context, id string) (*Score, error) {
ctx, span := startSpan(ctx, "ScoreStore/get")
defer span.End()
row, err := s.sc.Single().ReadRow(ctx, "Score", spanner.Key{id},
[]string{"Id", "ClassRank", "CircleId", "Score", "MaxScore", "CommitedAt"})
if err != nil {
return nil, err
}
var e Score
if err := row.ToStruct(&e); err != nil {
return nil, err
}
return &e, nil
}