@@ -89,6 +89,81 @@ type Client struct {
89
89
90
90
importLogEncoder encoder
91
91
logLock sync.Mutex
92
+
93
+ shardNodes shardNodes
94
+ tick * time.Ticker
95
+ done chan struct {}
96
+ }
97
+
98
+ func (c * Client ) getURIsForShard (index string , shard uint64 ) ([]* URI , error ) {
99
+ uris , ok := c .shardNodes .Get (index , shard )
100
+ if ok {
101
+ return uris , nil
102
+ }
103
+ fragmentNodes , err := c .fetchFragmentNodes (index , shard )
104
+ if err != nil {
105
+ return nil , errors .Wrap (err , "trying to look up nodes for shard" )
106
+ }
107
+ uris = make ([]* URI , 0 , len (fragmentNodes ))
108
+ for _ , fn := range fragmentNodes {
109
+ uris = append (uris , fn .URI ())
110
+ }
111
+ c .shardNodes .Put (index , shard , uris )
112
+ return uris , nil
113
+ }
114
+
115
+ func (c * Client ) runChangeDetection () {
116
+ for {
117
+ select {
118
+ case <- c .tick .C :
119
+ c .detectClusterChanges ()
120
+ case <- c .done :
121
+ return
122
+ }
123
+ }
124
+ }
125
+
126
+ func (c * Client ) Close () error {
127
+ c .tick .Stop ()
128
+ close (c .done )
129
+ return nil
130
+ }
131
+
132
+ // detectClusterChanges chooses a random index and shard from the
133
+ // shardNodes cache and deletes it. It then looks it up from Pilosa to
134
+ // see if it still matches, and if not it drops the whole cache.
135
+ func (c * Client ) detectClusterChanges () {
136
+ c .shardNodes .mu .Lock ()
137
+ // we rely on Go's random map iteration order to get a random
138
+ // element. If it doesn't end up being random, it shouldn't
139
+ // actually matter.
140
+ for index , shardMap := range c .shardNodes .data {
141
+ for shard , uris := range shardMap {
142
+ delete (shardMap , shard )
143
+ c .shardNodes .data [index ] = shardMap
144
+ c .shardNodes .mu .Unlock ()
145
+ newURIs , err := c .getURIsForShard (index , shard ) // refetch URIs from server.
146
+ if err != nil {
147
+ c .logger .Printf ("problem invalidating shard node cache: %v" , err )
148
+ return
149
+ }
150
+ if len (uris ) != len (newURIs ) {
151
+ c .logger .Printf ("invalidating shard node cache old: %s, new: %s" , URIs (uris ), URIs (newURIs ))
152
+ c .shardNodes .Invalidate ()
153
+ return
154
+ }
155
+ for i := range uris {
156
+ u1 , u2 := uris [i ], newURIs [i ]
157
+ if * u1 != * u2 {
158
+ c .logger .Printf ("invalidating shard node cache, uri mismatch at %d old: %s, new: %s" , i , URIs (uris ), URIs (newURIs ))
159
+ c .shardNodes .Invalidate ()
160
+ return
161
+ }
162
+ }
163
+ break
164
+ }
165
+ break
166
+ }
92
167
}
93
168
94
169
// DefaultClient creates a client with the default address and options.
@@ -138,6 +213,10 @@ func newClientWithOptions(options *ClientOptions) *Client {
138
213
client : newHTTPClient (options .withDefaults ()),
139
214
logger : log .New (os .Stderr , "go-pilosa " , log .Flags ()),
140
215
coordinatorLock : & sync.RWMutex {},
216
+
217
+ shardNodes : newShardNodes (),
218
+ tick : time .NewTicker (time .Minute ),
219
+ done : make (chan struct {}, 0 ),
141
220
}
142
221
if options .importLogWriter != nil {
143
222
c .importLogEncoder = newImportLogEncoder (options .importLogWriter )
@@ -148,9 +227,10 @@ func newClientWithOptions(options *ClientOptions) *Client {
148
227
c .tracer = options .tracer
149
228
}
150
229
c .retries = * options .retries
151
- c .minRetrySleepTime = 1 * time .Second
230
+ c .minRetrySleepTime = 100 * time .Millisecond
152
231
c .maxRetrySleepTime = 2 * time .Minute
153
232
c .importManager = newRecordImportManager (c )
233
+ go c .runChangeDetection ()
154
234
return c
155
235
156
236
}
@@ -282,7 +362,7 @@ func (c *Client) EnsureIndex(index *Index) error {
282
362
if err == ErrIndexExists {
283
363
return nil
284
364
}
285
- return err
365
+ return errors . Wrap ( err , "creating index" )
286
366
}
287
367
288
368
// EnsureField creates a field on the server if it doesn't exists.
@@ -296,13 +376,17 @@ func (c *Client) EnsureField(field *Field) error {
296
376
297
377
// DeleteIndex deletes an index on the server.
298
378
func (c * Client ) DeleteIndex (index * Index ) error {
379
+ return c .DeleteIndexByName (index .Name ())
380
+ }
381
+
382
+ // DeleteIndexByName deletes the named index on the server.
383
+ func (c * Client ) DeleteIndexByName (index string ) error {
299
384
span := c .tracer .StartSpan ("Client.DeleteIndex" )
300
385
defer span .Finish ()
301
386
302
- path := fmt .Sprintf ("/index/%s" , index . name )
387
+ path := fmt .Sprintf ("/index/%s" , index )
303
388
_ , _ , err := c .httpRequest ("DELETE" , path , nil , nil , false )
304
389
return err
305
-
306
390
}
307
391
308
392
// DeleteField deletes a field on the server.
@@ -340,7 +424,7 @@ func (c *Client) syncSchema(schema *Schema, serverSchema *Schema) error {
340
424
if _ , ok := serverSchema .indexes [indexName ]; ! ok {
341
425
err = c .EnsureIndex (index )
342
426
if err != nil {
343
- return err
427
+ return errors . Wrap ( err , "ensuring index" )
344
428
}
345
429
}
346
430
for _ , field := range index .fields {
@@ -535,7 +619,7 @@ func (c *Client) translateRecordsRowKeys(rowKeyIDMap *lru.LRU, field *Field, col
535
619
}
536
620
if len (keys ) > 0 {
537
621
// translate missing keys
538
- ids , err := c .translateRowKeys (field , keys )
622
+ ids , err := c .TranslateRowKeys (field , keys )
539
623
if err != nil {
540
624
return err
541
625
}
@@ -572,7 +656,7 @@ func (c *Client) translateRecordsColumnKeys(columnKeyIDMap *lru.LRU, index *Inde
572
656
}
573
657
if len (keys ) > 0 {
574
658
// translate missing keys
575
- ids , err := c .translateColumnKeys (index , keys )
659
+ ids , err := c .TranslateColumnKeys (index , keys )
576
660
if err != nil {
577
661
return err
578
662
}
@@ -641,6 +725,63 @@ func (c *Client) importValues(field *Field,
641
725
return errors .Wrap (err , "importing values to nodes" )
642
726
}
643
727
728
+ // ImportValues takes the given integer values and column ids (which
729
+ // must all be in the given shard) and imports them into the given
730
+ // index,field,shard on all nodes which should hold that shard. It
731
+ // assumes that the ids have been translated from keys if necessary
732
+ // and so tells Pilosa to ignore checking if the index uses column
733
+ // keys. ImportValues wraps EncodeImportValues and DoImportValues —
734
+ // these are broken out and exported so that performance conscious
735
+ // users can re-use the same vals and ids byte buffers for local
736
+ // encoding, while performing the imports concurrently.
737
+ func (c * Client ) ImportValues (index , field string , shard uint64 , vals []int64 , ids []uint64 , clear bool ) error {
738
+ path , data , err := c .EncodeImportValues (index , field , shard , vals , ids , clear )
739
+ if err != nil {
740
+ return errors .Wrap (err , "encoding import-values request" )
741
+ }
742
+ err = c .DoImportValues (index , shard , path , data )
743
+ return errors .Wrap (err , "doing import values" )
744
+ }
745
+
746
+ // EncodeImportValues computes the HTTP path and payload for an
747
+ // import-values request. It is typically followed by a call to
748
+ // DoImportValues.
749
+ func (c * Client ) EncodeImportValues (index , field string , shard uint64 , vals []int64 , ids []uint64 , clear bool ) (path string , data []byte , err error ) {
750
+ msg := & pbuf.ImportValueRequest {
751
+ Index : index ,
752
+ Field : field ,
753
+ Shard : shard ,
754
+ ColumnIDs : ids ,
755
+ Values : vals ,
756
+ }
757
+ data , err = proto .Marshal (msg )
758
+ if err != nil {
759
+ return "" , nil , errors .Wrap (err , "marshaling to protobuf" )
760
+ }
761
+ path = fmt .Sprintf ("/index/%s/field/%s/import?clear=%s&ignoreKeyCheck=true" , index , field , strconv .FormatBool (clear ))
762
+ return path , data , nil
763
+ }
764
+
765
+ // DoImportValues takes a path and data payload (normally from
766
+ // EncodeImportValues), logs the import, finds all nodes which own
767
+ // this shard, and concurrently imports to those nodes.
768
+ func (c * Client ) DoImportValues (index string , shard uint64 , path string , data []byte ) error {
769
+ c .logImport (index , path , shard , false , data )
770
+
771
+ uris , err := c .getURIsForShard (index , shard )
772
+ if err != nil {
773
+ return errors .Wrap (err , "getting uris" )
774
+ }
775
+
776
+ eg := errgroup.Group {}
777
+ for _ , uri := range uris {
778
+ eg .Go (func () error {
779
+ return c .importData (uri , path , data )
780
+ })
781
+ }
782
+ return errors .Wrap (eg .Wait (), "importing values to nodes" )
783
+ }
784
+
644
785
func importPathData (field * Field , shard uint64 , msg proto.Message , options * ImportOptions ) (path string , data []byte , err error ) {
645
786
data , err = proto .Marshal (msg )
646
787
if err != nil {
@@ -704,6 +845,18 @@ func (c *Client) importData(uri *URI, path string, data []byte) error {
704
845
return nil
705
846
}
706
847
848
+ // ImportRoaringBitmap can import pre-made bitmaps for a number of
849
+ // different views into the given field/shard. If the view name in the
850
+ // map is an empty string, the standard view will be used.
851
+ func (c * Client ) ImportRoaringBitmap (field * Field , shard uint64 , views map [string ]* roaring.Bitmap , clear bool ) error {
852
+ uris , err := c .getURIsForShard (field .index .Name (), shard )
853
+ if err != nil {
854
+ return errors .Wrap (err , "getting URIs for import" )
855
+ }
856
+ err = c .importRoaringBitmap (uris [0 ], field , shard , views , & ImportOptions {clear : clear })
857
+ return errors .Wrap (err , "importing bitmap" )
858
+ }
859
+
707
860
func (c * Client ) importRoaringBitmap (uri * URI , field * Field , shard uint64 , views viewImports , options * ImportOptions ) error {
708
861
protoViews := []* pbuf.ImportRoaringRequestView {}
709
862
for name , bmp := range views {
@@ -971,7 +1124,10 @@ func (c *Client) doRequest(host *URI, method, path string, headers map[string]st
971
1124
}
972
1125
err = errors .New (strings .TrimSpace (string (content )))
973
1126
}
974
- c .logger .Printf ("request failed with: %s, retrying (%d)" , err .Error (), tries )
1127
+ if tries == 0 {
1128
+ break
1129
+ }
1130
+ c .logger .Printf ("request failed with: %s status: %d, retrying after %d more time(s) after %v " , err .Error (), resp .StatusCode , tries , sleepTime )
975
1131
time .Sleep (sleepTime )
976
1132
sleepTime *= 2
977
1133
if sleepTime > c .maxRetrySleepTime {
@@ -1023,7 +1179,7 @@ func (c *Client) augmentHeaders(headers map[string]string) map[string]string {
1023
1179
return headers
1024
1180
}
1025
1181
1026
- func (c * Client ) translateRowKeys (field * Field , keys []string ) ([]uint64 , error ) {
1182
+ func (c * Client ) TranslateRowKeys (field * Field , keys []string ) ([]uint64 , error ) {
1027
1183
req := & pbuf.TranslateKeysRequest {
1028
1184
Index : field .index .name ,
1029
1185
Field : field .name ,
@@ -1032,7 +1188,7 @@ func (c *Client) translateRowKeys(field *Field, keys []string) ([]uint64, error)
1032
1188
return c .translateKeys (req , keys )
1033
1189
}
1034
1190
1035
- func (c * Client ) translateColumnKeys (index * Index , keys []string ) ([]uint64 , error ) {
1191
+ func (c * Client ) TranslateColumnKeys (index * Index , keys []string ) ([]uint64 , error ) {
1036
1192
req := & pbuf.TranslateKeysRequest {
1037
1193
Index : index .name ,
1038
1194
Keys : keys ,
0 commit comments