Skip to content
This repository was archived by the owner on Jan 28, 2021. It is now read-only.

Commit 8a4cd29

Browse files
authored
Merge pull request #583 from erizocosmico/feature/index-checksum
sql: use checksums to determine if indexes are outdated
2 parents 09a67ca + 7d82c68 commit 8a4cd29

File tree

5 files changed

+191
-11
lines changed

5 files changed

+191
-11
lines changed

sql/index.go

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@ import (
55
"strings"
66
"sync"
77

8+
"github.com/sirupsen/logrus"
89
"gopkg.in/src-d/go-errors.v1"
910
)
1011

1112
// IndexBatchSize is the number of rows to save at a time when creating indexes.
1213
const IndexBatchSize = uint64(10000)
1314

15+
// ChecksumKey is the key in an index config to store the checksum.
16+
const ChecksumKey = "checksum"
17+
18+
// Checksumable provides the checksum of some data.
19+
type Checksumable interface {
20+
// Checksum returns a checksum and an error if there was any problem
21+
// computing or obtaining the checksum.
22+
Checksum() (string, error)
23+
}
24+
1425
// PartitionIndexKeyValueIter is an iterator of partitions that will return
1526
// the partition and the IndexKeyValueIter of that partition.
1627
type PartitionIndexKeyValueIter interface {
@@ -211,17 +222,43 @@ func (r *IndexRegistry) LoadIndexes(dbs Databases) error {
211222

212223
for _, driver := range r.drivers {
213224
for _, db := range dbs {
214-
for t := range db.Tables() {
215-
indexes, err := driver.LoadAll(db.Name(), t)
225+
for _, t := range db.Tables() {
226+
indexes, err := driver.LoadAll(db.Name(), t.Name())
216227
if err != nil {
217228
return err
218229
}
219230

231+
var checksum string
232+
if c, ok := t.(Checksumable); ok {
233+
checksum, err = c.Checksum()
234+
if err != nil {
235+
return err
236+
}
237+
}
238+
220239
for _, idx := range indexes {
221240
k := indexKey{db.Name(), idx.ID()}
222241
r.indexes[k] = idx
223242
r.indexOrder = append(r.indexOrder, k)
224-
r.statuses[k] = IndexReady
243+
244+
var idxChecksum string
245+
if c, ok := idx.(Checksumable); ok {
246+
idxChecksum, err = c.Checksum()
247+
if err != nil {
248+
return err
249+
}
250+
}
251+
252+
if checksum == "" || checksum == idxChecksum {
253+
r.statuses[k] = IndexReady
254+
} else {
255+
logrus.Warnf(
256+
"index %q is outdated and will not be used, you can remove it using `DROP INDEX %s`",
257+
idx.ID(),
258+
idx.ID(),
259+
)
260+
r.statuses[k] = IndexOutdated
261+
}
225262
}
226263
}
227264
}
@@ -244,6 +281,18 @@ func (r *IndexRegistry) CanUseIndex(idx Index) bool {
244281
return r.canUseIndex(idx)
245282
}
246283

284+
// CanRemoveIndex returns whether the given index is ready to be removed.
285+
func (r *IndexRegistry) CanRemoveIndex(idx Index) bool {
286+
if idx == nil {
287+
return false
288+
}
289+
290+
r.mut.RLock()
291+
defer r.mut.RUnlock()
292+
status := r.statuses[indexKey{idx.Database(), idx.ID()}]
293+
return status == IndexReady || status == IndexOutdated
294+
}
295+
247296
func (r *IndexRegistry) canUseIndex(idx Index) bool {
248297
if idx == nil {
249298
return false
@@ -400,8 +449,8 @@ var (
400449
ErrIndexNotFound = errors.NewKind("index %q was not found")
401450

402451
// ErrIndexDeleteInvalidStatus is returned when the index trying to delete
403-
// does not have a ready state.
404-
ErrIndexDeleteInvalidStatus = errors.NewKind("can't delete index %q because it's not ready for usage")
452+
// does not have a ready or outdated state.
453+
ErrIndexDeleteInvalidStatus = errors.NewKind("can't delete index %q because it's not ready for removal")
405454
)
406455

407456
func (r *IndexRegistry) validateIndexToAdd(idx Index) error {
@@ -507,7 +556,7 @@ func (r *IndexRegistry) DeleteIndex(db, id string, force bool) (<-chan struct{},
507556
var key indexKey
508557
for k, idx := range r.indexes {
509558
if strings.ToLower(id) == idx.ID() {
510-
if !force && !r.CanUseIndex(idx) {
559+
if !force && !r.CanRemoveIndex(idx) {
511560
r.mut.RUnlock()
512561
return nil, ErrIndexDeleteInvalidStatus.New(id)
513562
}
@@ -563,13 +612,16 @@ func (r *IndexRegistry) DeleteIndex(db, id string, force bool) (<-chan struct{},
563612
}
564613

565614
// IndexStatus represents the current status in which the index is.
566-
type IndexStatus bool
615+
type IndexStatus byte
567616

568617
const (
569618
// IndexNotReady means the index is not ready to be used.
570-
IndexNotReady IndexStatus = false
619+
IndexNotReady IndexStatus = iota
571620
// IndexReady means the index can be used.
572-
IndexReady IndexStatus = true
621+
IndexReady
622+
// IndexOutdated means the index is loaded but will not be used because the
623+
// contents in it are outdated.
624+
IndexOutdated
573625
)
574626

575627
// IsUsable returns whether the index can be used or not based on the status.

sql/index/pilosa/index.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,33 @@ type pilosaIndex struct {
6767
table string
6868
id string
6969
expressions []string
70+
checksum string
7071
}
7172

7273
func newPilosaIndex(idx *pilosa.Index, mapping *mapping, cfg *index.Config) *pilosaIndex {
74+
var checksum string
75+
for _, c := range cfg.Drivers {
76+
if ch, ok := c[sql.ChecksumKey]; ok {
77+
checksum = ch
78+
}
79+
break
80+
}
81+
7382
return &pilosaIndex{
7483
index: newConcurrentPilosaIndex(idx),
7584
db: cfg.DB,
7685
table: cfg.Table,
7786
id: cfg.ID,
7887
expressions: cfg.Expressions,
7988
mapping: mapping,
89+
checksum: checksum,
8090
}
8191
}
8292

93+
func (idx *pilosaIndex) Checksum() (string, error) {
94+
return idx.checksum, nil
95+
}
96+
8397
// Get returns an IndexLookup for the given key in the index.
8498
// If key parameter is not present then the returned iterator
8599
// will go through all the locations on the index.

sql/index_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,40 @@ func TestLoadIndexes(t *testing.T) {
299299
}
300300
}
301301

302+
func TestLoadOutdatedIndexes(t *testing.T) {
303+
require := require.New(t)
304+
305+
d := &loadDriver{id: "d1", indexes: []Index{
306+
&checksumIndex{&dummyIdx{id: "idx1", database: "db1", table: "t1"}, "2"},
307+
&checksumIndex{&dummyIdx{id: "idx2", database: "db1", table: "t2"}, "2"},
308+
}}
309+
310+
registry := NewIndexRegistry()
311+
registry.RegisterIndexDriver(d)
312+
313+
dbs := Databases{
314+
dummyDB{
315+
name: "db1",
316+
tables: map[string]Table{
317+
"t1": &checksumTable{&dummyTable{name: "t1"}, "2"},
318+
"t2": &checksumTable{&dummyTable{name: "t2"}, "1"},
319+
},
320+
},
321+
}
322+
323+
require.NoError(registry.LoadIndexes(dbs))
324+
325+
var result []Index
326+
for _, idx := range registry.indexes {
327+
result = append(result, idx)
328+
}
329+
330+
require.ElementsMatch(d.indexes, result)
331+
332+
require.Equal(registry.statuses[indexKey{"db1", "idx1"}], IndexReady)
333+
require.Equal(registry.statuses[indexKey{"db1", "idx2"}], IndexOutdated)
334+
}
335+
302336
type dummyDB struct {
303337
name string
304338
tables map[string]Table
@@ -377,3 +411,21 @@ func (dummyExpr) Type() Type { panic("not implemented") }
377411
func (e dummyExpr) WithIndex(idx int) Expression {
378412
return &dummyExpr{idx, e.colName}
379413
}
414+
415+
type checksumTable struct {
416+
Table
417+
checksum string
418+
}
419+
420+
func (t *checksumTable) Checksum() (string, error) {
421+
return t.checksum, nil
422+
}
423+
424+
type checksumIndex struct {
425+
Index
426+
checksum string
427+
}
428+
429+
func (idx *checksumIndex) Checksum() (string, error) {
430+
return idx.checksum, nil
431+
}

sql/plan/create_index.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func getIndexableTable(t sql.Table) (sql.IndexableTable, error) {
8181
case sql.TableWrapper:
8282
return getIndexableTable(t.Underlying())
8383
default:
84-
return nil, ErrInsertIntoNotSupported.New()
84+
return nil, ErrNotIndexable.New()
8585
}
8686
}
8787

@@ -119,6 +119,13 @@ func (c *CreateIndex) RowIter(ctx *sql.Context) (sql.RowIter, error) {
119119
}
120120
}
121121

122+
if ch, ok := table.Table.(sql.Checksumable); ok {
123+
c.Config[sql.ChecksumKey], err = ch.Checksum()
124+
if err != nil {
125+
return nil, err
126+
}
127+
}
128+
122129
index, err := driver.Create(
123130
c.CurrentDatabase,
124131
table.Name(),

sql/plan/create_index_test.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,44 @@ func TestCreateIndexSync(t *testing.T) {
171171
require.True(found)
172172
}
173173

174+
func TestCreateIndexChecksum(t *testing.T) {
175+
require := require.New(t)
176+
177+
table := &checksumTable{
178+
mem.NewTable("foo", sql.Schema{
179+
{Name: "a", Source: "foo"},
180+
{Name: "b", Source: "foo"},
181+
{Name: "c", Source: "foo"},
182+
}),
183+
"1",
184+
}
185+
186+
driver := new(mockDriver)
187+
catalog := sql.NewCatalog()
188+
catalog.RegisterIndexDriver(driver)
189+
db := mem.NewDatabase("foo")
190+
db.AddTable("foo", table)
191+
catalog.AddDatabase(db)
192+
193+
exprs := []sql.Expression{
194+
expression.NewGetFieldWithTable(2, sql.Int64, "foo", "c", true),
195+
expression.NewGetFieldWithTable(0, sql.Int64, "foo", "a", true),
196+
}
197+
198+
ci := NewCreateIndex(
199+
"idx", NewResolvedTable(table), exprs, "mock",
200+
map[string]string{"async": "false"},
201+
)
202+
ci.Catalog = catalog
203+
ci.CurrentDatabase = "foo"
204+
205+
_, err := ci.RowIter(sql.NewEmptyContext())
206+
require.NoError(err)
207+
208+
require.Equal([]string{"idx"}, driver.saved)
209+
require.Equal("1", driver.config["idx"][sql.ChecksumKey])
210+
}
211+
174212
func TestCreateIndexWithIter(t *testing.T) {
175213
require := require.New(t)
176214
foo := mem.NewPartitionedTable("foo", sql.Schema{
@@ -283,14 +321,20 @@ func (i *mockIndex) Has(sql.Partition, ...interface{}) (bool, error) {
283321
func (*mockIndex) Driver() string { return "mock" }
284322

285323
type mockDriver struct {
324+
config map[string]map[string]string
286325
deleted []string
287326
saved []string
288327
}
289328

290329
var _ sql.IndexDriver = (*mockDriver)(nil)
291330

292331
func (*mockDriver) ID() string { return "mock" }
293-
func (*mockDriver) Create(db, table, id string, exprs []sql.Expression, config map[string]string) (sql.Index, error) {
332+
func (d *mockDriver) Create(db, table, id string, exprs []sql.Expression, config map[string]string) (sql.Index, error) {
333+
if d.config == nil {
334+
d.config = make(map[string]map[string]string)
335+
}
336+
d.config[id] = config
337+
294338
return &mockIndex{db, table, id, exprs}, nil
295339
}
296340
func (*mockDriver) LoadAll(db, table string) ([]sql.Index, error) {
@@ -305,3 +349,14 @@ func (d *mockDriver) Delete(index sql.Index, _ sql.PartitionIter) error {
305349
d.deleted = append(d.deleted, index.ID())
306350
return nil
307351
}
352+
353+
type checksumTable struct {
354+
sql.Table
355+
checksum string
356+
}
357+
358+
func (t *checksumTable) Checksum() (string, error) {
359+
return t.checksum, nil
360+
}
361+
362+
func (t *checksumTable) Underlying() sql.Table { return t.Table }

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy