Compare commits

...

1673 commits
master ... v-ci

Author SHA1 Message Date
Cody Soyland
dca19f7e94
Add new Docker build process and continuous delivery 2021-02-26 14:34:14 -06:00
Ben Johnson
eff313d181
Merge pull request #1474 from molecula/bench-autogenerate
Add benchmarking of autogenerated ID data
2021-02-26 12:39:36 -07:00
Ben Johnson
90278b206c Add benchmarking of autogenerated ID data 2021-02-26 10:37:15 -07:00
Ben Johnson
4454df0af0
Merge pull request #1454 from molecula/remove-rbftx-frag
Remove unused RBFTx.frag field
2021-02-23 09:42:50 -07:00
Ben Johnson
02de222387
Merge branch 'master' into remove-rbftx-frag 2021-02-23 08:56:45 -07:00
Alan Bernstein
2641e57ddb
Merge pull request #1450 from alanbernstein/dont-limit-before-sort
CORE-182 Add ignoreLimit argument to executeGroupByShard
2021-02-23 09:33:07 -06:00
Ben Johnson
5e477e107a Remove unused RBFTx.frag field 2021-02-23 08:32:07 -07:00
Alan Bernstein
f228a35d52 Add ignoreLimit argument to executeGroupByShard 2021-02-22 16:16:11 -06:00
tgruben
0170167bdf
Merge pull request #1447 from tgruben/cleanup
Provide option to update existence field on import roaring request
2021-02-22 15:36:50 -06:00
Todd Gruben
081d86c04b mixed row test 2021-02-22 15:06:45 -06:00
Todd Gruben
d220426686 doc comment 2021-02-22 11:50:01 -06:00
tgruben
18e31a63b6
Update roaring/roaring.go
Co-authored-by: Matthew Jaffee <matthew.jaffee@gmail.com>
2021-02-22 11:42:48 -06:00
Todd Gruben
8e27e55459 Merge branch 'cleanup' of github.com:tgruben/privilosa into cleanup 2021-02-22 11:38:02 -06:00
Todd Gruben
5cbbb7996e jaffee test handling suggestions 2021-02-22 11:37:30 -06:00
tgruben
c2da1d2671
Update api.go
Co-authored-by: Matthew Jaffee <matthew.jaffee@gmail.com>
2021-02-22 11:07:41 -06:00
tgruben
1b87b7148e
Update api.go
Co-authored-by: Matthew Jaffee <matthew.jaffee@gmail.com>
2021-02-22 11:06:55 -06:00
Todd Gruben
155a4b4a31 comment clarification 2021-02-22 10:07:32 -06:00
Todd Gruben
9e186669d8 update go mod to fix cors bug 2021-02-22 08:33:52 -06:00
Todd Gruben
4e3beb0d10 Provide option to update existence on import roaring 2021-02-19 18:08:25 -06:00
Todd Gruben
6219b4ca8b add optional UpdateExistence on importRoaring 2021-02-19 17:47:28 -06:00
Todd Gruben
5b43a84a3b . 2021-02-19 17:09:50 -06:00
nagamocha3000
4dfd713189
Merge pull request #1395 from nagamocha3000/fix-core-78
CORE-78 Add parsing for partial time inputs
2021-02-16 00:18:36 +03:00
nagamocha3000
0927240956 Add parsing for partial time inputs 2021-02-15 22:55:35 +03:00
Alan Bernstein
06503813c5
Merge pull request #1420 from alanbernstein/fix-schema-null-fields
Represent zero fields in /schema response as [] instead of null
2021-02-13 06:40:36 -06:00
Alan Bernstein
f758a04e18 Represent zero fields in /schema response as [] instead of null 2021-02-12 19:40:42 -06:00
Matthew Jaffee
421848c86a
Merge pull request #1418 from molecula/fix-lattice
update lattice version to correct from accidental downgrade
2021-02-12 16:16:34 -06:00
Matt Jaffee
0e3461e9f4
update lattice version to correct from accidental downgrade 2021-02-12 14:50:10 -06:00
Alan Bernstein
a75c62c90f
Merge pull request #1391 from alanbernstein/field-cardinality
CORE-92 Add /schema/details endpoint, which includes field cardinality
2021-02-12 09:38:42 -06:00
Alan Bernstein
882b14444b Use consistent behavior in test to add queries to transaction 2021-02-11 16:42:30 -06:00
Alan Bernstein
dc6c92771b Fail entire /schema/details request if one field query fails 2021-02-11 16:42:30 -06:00
Alan Bernstein
2773999190 Simplify response structs 2021-02-11 16:42:30 -06:00
Alan Bernstein
12462886a6 Shorten shard lists in error messages 2021-02-11 16:42:30 -06:00
Alan Bernstein
246c345c49 Eliminate omitempty from /schema/details response type 2021-02-11 16:42:30 -06:00
Alan Bernstein
56c940c6a0 Add /schema/details endpoint, which includes field cardinality computed via Count(Distinct()) 2021-02-11 16:42:30 -06:00
Maxton Huff
2e6a8e2030
Merge pull request #1393 from Maxtonian/30trial
CORE-140 add draft trial version of molecula
2021-02-11 16:38:53 -06:00
Maxton Huff
f8fc69670c Merge branch '30trial' of github.com:Maxtonian/pilosa into 30trial 2021-02-11 16:19:09 -06:00
Maxton Huff
b89dae01cc clarify purpose of trialVersion by changing the name to handleTrialDeadline and adding a doc string 2021-02-11 16:13:04 -06:00
Maxton Huff
d5b96ab5ce
Merge branch 'master' into 30trial 2021-02-11 11:02:21 -06:00
Maxton Huff
136a4b8068 change wording 2021-02-11 11:01:16 -06:00
Maxton Huff
dd8d6ebfb1 Respond to code review feedback
change logic in version.go so that the trial related messages only appear on trial versions of molecula
convert Command methods in trial.go to functions and  pass a loggerLogger variable instead since that was the only piece of Command being used
add a function named expireAfter which seperately runs similar functionality to what was previously in daily check with chnages directed at stopping users from changing their internal clock date
change variable names and placement to be more readable and organized
2021-02-11 11:01:16 -06:00
Maxton Huff
ddcf9fe1e8 add licence header 2021-02-11 11:01:16 -06:00
Maxton Huff
c973fae6aa remove remaining release-build-trial 2021-02-11 11:01:16 -06:00
Maxton Huff
c60893a8ee edit and move trial code from server.go to trial.go and combine release build targets 2021-02-11 11:01:16 -06:00
Maxton Huff
7fd3fc775f remove test code 2021-02-11 11:01:16 -06:00
Maxton Huff
d02ba32743 alter error output to satisfy test 2021-02-11 11:01:15 -06:00
Maxton Huff
9840ae67e4 remove error return type 2021-02-11 11:01:15 -06:00
Maxton Huff
1dbab1ff92 add draft trial version of molecula 2021-02-11 11:01:15 -06:00
Maxton Huff
d357447673 change wording 2021-02-11 10:59:32 -06:00
Maxton Huff
0f952bb594 Respond to code review feedback
change logic in version.go so that the trial related messages only appear on trial versions of molecula
convert Command methods in trial.go to functions and  pass a loggerLogger variable instead since that was the only piece of Command being used
add a function named expireAfter which seperately runs similar functionality to what was previously in daily check with chnages directed at stopping users from changing their internal clock date
change variable names and placement to be more readable and organized
2021-02-11 10:36:04 -06:00
Matthew Jaffee
a781d472b8
Merge pull request #1415 from molecula/revert-1412-import-roaring-existence
Revert "Update existence field on import-roaring requests"
2021-02-10 18:40:59 -06:00
tgruben
a61ed011fc
Revert "Update existence field on import-roaring requests" 2021-02-10 14:51:11 -06:00
tgruben
a41a65d7bd
Merge pull request #1412 from tgruben/import-roaring-existence
Update existence field on import-roaring requests
2021-02-10 13:03:54 -06:00
Todd Gruben
e7f272f37f updates existence field on importroaring fixes issue (1411) 2021-02-10 12:43:27 -06:00
Maxton Huff
302093035e add licence header 2021-02-10 12:37:13 -06:00
Maxton Huff
4bcdfcc9a2 remove remaining release-build-trial 2021-02-10 08:21:36 -06:00
Maxton Huff
7ae1599c12 edit and move trial code from server.go to trial.go and combine release build targets 2021-02-09 15:39:32 -06:00
Cody Soyland
f1f7bd6327
Merge pull request #1406 from codysoyland/upgrade-lattice
Upgrade lattice
2021-02-09 15:19:04 -06:00
Cody Soyland
0227d8306f
Upgrade lattice 2021-02-09 15:00:11 -06:00
Maxton Huff
c967bc96f4 remove test code 2021-02-05 15:40:55 -06:00
Maxton Huff
f37ec5b425 alter error output to satisfy test 2021-02-05 14:25:57 -06:00
Maxton Huff
fb47cb875a remove error return type 2021-02-05 13:40:57 -06:00
Maxton Huff
fec0337630 add draft trial version of molecula 2021-02-05 12:16:08 -06:00
Alan Bernstein
8e637cc34a
Merge pull request #1390 from alanbernstein/memory-usage
CORE-162 Add memory info to /ui/usage response
2021-02-03 16:36:57 -06:00
Alan Bernstein
6c139935f7 Add memory info to /ui/usage response 2021-02-03 14:18:29 -06:00
nagamocha3000
52ab7e5a99
Merge pull request #1379 from nagamocha3000/fix-core-26
Close process on fragment.openStorage error
2021-02-03 19:18:58 +03:00
nagamocha3000
92426a9d1b Close process on fragment.openStorage error
When *fragment.openStorage is invoked in both f.importValue and
f.importValueSmallWrite and it returns an error, this means there's
some underlying error with the storage device and at the point of this
commit, the sane thing to do is to close the process, otherwise the
operation of Pilosa might proceed in an inconsistent state thus
precipiatting other silent but hairy errors along the way such as
dereferencing *fragment.gen later on which is set to nil once
openStorage fails.
2021-02-03 18:47:10 +03:00
Ben Johnson
c75d5553b9
Merge pull request #1373 from molecula/bench
Update keyed/unkeyed benchmarks
2021-02-01 14:39:21 -07:00
Ben Johnson
0900e7b9d2
Merge branch 'master' into bench 2021-02-01 14:10:15 -07:00
Cody Soyland
a057cc9899
Merge pull request #1375 from codysoyland/duration-header
CORE-27 Add duration header to all gRPC query results
2021-02-01 11:55:55 -06:00
Cody Soyland
0e22ed71cc
Fix instances of context.Background that need mocked context 2021-02-01 11:40:45 -06:00
Cody Soyland
c30e3f0c2d
Move duration header to fix error handling 2021-02-01 11:40:45 -06:00
Cody Soyland
e2331372d8
Handle errors 2021-02-01 11:40:45 -06:00
Cody Soyland
367425bba1
Add duration header to all gRPC query results 2021-02-01 11:40:43 -06:00
Alan Bernstein
51e41ee6b2
Merge pull request #1331 from alanbernstein/field-usage
Show disk usage broken down by field and keys
2021-02-01 11:31:29 -06:00
Alan Bernstein
e981b162f0 Upgrade lattice 2021-01-29 17:48:24 -06:00
Alan Bernstein
1e7c4d7e8e Include metadata AKA 'other' in response 2021-01-29 17:48:24 -06:00
Alan Bernstein
5beb2664b0 Use simpler test 2021-01-29 17:48:24 -06:00
Alan Bernstein
c81ea88847 Fix total summation 2021-01-29 17:48:24 -06:00
Alan Bernstein
15443b371c Force consistent timestamp width in startup log 2021-01-29 17:48:24 -06:00
Alan Bernstein
5dc00883bd Add more involved diskUsage test 2021-01-29 17:48:24 -06:00
Alan Bernstein
703bd14048 Fix errors in usage check 2021-01-29 17:48:24 -06:00
Alan Bernstein
85fad859e2 Correct some disk usage computations 2021-01-29 17:48:24 -06:00
Ben Johnson
32a35805a4 Add RBF index/field usage stats 2021-01-29 17:48:24 -06:00
Alan Bernstein
e397d35ed5 Include roaring field and key details in usage endpoint 2021-01-29 17:48:24 -06:00
Ben Johnson
ea653eb9a4
Merge branch 'master' into bench 2021-01-29 12:46:26 -07:00
Nia
20d55f0805
Merge pull request #1369 from niaow/count-global
CORE-29 Invoke pre-calls directly in count operations (fixes Count(Distinct()) on negative integers)
2021-01-29 11:20:29 -05:00
Nia
2961a69bd2
Merge branch 'master' into count-global 2021-01-29 11:14:02 -05:00
Alan Bernstein
1f87b54e5f
Merge pull request #1361 from alanbernstein/distinct-crash
CORE-32 Handle nil result in signed row translation
2021-01-29 10:13:48 -06:00
Nia
13db91379f
Merge branch 'master' into count-global 2021-01-29 10:57:46 -05:00
Alan Bernstein
c4455acbd8 Use inconsistent JSON schema to reach Distinct translation error condition 2021-01-29 09:39:31 -06:00
Alan Bernstein
4fba6bea82 Prevent nil pointer exception during Distinct key translation 2021-01-29 09:39:01 -06:00
Ben Johnson
60b6eecc7a Update keyed/unkeyed benchmarks 2021-01-28 16:36:45 -07:00
Maxton Huff
4b33a7fe16
Merge pull request #1368 from Maxtonian/mmap
Mmap limit comparison warning message for Linux
2021-01-27 10:33:43 -06:00
Maxton Huff
7d82c55274
Merge branch 'master' into mmap 2021-01-27 10:15:50 -06:00
Maxton Huff
927db378b4 add linux OS check and the way mmap limit is read 2021-01-27 09:52:13 -06:00
Nia Weiss
0d97ec559b
switch signed count distinct test to use TestVariousQueries 2021-01-27 10:29:40 -05:00
Matthew Jaffee
f84f43ec64
Merge pull request #1370 from travisturner/distinctset-empty-row
Return zero-bit row (with Index/Field) instead of nil in executeDistinctShardSet
2021-01-26 20:50:38 -06:00
Travis
48ac989e6a
Return zero-bit row (with Index/Field) instead of nil in executeDistinctShardSet 2021-01-26 15:47:28 -06:00
Nia Weiss
ac09c11bad
Invoke precalls directly in count operations
This changes Count(Precall()) operations to execute the precall directly inside of the count operation, bypassing the transformation to a Precomputed() call.
Eliminating the Precomputed() step causes Count(Distinct()) to work properly on negative integers.
2021-01-26 12:44:03 -05:00
Maxton Huff
8c36da9eeb Merge branch 'mmap' of github.com:Maxtonian/pilosa into mmap 2021-01-26 11:27:12 -06:00
Maxton Huff
3982a8e970 format messages and change mmap comparison logic 2021-01-26 11:26:36 -06:00
Maxton Huff
bd680ef144
Merge branch 'master' into mmap 2021-01-26 10:21:18 -06:00
seebs
89f1dfb239
Merge pull request #1367 from seebs/countRange
RBF countRange: Handle partial counts on bitmapPtr containers
2021-01-26 09:51:09 -06:00
Maxton Huff
3809fe6734 add error check 2021-01-26 09:35:51 -06:00
Maxton Huff
55a9952b92 mmap limit comparison error message 2021-01-25 16:19:12 -06:00
Seebs
a238afb21a Handle BitmapPtr cells in countRange
We need to be able to count bits in BitmapPtr containers. This only
comes up if you have a non-container-aligned range count, which we
never do in real production yet, but the API allows it so it should
work. In order to do this, we need to provide the tx to countRange
so it can grab pages as needed. Arguably, we should be able to avoid
actually creating/copying that page since we're only using it
internally, never returning it, but this is a pretty rare case
and probably not performance-critical.
2021-01-25 15:41:35 -06:00
Seebs
d1a9c91a96 Test CountRange on non-container ranges.
This test was supposed to check against all the container types,
but especially bitmaps, but turns out not to work because the
containers turn into RLE containers. Oops. Now, we start with
every-other-bit for the first 8k, then start filling in the holes,
so we get some bitmap containers and then start generating
RLE containers.
2021-01-25 15:41:35 -06:00
Nia
13c63baa14
Merge pull request #1365 from niaow/groupby-cluster-rows
CORE-28 Fix GroupBy with a global Rows filter
2021-01-25 15:37:48 -05:00
Nia
60ab9e84dd
Merge branch 'master' into groupby-cluster-rows 2021-01-25 15:26:13 -05:00
Nia
ed9fc962d5
Merge pull request #1359 from niaow/intersectany-single-word-run
CORE-31 Fix intersectionAnyRunBitmap when processing single-word runs
2021-01-25 11:16:30 -05:00
Nia
70d87a7950
Merge branch 'master' into intersectany-single-word-run 2021-01-25 11:03:51 -05:00
Nia Weiss
dde318ac8c
Move globally computed GroupBy rows calls into EmbeddedData
This fixes a bug where a globally computed Rows call would be computed with a subset of the shards.
2021-01-25 10:17:58 -05:00
Ben Johnson
66bc79634b
Merge pull request #1363 from molecula/pilosa-bench-vars
Add ingest time & latency stats to query benchmarks
2021-01-25 08:16:34 -07:00
Cody Soyland
649202b081
Fix cluster size setter: expose failures 2021-01-25 10:16:19 -05:00
Ben Johnson
a30fd9957e
Merge branch 'master' into pilosa-bench-vars 2021-01-25 07:56:20 -07:00
Cody Soyland
3da3d23b21
Merge pull request #1364 from codysoyland/lattice-submodule
Add git-submodule to manage UI version
2021-01-22 16:57:57 -06:00
Alan Bernstein
852057533b Add lattice submodule upgrade instructions 2021-01-22 16:51:17 -06:00
Cody Soyland
671c7262c9
Add lattice to PHONY 2021-01-22 16:26:09 -06:00
Cody Soyland
34f728521c
Add git-submodule to manage lattice version 2021-01-22 15:05:19 -06:00
Ben Johnson
acdff02fed Add ingest time & latency stats to query benchmarks 2021-01-22 11:41:44 -07:00
nagamocha3000
6c3c460c83
Merge pull request #1344 from nagamocha3000/bnm-fix-1080
Fix filtering on time fields in Rows() embedded within GroupBy
2021-01-22 19:56:08 +03:00
nagamocha3000
b5fdc6609a Update tests to remove sum column on GroupBy 2021-01-22 18:44:36 +03:00
Nia Weiss
0d9179f10c
roaring: fix intersectionAnyRunBitmap when processing single-word runs
When a run started and ended within a single word, the entirety of the word would be checked.
This would cause small runs to be processed incorrectly, and caused Distinct-on-sets to select rows that did not match the specified filter.
2021-01-21 18:09:27 -05:00
nagamocha3000
16b5fbe40b
Merge branch 'master' into bnm-fix-1080 2021-01-21 17:37:08 +03:00
nagamocha3000
6cbcde3a82 Add more tests for GroupBy on Time fields 2021-01-21 16:20:46 +03:00
nagamocha3000
113bec2474 Remove unnecessary nil check 2021-01-21 15:48:52 +03:00
Alan Bernstein
b273f3ba60
Merge pull request #1327 from alanbernstein/sql-history
Include SQL string in query-history
2021-01-20 19:05:36 -06:00
Alan Bernstein
eaf3ef153e Test SQL behavior 2021-01-20 15:58:55 -06:00
Alan Bernstein
bed03ba490 Update tests 2021-01-20 10:48:35 -06:00
Alan Bernstein
a0d6c253d1 Pass SQL query string from mapper to tracker 2021-01-20 10:48:35 -06:00
Cody Soyland
06241d1f84
Merge pull request #1258 from codysoyland/distinct-failure
Test to demonstrate failure running Distinct
2021-01-20 10:37:47 -06:00
Cody Soyland
0a325dcdb8
Merge branch 'master' into distinct-failure 2021-01-19 16:50:25 -06:00
seebs
38ded7963e
Merge pull request #1342 from seebs/aggregate
Handle aggregate functions better in sql/grpc/json
2021-01-19 16:28:32 -06:00
Seebs
932e84b681 handling aggregate types: add to protobuf, etc
We want to distinguish different *kinds* of GroupCounts, so we're
making the GroupCounts parent object track its type so we can keep that
correct.

Adding this to protobuf, etc, then creates some weird behaviors
because sometimes we expect []GroupCount, and sometimes we expect
*GroupCounts. This implies changes to test cases. Also, the
changes to test cases imply that some test cases are probably now
wrong; for instance, they're expecting a "sum" column, equal to zero,
when no sum was requested.

We try to make the encoder handle a []*GroupCount gotten from another
node without panicing, and avoid breaking the semantics of the existing
messages, renumbering messages or components, etc.

Since a previous version, the `.Groups` member has been privatized,
and the `.Get()` convenience accessor has been renamed `.Groups()`
and is now used consistently in a way that should reduce the risk
of nil pointers causing crashes. Also, NewGroupCounts is used in
a couple more places.
2021-01-19 16:23:15 -06:00
Cody Soyland
4ebf6f6ff7 Customize serialization of []GroupCount based on aggregate type/presence 2021-01-19 12:10:56 -06:00
nagamocha3000
0437f5d28a Handle case where row returned might be nil 2021-01-19 16:51:26 +03:00
Ben Johnson
56c001d288
Merge pull request #1341 from molecula/bench-vars
Update benchmarks to use expvar URL list
2021-01-18 16:06:43 -07:00
nagamocha3000
d26c6b048e Sort all rowIDs gathered before storing them 2021-01-19 02:00:50 +03:00
nagamocha3000
9b23dcdd0a Gather rows for each fragment in a much smarter way 2021-01-19 01:44:54 +03:00
Ben Johnson
01e6781abd Update benchmarks to use expvar URL list 2021-01-18 13:43:53 -07:00
nagamocha3000
f51ff4dc85 Add tests for Rows() on time fields 2021-01-18 20:54:31 +03:00
nagamocha3000
b77f9e8a43 Add timeFragments rowIterator 2021-01-15 23:36:54 +03:00
nagamocha3000
f91b4bc016 Add queries to test GroupBy Rows on time field 2021-01-15 22:58:18 +03:00
nagamocha3000
54d79d1e4d Populate 'places_visited' field for 'users' index 2021-01-15 22:50:54 +03:00
nagamocha3000
4dc3e49a0d Add test helper for inserting to time quantum fields 2021-01-15 22:42:37 +03:00
Ben Johnson
fbf546f131
Merge pull request #1319 from molecula/pilosa-bench 2021-01-12 16:51:27 -07:00
Ben Johnson
b7973e2612 Add read benchmarks 2021-01-12 13:48:09 -07:00
Matthew Jaffee
701d6448cc
Merge pull request #1307 from jaffee/1292-panic-groupby-int-distinct-aggregate
Fix potential panic when grouping on int field with Distinct aggregate
2021-01-08 16:38:00 -06:00
Matt Jaffee
48552553dc
guard against NPE when setting precomputed data
If a precomputed call returns a nil Row result somehow, that could
cause a nil pointer exception when handling the result in
handlePreCall.

In this particular case, A Distinct call on a BSI field with a filter
which returned no results could return a SignedRow{} with nil *Rows
inside of it. This only manifested if there was data in a single shard
as otherwise the reduce logic created a SignedRow with empty *Row
objects rather than nil ones. Isn't that fun?

Extra fun: the reason the filter was returning no results was not
because it was actually empty, but because of another bug where
constructing the Distinct calls to compute the aggregate of a GroupBy
doesn't take into account that the group might include an integer
field which means that the call needs to be constructed
differently. That bug is not fixed in this commit, hence the tests are
still failing, but not panicking.
2021-01-08 15:03:49 -06:00
Matthew Jaffee
616ba408f8
Merge pull request #1308 from codysoyland/groupby-distinct-int
Fix GroupBy Distinct aggregate on int field
2021-01-08 13:55:38 -06:00
Cody Soyland
83ce30f1c4
Fix GroupBy Distinct aggregate on int field 2021-01-08 13:35:58 -06:00
Ben Johnson
55fad52c59
Merge pull request #1306 from molecula/rbf-remove-cursor-arena
Remove RBF cursor arena.
2021-01-08 10:13:52 -07:00
Ben Johnson
489f33a173 Remove RBF cursor arena.
Previously there were two implementations of cursor reuse: sync pool &
an arena. This commit removes the arena in favor of the global pool.
2021-01-08 07:59:10 -07:00
Ben Johnson
1865de8248
Merge pull request #1301 from molecula/fix-rbf-cursor-close 2021-01-07 19:22:34 -07:00
jaten-molecula
b3854e867b
Merge branch 'master' into fix-rbf-cursor-close 2021-01-07 20:14:59 -06:00
Cody Soyland
476767f150
Merge pull request #1285 from codysoyland/grpc-crd
Add create, read, and delete index methods to gRPC interface
2021-01-07 15:24:13 -06:00
Cody Soyland
4f57b4d07b
Remove TrackExistence configuration from gRPC CreateIndex 2021-01-07 15:19:08 -06:00
Cody Soyland
d8ebfda1bd
Undo error cause changes due to broken logic in other places, check for ConflictError explicitly 2021-01-07 14:49:18 -06:00
Cody Soyland
219714a18e
Fix linter problems 2021-01-07 13:50:59 -06:00
Cody Soyland
daa8c9bd8b
Add tests for new gRPC create/get/delete calls 2021-01-07 13:05:48 -06:00
Cody Soyland
fdf5818fc2
Add create, read, and delete index methods to gRPC interface 2021-01-07 13:05:48 -06:00
Ben Johnson
93f06e0f9d Fix rbf.Cursor.Close() panic 2021-01-07 11:28:46 -07:00
Maxton Huff
17e010cca2
Merge pull request #1293 from Maxtonian/verbose-message
Fix formatting bug in verbose log message
2021-01-07 09:38:29 -06:00
Maxton Huff
d10e45648b change f.path to f.path() 2021-01-06 15:58:01 -06:00
Matthew Jaffee
d6cba17a01
Merge pull request #1274 from jaffee/generalized-groupby-sort-2
Add ability to sort on count or aggregate in GroupBy. Fix bug with offset being unsupported. Fix bugs with limit interacting poorly with other arguments.
2021-01-05 09:41:18 -07:00
Matt Jaffee
216e28a77e
add getSorter tests, fix bugs 2021-01-05 09:54:46 -06:00
Matt Jaffee
6dccb3d6be
remove (unused) sorting code related to fields, add comments 2021-01-03 08:34:53 -06:00
Matt Jaffee
6094663e7a
fix bug with "having" and "limit" in GroupBy
the limit could get applied before "having" in some cases which could
result in results being discarded which met the having condition while
results were kept which did not, ultimately resulting in GroupBy
falsely reporting fewer results than actually existed.
2021-01-01 21:56:59 -06:00
Matt Jaffee
ea539d8241
simplify groupby sorting and fix bugs
Back out support for sorting on fields (only count and aggregate
supported for now).

Fix bug where default return of "true" caused sort to be unstable. (If
they are equal, Less should return false)

Fix bug where limit was being applied before sorting.

Fix bug where offset was not actually allowed to be an argument to
GroupBy (weird! guess we weren't testing that very well)

Apply "having" after calculating Count(Distinct) aggregate so that
having can apply to that.

Switch to stable sort to make testing easier.
2020-12-31 14:42:03 -06:00
Matt Jaffee
5fdae74812
draft of sorting groupby results 2020-12-31 14:41:52 -06:00
Matthew Jaffee
46bbff786e
Merge pull request #1245 from codysoyland/groupby-aggregate-distinct
Add Distinct call as GroupBy aggregate
2020-12-30 10:05:37 -07:00
Matt Jaffee
16fd6a7edd
add tests for GroupBy(Distinct), fix various problems
We execute the aggregate Distinct calls after the GroupBy is complete,
and we need these to act like non-remote calls in that they forward to
all nodes, but like remote calls in that they bypass key
translation. Added a "PreTranslated" flag to the QueryRequest to
achieve this.

Discovered an issue where a nil *Row in EmbeddedData would cause a
panic in the protobuf serialization. Changed the encoding code we
control to never pass a nil *Row.

Got fed up with lack of context on errors and added wrapping to all
calls under executor.executeCall as well as a few other places.

Handled a situation where not having data on a shard for a particular
field could cause a query to error instead of just treating that
fragment as being empty. (see the switch in executeDistinctShardSet)

Stopped GroupBy from executing the Count(Distinct) aggregate on Remote
calls.

Fixed a longstanding issue where errors retrieved from remote query
calls had a garbage character at the front due to treating a protobuf
payload as an error message instead of decoding it. (see
http/client.go)
2020-12-30 08:13:41 -06:00
Cody Soyland
fd7417a49b
Use shardwidth instead of hardcoded value 2020-12-30 08:12:09 -06:00
Cody Soyland
f099a90264
Modify aggregate distinct logic and add tests
Use execute instead of directly using executeCount
Address code review feedback (add additional filters if provided)
Add basic tests
2020-12-30 08:12:09 -06:00
Cody Soyland
b435e9d793
Add Distinct call as GroupBy aggregate 2020-12-30 08:12:08 -06:00
Matthew Jaffee
fd3a423342
Merge pull request #1248 from jaffee/make-tests-more-robust
fix test failures in case of running Pilosa on system
2020-12-29 15:46:19 -07:00
Matt Jaffee
bed2cffd5e
fix test failures in case of running Pilosa on system
If you're running a Pilosa with mostly default configuration on your
system, some of these tests would fail due to things like port
conflicts. These changes address the most common failures.
2020-12-29 13:57:18 -06:00
Cody Soyland
1fa2a65c0b
Test to demonstrate failure running Distinct 2020-12-28 16:50:06 -06:00
Matthew Jaffee
4447d6fa76
Merge pull request #1261 from jaffee/some-distinct-bugs
fix Count(Distinct) bug and add better tests
2020-12-28 15:24:50 -07:00
Matt Jaffee
1ed91ebad3
simplify error messages
also remove test which was accidentally committed
2020-12-28 16:06:40 -06:00
Matt Jaffee
790bea147f
make view and fragment not found errors constant
based on code review feedback
2020-12-28 15:59:49 -06:00
Matt Jaffee
a3b07ff519
re-add log line which has more utility than I thought
From Nia:
While debugging the Q2 bugs this was somewhat useful in analyzing cluster events. As for the spammy part. . . that seems to be more of an issue with spamming our resets than an issue with the log itself.
2020-12-28 15:30:38 -06:00
Matt Jaffee
21c67352af
remove paranoia mode in top level Pilosa 2020-12-28 15:25:13 -06:00
Matt Jaffee
757c8a86b5
add comments, simplify tests, move ToCSV code
generally, address code review feedback
2020-12-28 15:22:31 -06:00
Matt Jaffee
27ca9dab36
don't hide error getting sign bitmap 2020-12-28 11:14:04 -06:00
Matt Jaffee
446950979a
fix potential nil dereference in SignedRow.ToRows
This used to be possible to hit, but I think now that Distinct on a
set field returns a *Row rather than a SignedRow it isn't an issue. (I
wasn't able to trigger it in the tests). Adding the fix anyway as it
seems safer than not.

The rest of the changes are test infrastructure to make it easy to
call GRPC queries and verify the results as CSV.
2020-12-28 11:00:46 -06:00
Matt Jaffee
1372bafe02
fix bugs where row index and field weren't always being propagated
I used a "paranoia" check to find these, but then realized the check
had a ton of false positives and doing it properly wasn't going to be
straightforward. I'm leaving the paranoia stuff in unless there are
objections, because I've wanted it before and not had it.

I also removed a log line that is very verbose and I don't think helps
anyone.
2020-12-23 19:08:10 -06:00
Matt Jaffee
427e9cb538
fix executor tests which were expecting a signedrow from Distinct 2020-12-23 15:33:15 -06:00
Matt Jaffee
6ff6fa7bb8
fix comments/capitalization 2020-12-23 14:44:18 -06:00
Matt Jaffee
9ee5f52a11
fix some Distinct key translation issues (e.g. empty index)
This commit changes executeDistinct to return either a *Row or a
SignedRow (instead of only being able to return a SignedRow). Distinct
on a set field will return a *Row while an int field will still return
a signed row.

We then add Field and Index fields to the Row object so that we can
determine how to translate the rows IDs to keys (if needed). This adds
a lot of logic around the translation which fixes bugs where Distinct
calls would fail to get translated.

There are, I think, still issues if you were to try to join a keyed
field to a keyed index which wasn't explicitly specified as the
field's foreign index. The IDs in the field wouldn't be using the same
translation as the IDs in the index, so the query might appear to work
but give incorrect results.
2020-12-23 14:37:03 -06:00
Matt Jaffee
385381e5f3
fix issue where a shard with no data can cause query to fail
add Distinct test with integer data, and because one of the records
had a null value (and was in a shard by itself), it uncovered this
issue. I added a special error type if a view or fragment is not found
when so that we can match against it and ignore it when calculating
the results for a query.

I also added an implementation within executeCount to handle the
SignedRow case, but discovered that handlePrecalls always dumps the
negative data and that will be a bigger thing to fix
2020-12-23 14:37:03 -06:00
Matt Jaffee
121f3fb610
fix Count(Distinct) bug and add better tests
the Distinct call would get precomputed correctly, but then the
executeCount would happen in the available shards context of the
index. So if the index only had records in (e.g.) shards 10,12,18,22,
and all the values of the Distinct call were in shard 0, you'd see 0
results.

The fix skips the whole map/reduce step of executeCount (which was
basically fake anyway when the argument is precomputed), and just adds
up all counts of all the precomputed segments.

This currently won't properly count Distinct values from an int field
which contains negative numbers... going to add a test and fix for
that next.

There is also still a key translation bug which is why the one test
case is commented out... fix coming for that soon as well.
2020-12-23 14:37:03 -06:00
Ben Johnson
fc7f7d6a7c
Merge pull request #1260 from molecula/bench-1month 2020-12-22 09:42:55 -07:00
Ben Johnson
02c4c1dff4
Merge branch 'master' into bench-1month 2020-12-22 08:51:18 -07:00
Ben Johnson
c189cb8e81
Merge pull request #1259 from molecula/bench-chmod-x
Make scripts/bench.sh executable
2020-12-22 08:51:03 -07:00
Ben Johnson
64f2ff6b78 Change benchmark script to test against GH 1 month of data 2020-12-22 08:10:29 -07:00
Ben Johnson
34c46cf084 Make scripts/bench.sh executable 2020-12-22 07:44:43 -07:00
tgruben
4b36805190
Merge pull request #1254 from jaten-molecula/migration_speedup
smaller batches of write Tx help boost migration speed
2020-12-21 13:28:31 -06:00
Jason E. Aten
1efff0c99b smaller batches of write Tx help boost migration speed 2020-12-21 19:00:13 +00:00
tgruben
e290e13d0b
Merge pull request #1252 from jaten-molecula/minimal_view_opening
pilosa: only open views with data
2020-12-18 18:41:44 -06:00
jaten-molecula
264f4382d5
Merge branch 'master' into minimal_view_opening 2020-12-18 18:19:24 -06:00
Ben Johnson
2d35f0d96a
Merge pull request #1243 from molecula/nightly-benchmark
Add nightly benchmark script.
2020-12-18 17:18:36 -07:00
jaten-molecula
94c38a59af
Merge branch 'master' into nightly-benchmark 2020-12-18 18:09:35 -06:00
Jason E. Aten
035073555a pilosa: only open views with data
- Previously, on timequantum schemas, we would
create and open a view for the cartesian
product of every possible view and shard.

- This caused us to be very slow on re-open,
and to use lots of memory for views that
held nothing.

- This change makes startup faster, memory
use much lower, and should speed migration.
2020-12-19 00:03:31 +00:00
Matthew Jaffee
7b6c6303ce
Merge pull request #1246 from jaffee/1242-createdAtBug
Fix field "createdAt" race by sending schema changes to coordinator
2020-12-18 12:06:29 -07:00
Ben Johnson
d635ece5a9 Add workflow name to benchmark 2020-12-18 09:33:50 -07:00
Matt Jaffee
b8cbd54d1b
forward all CreateIndex/CreateField requests to coordinator
this should avoid a race condition with CreateField where createdAt
can get out of sync if there are multiple concurrent requests.

The client methods didn't allow specification of the URI, so I
modified the implementation to find the coordinator and send to it
explicitly.
2020-12-18 10:16:46 -06:00
Matt Jaffee
58b9418f3c
add failing test for field creation race 2020-12-17 16:30:23 -06:00
Ben Johnson
cfdc7f4c63 Add nightly benchmark script.
This commit adds a script for executing a nightly benchmark and posting
the results to Slack.
2020-12-17 09:23:40 -07:00
jaten-molecula
513743f30d
Merge pull request #1241 from jaten-molecula/tests_for_NewBitmapBitmapFilter
add tests for NewBitmapBitmapFilter constructor
2020-12-16 21:39:18 -06:00
Jason E. Aten
0db4914bc4 add tests for NewBitmapBitmapFilter constructor
- document sort.Stable need
2020-12-17 00:27:34 +00:00
seebs
ad35f11a27
Merge pull request #1208 from seebs/fastRows2
Performance improvements for transactional backends scanning fragments
2020-12-16 17:14:54 -06:00
jaten-molecula
5b43513063
Merge branch 'master' into fastRows2 2020-12-16 17:09:46 -06:00
Seebs
cc5e822799 fix comment, remove unneeded step
It turns out NewSliceBitmap can take an initial set of values
already.
2020-12-16 17:00:34 -06:00
Nia
26ab79ecb8
Merge pull request #1238 from niaow/fix-snapshot-queue
Fix automatic snapshot queue enable check
2020-12-16 17:01:09 -05:00
jaten-molecula
6263f0bcfd
Merge branch 'master' into fix-snapshot-queue 2020-12-16 13:45:04 -06:00
Seebs
4ddbadcea7 review issues: fix unclearSets (now sliceDifference) and prune/fullPrune
unclearSets was completely broken and I have no idea why the test I thought
was testing it didn't actually catch that problem. Added unit tests and fixed
the logic. Improved/clarified prune and fullPrune, and unexported their
names because why export methods on an unexported type.

Also improve some comments and rename a variable or two to improve clarity.
2020-12-16 13:18:18 -06:00
Seebs
0952db5af7 Add (temporary, perhaps) locking on TestTx_CountRange
On roaring, CountRange needs to have exclusive access to a fragment, but
doesn't currently require a lock, because it's usually used from inside
other already-locked things.
2020-12-16 13:18:18 -06:00
Seebs
de14762661 create Tx tests for CountRange
CountRange for RBF had a subtle bug which wasn't noticed, so, let's
have some CountRange testing and also a benchmark.

We also fix a couple of subtle bugs caught in the process of developing
and testing this.

SliceContainers will allow nil containers, but doesn't return them when
iterating because there's various things that can panic if called on a nil
container. Since countEmptyContainers() has to traverse the whole bitmap
anyway, it doesn't matter which it counts, so we replace it with
countNonEmptyContainers(), and adjust test cases accordingly. This fixes
an issue where if roaring is smart enough to insert a nil container
into a SliceContainers, trying to write it to a file produces an invalid
bitmap with offsets off by 16 and one container fewer than its header predicts.

RBF: don't try to count 0 bits in a container

If we're to the "last container", and we'd be counting all the bits less than
zero, we can skip that. This avoids hitting a bug, which is that c.countRange
doesn't handle BitmapPtr.
2020-12-16 13:16:46 -06:00
Seebs
17c24c236a don't try to use the rowCache for CountRange
Several issues:
1. tx.frag could be non-nil but not the fragment requested.
2. start and end need not be exact row boundaries.
3. therefore this could be returning the count of the row containing
"start", for a fragment other than the one requested.
4. also in fact the rowcache wasn't populated before this so in one
memory profile, this function alone was responsible for nearly
100GB of cached values...
2020-12-16 13:16:46 -06:00
Seebs
dec0a00155 add container N to ConsiderKey 2020-12-16 13:16:46 -06:00
Seebs
23e16474ae use ApplyFilter instead of roaring.ApplyFilterToIterator
In some cases, ApplyFilter can be significantly faster. On the other hand, it doesn't
matter as much as you might think on the mutex imports, because we've already sucked
most of the time out of those.
2020-12-16 13:16:46 -06:00
Seebs
e4e94a5668 prevent weird rare failures in mutex imports
Added additional mutex sample data and batches of it so we can
confirm that overwrite works. It didn't work, so that needed to be fixed.

Couple of things:
(1) Wasn't updating "last value seen" so the check for an unsorted list
didn't work.
(2) Also didn't handle the case where there were to-clear values higher
than any to-set value.

This could result in bits not getting cleared, which could result in
there being more than N bits to clear for N new bits. And that could cause
really strange problems when the input slices were parts of a single
larger slice, because bit positions to clear could get shoved in as
possible columns in a future batch.
2020-12-16 13:16:46 -06:00
Seebs
e99744c8da Do benchmarks with read-only Tx after committing write sometimes
For the Rows benchmark, we were continuing to use the original writable
transaction, meaning RBF was spending all its time looking up dirty
pages in the transaction's dirty page cache rather than working with
the disk in any way. It wasn't clear whether this was hurting or
helping performance, but it was clear that it wasn't testing the
"real" workload use case, where queries are done against the RBF
file rather than the dirty page cache.

Modify the benchmark to test it both ways for comparison. Answer:
The RBF file is faster than the in-memory map (!).
2020-12-16 13:16:46 -06:00
Seebs
9b13ab7dd3 use readLeafCellKey to read a leaf cell's key 2020-12-16 13:16:46 -06:00
Seebs
dc67149326 read leaf cells through a pointer
This reduces noticably the cost of reading leaf cells, by passing
a single pointer down the stack instead of the entire data structure
up the stack. It's only a few percent overall, but it's noticeable.
2020-12-16 13:16:46 -06:00
Seebs
7c415b4217 Implement rbf-specific ApplyFilter
This gives RBF an ApplyFilter that can run without instantiating containers
when the filter it's using doesn't need them instantiated. We can also seek
ahead in cases where we know the next key we care about is not just the next
key numerically.
2020-12-16 13:16:46 -06:00
Seebs
2c4aff2d18 add benchmarks for f.rows()
This is a simplistic benchmark for f.rows() to let us evaluate its performance
in preparation for trying to do some profiling and tuning.
2020-12-16 13:16:46 -06:00
Seebs
30d5f891c0 create naive ApplyFilter 2020-12-16 13:16:46 -06:00
Seebs
59d89dda99 Allow arbitrary and potentially more efficient filtering of bitmaps
This is a partial solution to a nasty performance problem, which is that
a ContainerIterator has to *generate* all the containers. With roaring, this
was cheap because they already exist in memory; with transactional backends,
it's an allocation per container, *even for the containers we don't use*.

This design admits filters which can distinguish between answers they
can give just based on keys and times when they actually need containers
instantiated, and can also give hints as to future answers -- saying "yes"
or "no" to entire rows at a time, or indicating when they're done.

This is only part of the solution; we also need a Tx API hook for
doing scans like this which doesn't rely on ContainerIterator.
2020-12-16 13:16:46 -06:00
jaten-molecula
440b90f3c8
Merge pull request #1240 from jaten-molecula/roaring_printutil
roaring: print utility AsContainerMatrixString for diagnostics
2020-12-16 13:02:47 -06:00
Jason E. Aten
1a6f573739 roaring: print utility AsContainerMatrixString for diagnostics
AsContainerMatrixString returns a string showing
 the matrix of rows in a shard, showing the count of hot (1) bits
 in each container.
2020-12-16 18:50:45 +00:00
Nia Weiss
eab70e2314
use the txf to determine if snapshots are needed 2020-12-16 11:00:27 -05:00
Nia Weiss
aaf94c6aeb
fix the automatic snapshot queue enable check
The "needs snapshot queue" check was broken, as it only checked inside a loop over indices.
If there are no indexes yet (or more likely if the indices have not yet been loaded off of disk), then this would never use the snapshot queue on roaring.
2020-12-16 08:15:17 -05:00
Cody Soyland
87bf0df786
Merge pull request #1234 from codysoyland/rbf-usage-calc
Fix disk usage calculation in RBF backend
2020-12-15 15:57:17 -06:00
Cody Soyland
4cb21da6e9 Fix disk usage calculation in RBF backend 2020-12-15 13:39:27 -06:00
Cody Soyland
765bac992d
Merge pull request #1225 from codysoyland/upgrade-gopsutil
Upgrade gopsutil
2020-12-14 11:36:07 -06:00
Cody Soyland
5eb726ce59 Upgrade gopsutil 2020-12-14 10:58:28 -06:00
tgruben
3beaed1645
Merge pull request #1220 from jaten-molecula/avoid_dynamic_dispatch
FragSpec as struct, fragment fields to avoid dynamic dispatch
2020-12-14 09:21:57 -06:00
Jason E. Aten
b254c6776b FragSpec as struct, fragment fields to avoid dynamic dispatch
- allow inlining of getters index(), view().
 - GOMAXPROCS set to 128
2020-12-12 16:39:24 +00:00
tgruben
b0755577b3
Merge pull request #1218 from jaten-molecula/short_txkey
short_txkey elides index and shard from the txkey
2020-12-11 17:30:40 -06:00
Jason E. Aten
b52a814b3c short_txkey elides index and shard from the txkey
- use short_txkey for rbf
 - short_txkey breaks a bunch of bolt_test.go, so leave it on (long) txkey for now.
 - remove SliceOfShards method from Tx interface
2020-12-11 22:24:56 +00:00
tgruben
c5302f2f57
Merge pull request #1212 from tgruben/fragment-refactor
Fragment refactor (wip)
2020-12-11 15:08:19 -06:00
Todd Gruben
8ad7afbe43 FragProxy reduces string memory consumption drastically
for datasets with lots of views, because we don't
replicate path, index, field, view strings so often.
2020-12-11 21:01:15 +00:00
jaten-molecula
30502c99ab
Merge pull request #1214 from jaten-molecula/readwrite_locks
use tx.mu.RLock for OffsetRange, Lock for AddRoaring
2020-12-11 08:13:52 -06:00
Jason E. Aten
2b569b1edf use tx.mu.RLock for OffsetRange, Lock for AddRoaring 2020-12-11 13:53:27 +00:00
tgruben
f5e656138f
Merge pull request #1211 from jaten-molecula/skip_serz_1node
avoid serializing for sync shards in 1 node cluster situation
2020-12-10 17:16:10 -06:00
Jason E. Aten
7571792401 avoid serializing for sync shards in 1 node cluster situation 2020-12-10 20:54:32 +00:00
Maxton Huff
8c20255b12
Merge pull request #1210 from Maxtonian/lonquerytime2
deprecate cluster.long-query-time and create long-query-time
2020-12-10 11:14:09 -06:00
Maxton Huff
dee700741a deprecate cluster.long-query-time and create long-query-time
moved lonquerytime from cluster into server and moved cluster.longquerytime into top level config
kept cluster.longquerytime for backwards compatibility, favored if both longquerytime options are present
2020-12-10 10:51:31 -06:00
tgruben
087418566b
Merge pull request #1204 from jaten-molecula/clear_cursors
reset cursor stacks before reuse
2020-12-09 19:01:08 -06:00
jaten-molecula
21cb390d2f
Merge branch 'master' into clear_cursors 2020-12-09 17:01:21 -06:00
Jason E. Aten
81b9586a4b reset cursor stacks before reuse 2020-12-09 21:41:41 +00:00
Cody Soyland
1990ecf2b6
Merge pull request #1205 from codysoyland/docker-build-make
Use "make" inside docker build so that CGO_ENABLED uses exported value from Makefile
2020-12-09 15:02:04 -06:00
Cody Soyland
f351b7efa6 Use "make" inside docker build so that CGO_ENABLED uses exported value from Makefile 2020-12-09 13:21:07 -06:00
Cody Soyland
1add244f8a
Merge pull request #1201 from codysoyland/default-roaring
Default TxSrc to roaring
2020-12-09 09:36:28 -06:00
Cody Soyland
c1fe8b214a Default TxSrc to roaring 2020-12-09 09:31:40 -06:00
Cody Soyland
53f6a127af
Merge pull request #1198 from codysoyland/docker-build-test
Disable cgo on all builds, add additional tests for docker
2020-12-09 09:17:03 -06:00
Cody Soyland
5d18c0af2b Enable cgo on test -race 2020-12-09 09:00:03 -06:00
Cody Soyland
bc94c7bcdf Remove lmdb dependency and references, vendor Barrier 2020-12-09 08:43:23 -06:00
Cody Soyland
eef6359db3 Disable cgo on all builds, add additional tests for docker 2020-12-08 19:42:51 -06:00
tgruben
d05531b77a
Merge pull request #1197 from jaten-molecula/rowcache_off_by_default
rowcache off by default. pilosa server --rowcache-on turns it back on.
2020-12-08 19:32:19 -06:00
Jason E. Aten
03a54c6d5f rowcache off by default. pilosa server --rowcache-on turns it back on. 2020-12-08 23:18:45 +00:00
tgruben
d4d30e160b
Merge pull request #1196 from jaten-molecula/diagn
pilosa debugstats and rbf tooling for enhanced debugging/diagnostics
2020-12-08 16:58:54 -06:00
Jason E. Aten
82d07bc123 debugstats and rbf tooling for enhanced debugging/diagnostics 2020-12-08 22:47:40 +00:00
tgruben
9f7fa7eaad
Merge pull request #1195 from jaten-molecula/rbf_doc
document pattern of branch splits
2020-12-08 16:19:56 -06:00
tgruben
63d67ac65e
Merge branch 'master' into rbf_doc 2020-12-08 15:52:46 -06:00
tgruben
662073c284
Merge pull request #1194 from jaten-molecula/fix_freepageset
fix bug in pgno computation in freePageSet
2020-12-08 15:52:25 -06:00
Jason E. Aten
cbff5bd29d document pattern of branch splits 2020-12-08 20:50:29 +00:00
Jason E. Aten
96abbfa059 fix bug in pgno computation in freePageSet 2020-12-08 20:45:54 +00:00
Cody Soyland
bd060b6fd2
Merge pull request #1191 from codysoyland/docker-disable-cgo
Dockerfile: switch back to alpine and disable cgo
2020-12-07 10:07:56 -06:00
Cody Soyland
2bd27cdf5e Upgrade alpine and disable cgo 2020-12-07 09:26:52 -06:00
Cody Soyland
5d08d54518 Revert "ubuntu 20:10 image instead of alpine, for cgo support"
This reverts commit ef156f6172.
2020-12-07 09:26:52 -06:00
Nia
8a6d20d314
Merge pull request #1168 from niaow/pilosa-id-gen
Add ID auto-generation on the coordinator
2020-12-07 09:58:13 -05:00
Nia
a4f538ffec
Merge branch 'master' into pilosa-id-gen 2020-12-07 09:46:58 -05:00
Nia
2a863e5c13
clarify OptServerOpenIDAllocator
Co-authored-by: Matthew Jaffee <matthew.jaffee@gmail.com>
2020-12-07 08:08:51 -05:00
jaten-molecula
d38184a5ff
Merge pull request #1183 from jaten-molecula/rm_lmdb
remove lmdb as tx backend
2020-12-04 17:32:15 -06:00
Jason E. Aten
7a9e0969cb remove lmdb as a Tx backend
- test only still uses a Barrier utility from the go-lmdb package;
  it could be ported in at some point.
2020-12-04 23:17:59 +00:00
Cody Soyland
4f14774883
Merge pull request #1174 from codysoyland/query-timing
Add query duration to gRPC responses
2020-12-04 15:47:03 -06:00
Cody Soyland
fc3b64e500 Add table conversion to duration timing 2020-12-04 15:19:12 -06:00
Cody Soyland
c33eafc27e Add tests for streaming queries 2020-12-04 15:19:12 -06:00
Cody Soyland
6830657d12 Add query duration to gRPC responses
This adds a "duration" parameter to RowResponse and TableResponse, which
will be populated with the query duration in nanoseconds.

For QueryPQLUnary and QuerySQLUnary, the duration is a included in the
returned TableResponse.

For QuerySQL and QueryPQL, only the first RowResponse in the stream will
contain the duration.
2020-12-04 15:19:12 -06:00
Ben Johnson
26789ef0b5
Merge pull request #1163 from molecula/page-pool
Add sync.Pool for RBF pages
2020-12-04 08:08:11 -07:00
Nia Weiss
7ea2e4b7e3
add ID auto-generation on the coordinator 2020-12-03 13:04:14 -05:00
Ben Johnson
79e6156003 Add sync.Pool for RBF pages 2020-12-03 07:32:36 -07:00
Ben Johnson
1ce15a51c9
Merge pull request #1157 from molecula/put-leaf-cell-faster 2020-12-01 15:49:16 -07:00
jaten-molecula
836540151b
Merge branch 'master' into put-leaf-cell-faster 2020-12-01 16:24:09 -06:00
tgruben
dd856378d1
Merge pull request #1156 from jaten-molecula/container_key_span
RoaringIterator.ContainerKeySpan method
2020-12-01 16:22:57 -06:00
Ben Johnson
df958f40dd Optimize putLeafCellFast() 2020-12-01 12:12:54 -07:00
jaten-molecula
53b64bd939
Merge branch 'master' into container_key_span 2020-12-01 09:15:11 -06:00
Matthew Jaffee
c8c7b98654
Merge pull request #1142 from alanbernstein/update-ui-paths
Maintain list of frontend routes, update 'vds' to 'tables'
2020-12-01 07:42:50 -07:00
Jason E. Aten
6f4ca78bc4 RoaringIterator.ContainerKeySpan method
- enables rbf ingest optimization. When we know there won't be
   updates involved we can bulk import faster.
2020-12-01 01:52:58 +00:00
Alan Bernstein
4dc38bee02 Maintain list of frontend routes, updates 'vds' to 'tables' 2020-11-25 13:30:28 -06:00
Nia
df13c72351
Merge pull request #1145 from niaow/err-id-on-keyed
Report an error when an ID is used on a keyed field
2020-11-25 13:48:04 -05:00
Nia Weiss
353fd3937d
report an error when an ID is used on a keyed field 2020-11-25 11:37:18 -05:00
Alan Bernstein
9e3e0a6be7
Merge pull request #1013 from alanbernstein/query-activity-api
Add query history endpoint
2020-11-24 08:46:43 -06:00
Alan Bernstein
65877c1e56 Store start instead of age, skip remote, fix timing bug 2020-11-23 21:31:40 -06:00
Alan Bernstein
0c8e4afe45 Set test nodeIDs to guarantee iteration order in executor 2020-11-23 20:23:28 -06:00
Alan Bernstein
a8a7e53382 Add query-history test 2020-11-23 20:23:28 -06:00
Alan Bernstein
c5a7a259aa Make minor fixes to query tracker 2020-11-23 20:23:28 -06:00
Alan Bernstein
2bc2a32263 Make query history length configurable 2020-11-23 20:23:27 -06:00
Alan Bernstein
b172f4d05b Include index in query history response 2020-11-23 20:23:27 -06:00
Alan Bernstein
542a6ffdb6 Gather query history from remote nodes 2020-11-23 20:23:27 -06:00
Alan Bernstein
bbd147d18f Replace query history map with ringBuffer 2020-11-23 20:23:27 -06:00
Alan Bernstein
65de5df2a9 Update tracker test 2020-11-23 20:23:27 -06:00
Alan Bernstein
c94d242097 Add basic implementation of query history endpoint 2020-11-23 20:23:27 -06:00
seebs
55d0c12703
Merge pull request #1131 from seebs/randomerQuery
Improve random-query
2020-11-23 11:58:53 -06:00
Seebs
3d802f428b add Distinct, be more consistent about using correct rand.Rand
The RandomQueryConfig has its own seeded RNG, but we didn't always use
it (especially in the last two commits, but also I think in one previous
thing), so let's use it more consistently.

We check for int fields (as opposed to decimal), and if we find them,
we add Distinct to our list of potential queries to use if and only
if we've got a depth of at least one so there'd be a child query under
the current query, and if we do, grab one of the int (not decimal)
fields and do a query on that.
2020-11-23 09:54:59 -06:00
Seebs
796d646a0c handle integer fields
Extend field handling to include int (and decimal) fields, allowing them
to have range operations specified on them.
2020-11-23 09:51:29 -06:00
Seebs
2a2a8abbd4 support time fields
For time fields, allow specifying a range of times, then 19/20 times,
specify "from" and "to" times in that range when querying those fields,
rather than just looking at the standard view all the time.
2020-11-23 09:51:29 -06:00
Seebs
48b97d6ad4 report QPS after last query
More generally, report QPS not at 0 queries, which is boring, but every
100 queries *and* after the last query if the last query wasn't at a
multiple of 100 queries. Makes the output slightly more useful, I think.
2020-11-23 09:51:29 -06:00
Matthew Jaffee
7271fbf396
Merge pull request #1015 from alanbernstein/dont-create-keys-directory
Check index.keys before opening translate store to avoid unnecessary keys directory
2020-11-20 14:30:37 -07:00
Matt Jaffee
a48bf28be2
don't try to translate keys on unkeyed indexes
this causes a few things to error earlier than they otherwise would
have, hence the changed tests.
2020-11-20 14:48:28 -06:00
Alan Bernstein
063a3b96e3
Check index.keys before opening translate store 2020-11-20 12:43:15 -06:00
Nia
106952dc5e
Merge pull request #1106 from niaow/topk-time
TopK on time
2020-11-20 13:24:05 -05:00
Nia
c8ab2a9fb3
Merge branch 'master' into topk-time 2020-11-20 13:11:51 -05:00
Nia Weiss
d7b9568960
add licesnse header to bsi_test.go 2020-11-20 12:40:28 -05:00
Nia Weiss
410a372351
clarify bsi-building preconditions 2020-11-20 12:38:53 -05:00
Nia Weiss
f9c196e418
skip roaring add test in race
Otherwise it overloads the race detector.
2020-11-20 12:16:09 -05:00
tgruben
72f4024e99
Merge pull request #1122 from jaten-molecula/inlined_immutable_map_rb
rbf: use an inlined immutable.Map<uint32, int64> for the PageMap
2020-11-20 10:44:44 -06:00
Nia Weiss
5d2c42b7bf
address documentation todo in pivotDescending 2020-11-20 11:13:44 -05:00
Matthew Jaffee
64b83137a0
add tests for addBSI 2020-11-20 11:07:30 -05:00
Nia Weiss
46818863e8
implement TopK on time
This replaces the former TopK BSI building algorithm, as the row cache was too expensive.
Additionally, BSI addition has been optimized with specialized adders inside of roaring.
2020-11-20 11:06:55 -05:00
Jason E. Aten
e1bb6e303a rbf: use an inlined immutable.Map<uint32, int64> for the PageMap
- goes 7% faster on kitchen sink import test

- reduces total allocations by 5% on same test.
2020-11-20 00:49:41 +00:00
jaten-molecula
47202b0ed2
Merge pull request #1095 from molecula/fast-write
Implement optimized fast leaf write.
2020-11-19 16:04:31 -06:00
Ben Johnson
a9831ad5a1 Replace literals with leafCellHeaderSize 2020-11-19 14:18:53 -07:00
Ben Johnson
d125c68275 Fix estimated page size calculation to include index padding. 2020-11-19 14:12:43 -07:00
Ben Johnson
33334511c4
Merge branch 'master' into fast-write 2020-11-19 13:44:09 -07:00
jaten-molecula
5b8cda3c88
Merge pull request #1120 from molecula/fix-root-record-overflow
Fix rbf write root record iterator reset
2020-11-19 13:11:09 -06:00
Ben Johnson
a31487b873 Fix rbf write root record iterator reset 2020-11-19 11:15:24 -07:00
jaten-molecula
61bdc2257b
Merge pull request #1111 from jaten-molecula/sync_pool
rbf: reuse cursors with sync.Pool/arena
2020-11-19 11:17:20 -06:00
Jason E. Aten
9c7bc603af rbf: reuse cursors with sync.Pool or arena
- the sync.Pool default uses little memory under CI.

- arena approach provides ability to control the maximum memory
  used by rbf Cursors.

- cursor caching is adjustable with --rbf-cursor-cache
  currently 0 by default (meaning use sync.Pool), and
  larger than 0 meaning use an arena of this size.
  With the arena, 20 or less is needed to pass CI.

- rbf test suite runs ~ 4x faster

- kitchen sink ingest test runs 16% faster.

- report TotalAlloc in CALLSTATs

fixes #1105
2020-11-19 17:02:49 +00:00
jaten-molecula
274a95c4da
Merge pull request #1115 from molecula/remove-rbtree
Use immutable.SortedMap for root records
2020-11-19 11:00:57 -06:00
Ben Johnson
26d0e9d958
Merge branch 'master' into remove-rbtree 2020-11-19 07:27:09 -07:00
tgruben
b849fe4b98
Merge pull request #1116 from jaten-molecula/pilosa_txsrc
use pilosa server --txsrc instead of --tx to prevent viper env var shadowing
2020-11-18 14:37:10 -06:00
Jason E. Aten
524c78623f repair pilosa/ctl/server_test too 2020-11-18 18:53:22 +00:00
Ben Johnson
0175c66756 Use immutable.SortedMap for root records
This commit fixes a bug where the root record cache was being
updated in-place causing a race condition with other transactions
using it. The cache implementation has been changed from `rbtree`
to an `immutable.SortedMap`.
2020-11-18 11:22:52 -07:00
Jason E. Aten
06eb3afaaa use pilosa server --txsrc to prevent viper env var shadowing from stopping PILOSA_TXSRC getting through 2020-11-18 18:21:31 +00:00
jaten-molecula
a9c1ff3047
Merge pull request #1107 from jaten-molecula/fix_tx
restore the env variable PILOSA_TXSRC's affect
2020-11-16 17:26:22 -06:00
Jason E. Aten
744827d5cc restore the env variable PILOSA_TXSRC's affect 2020-11-16 23:18:50 +00:00
Ben Johnson
89ae5ad4b7
Merge branch 'master' into fast-write 2020-11-16 09:10:01 -07:00
Ben Johnson
519dc5069d Implement optimized fast leaf write.
This commit adds an optimized implementation for `putLeafCell()` if
the insert/update will not cause the page to overflow.
2020-11-16 08:13:35 -07:00
Nia
6dfbbf91d6
Merge pull request #1098 from niaow/topn-v3
Add TopK with perpendicular BSI bitmaps
2020-11-16 08:39:50 -05:00
Nia Weiss
02498ce57a
add TopK with perpendicular BSI bitmaps 2020-11-13 18:31:55 -05:00
tgruben
43443d10ff
Merge pull request #1097 from jaten-molecula/bitmap_foreach
rbf: BitN needs int32 to hold its maximum value.
2020-11-13 17:12:19 -06:00
Jason E. Aten
4120024b10 rbf: BitN needs int32 to hold its maximum value.
- decrease ArrayMaxSize and RLEMaxSize by 1
  to make space
- add TestForEachRange, fix bugs found in ForEach,
  where the call f() logic was backwards.
2020-11-13 22:59:18 +00:00
tgruben
b2e9e3904a
Merge pull request #1094 from jaten-molecula/elem_rb
rbf: keep ElemN and BitN up to date.
2020-11-13 15:44:15 -06:00
Jason E. Aten
d46b603cd6 rbf: keep ElemN and BitN up to date.
- fix a bug in computing leafCell.BitN in a run after a bit Remove
- shrink bitmaps on remove
- util_test.go has Cursor.DebugSlowCheckAllPages to verify;
  used by cursor_test.go
2020-11-13 21:18:09 +00:00
Cody Soyland
dc0ac0ccd0
Merge pull request #1092 from codysoyland/ci-go-version-cve-2020-28362
Ci: Go version update for CVE-2020-28362
2020-11-13 11:48:16 -06:00
Cody Soyland
e53b74261b Update default Go version 2020-11-13 11:23:39 -06:00
Cody Soyland
a2f49b6a84 Fix yaml syntax, add requires section 2020-11-13 11:23:39 -06:00
Cody Soyland
dca606dea8 Add pilosa (binary build) to .gitignore 2020-11-13 11:23:39 -06:00
Cody Soyland
671250d273 Update Go version for CVE-2020-28362, use param matrix 2020-11-13 11:23:39 -06:00
tgruben
d15dc4204b
Merge pull request #1086 from jaten-molecula/rbtree_root_records
rbf: use a red-black tree to manage the root records list
2020-11-13 11:14:51 -06:00
Jason E. Aten
eae82b72c2 rbf: use a red-black tree to manage the root records list
- 40% faster on ingest_test when putting 10K roots/containers.
 - 15% fewer bytes allocated total
 - clarifying renames leafCell.N -> ElemN, allocate -> allocatePgno,
   deallocate -> freePgno
2020-11-13 16:49:26 +00:00
jaten-molecula
cb73c71e02
Merge pull request #1072 from molecula/uni-wal
Refactor RBF to use a single WAL file
2020-11-11 15:44:45 -06:00
jaten-molecula
563c70dc43
Merge branch 'master' into uni-wal 2020-11-11 15:27:15 -06:00
tgruben
b3d2704066
Merge pull request #1085 from jaten-molecula/uniq_dbnames
unique db names so parallel test runs don't collide
2020-11-11 14:26:04 -06:00
Jason E. Aten
fd9210b31c unique db names so parallel test runs don't collide 2020-11-11 20:04:02 +00:00
Ben Johnson
81a64c5902 Add RBF halting; remove time based checkpoint 2020-11-11 11:17:07 -07:00
Ben Johnson
9eba299d35 Restrict max RBF transaction size 2020-11-10 08:14:14 -07:00
Ben Johnson
78eb9e0711 Fix linter 2020-11-10 07:07:06 -07:00
Ben Johnson
52340212f6 Add RBF dirty page cache 2020-11-09 16:11:20 -07:00
Ben Johnson
8de1959938 Remove RBF exclusive/direct write. 2020-11-09 08:23:09 -07:00
Ben Johnson
3554048877 Refactor RBF to use a single WAL file 2020-11-09 08:03:22 -07:00
tgruben
e99f7f0fc3
Merge pull request #1074 from jaten-molecula/fsync_applies_to_all
apply --fsync flag to all tx backend
2020-11-06 12:34:28 -06:00
Jason E. Aten
a38d4fa46c apply --fsync flag to all tx backend
- rename from --rbf-fsync to --fsync, as it
   now effects bolt, lmdb too.
2020-11-06 18:11:02 +00:00
jaten-molecula
822c7482a5
Merge pull request #1071 from jaten-molecula/parallel_migration_rb
run migration in parallel
2020-11-05 14:09:31 -06:00
Jason E. Aten
7e22994a6d run migration in parallel
- migration can be slow. parallelize it.
2020-11-05 18:31:27 +00:00
tgruben
bc26de63b0
Merge pull request #1070 from jaten-molecula/slurp_profile_rbf_comments
rbf default. Add TODO comments, slurp -profile returns a cpu profile
2020-11-03 19:52:54 -06:00
Jason E. Aten
458095a707 rbf default. Add TODO comments, slurp -profile returns a cpu profile
- default Tx is once again RBF, changed from bolt.
- document the RBF code review comments that were not addressed
  before #1052 was merged, so they don't get lost.
- they should be easily addressed by replaying the entire WAL file
  rather than from the DB meta page 0 notion of the last WalID
- cleanup rbf/cfg/cfg.go stale comments, ensure default0 respected.
  1 msec checkpoint time, 1MB wal segment defaults.
- return a specific error, ErrNoMetaFound, from findNextWALMetaPage()
  rather than io.EOF, since there actually wasn't any file IO involved.
- add http handlers for /cpu-profile/start and /cpu-profile/stop
  in http/handler.go enable CPU profiling at specific time points
  during an ingest or other operation.
2020-11-04 01:36:17 +00:00
tgruben
a55cdad139
Merge pull request #1069 from jaten-molecula/faster_new_tx
take a mutex off the fast path of TxFactory.NewTx
2020-11-03 19:15:26 -06:00
Jason E. Aten
bf36f54b8d take a mutex off the fast path of TxFactory.NewTx 2020-11-04 01:04:04 +00:00
tgruben
40a773b839
Merge pull request #1068 from jaten-molecula/qcx_gettx_error
qcx.GetTx returns an error
2020-11-03 18:34:24 -06:00
Jason E. Aten
6aadd13095 qcx.GetTx returns an error
- to indicate that the query context is already
   done.
 - handles the case where the import worker is
   interrupted early by a ctx cancellation,
   thus avoiding a panic.
2020-11-04 00:01:02 +00:00
Ben Johnson
06cb04f87a
Merge pull request #1052 from molecula/fix-wal-not-found
Fix RBF WAL ID panic.
2020-11-03 11:16:33 -07:00
Ben Johnson
731a1ef25e Refactor RBF WAL to only only checkpoint-in-full. 2020-11-03 10:41:31 -07:00
Ben Johnson
ea3732fa62 Fix WAL ID not found panic.
This commit changes the checkpointing to determine a minimum WAL ID
for readers and a max ID based on the writer. Pages are checkpointed
from the WAL up to the writer's max WAL ID but segments are removed
only up to the reader's minimum WAL ID. This ensures that WAL pages
are not removed out from under current read transactions.
2020-11-03 10:41:31 -07:00
Ben Johnson
c47b49a06e
Merge pull request #1053 from molecula/increase-rbf-wal-write-cache 2020-11-03 07:37:05 -07:00
Ben Johnson
db74f0dffa Increase RBF WAL write cache size to 1MB.
Previously we dropped the cache size to 64KB but that seems much
too low. Write performance suffers considerably.
2020-10-30 17:09:33 -06:00
Matthew Jaffee
3ff85ccb53
Merge pull request #1051 from molecula/default-txn-bolt-fix-ci
default to Bolt txn due to CI failures blocking other team
2020-10-30 15:01:57 -05:00
Matt Jaffee
83dd8026d5
default to Bolt txn due to CI failures blocking other team
Our nightly CI has been failing for a week due to WAL issues and it's
making it difficult for Kuba and Antonio to do things on the
integration repo. Hoping bolt backend will solve that in the short
term. I think the issue is Pilosa #1046 (that's from memory though)
2020-10-30 13:55:36 -05:00
Ben Johnson
bb5153a7c1
Merge pull request #1049 from molecula/rbf-cli-help
Improve RBF CLI help
2020-10-30 08:38:14 -06:00
Ben Johnson
d5369a084e Improve RBF CLI help 2020-10-30 07:25:36 -06:00
tgruben
f446eee088
Merge pull request #1048 from jaten-molecula/delete_empty
DeleteEmptyContainer true by default now
2020-10-30 06:30:09 -05:00
Jason E. Aten
9c66cd5a81 DeleteEmptyContainer true by default now 2020-10-30 04:15:53 +00:00
tgruben
4113a3d85b
Merge pull request #933 from molecula/qcx_no_auto_reset
pilosa: no automatic reuse of Qcx
2020-10-29 20:03:42 -05:00
Jason Aten
016e5e0774 no automatic reuse of Qcx
Per slack discussion with Seebs and Nia,
we'll try not automatically resetting the Qcx.

The worry was that our goroutine shutdown
management is so poor that we are asking for
GetTx on a goroutine that still has a Qcx
from a query that was cancelled.

If this is the case, we will now panic instead of
issuing a new Tx. Then we can fix the poor
goroutine management.

 - also require Qcx.Finish or Abort before Reset
2020-10-30 00:36:46 +00:00
tgruben
51c9f4feeb
Merge pull request #1044 from jaten-molecula/startup_shards
don't apply startup shard cache to roaring with a specified view path
2020-10-29 18:24:57 -05:00
Jason E. Aten
18e253870e cleanup TypedDBPerShardGetShardsForIndex logic 2020-10-29 22:25:06 +00:00
Jason E. Aten
f8e7b27a6f don't apply startup shard cache to roaring with a specified view path
- avoids creating a new empty 8 byte shard file under all the
   views that don't have them already.
2020-10-29 22:09:55 +00:00
tgruben
ed6f59a827
Merge pull request #1042 from jaten-molecula/migration_logging
better migration logging
2020-10-29 04:38:53 -05:00
Jason E. Aten
2d55ffbd28 short circuit if no data to migrate 2020-10-29 02:44:22 +00:00
Jason E. Aten
d4df15721d allow migration of empty to empty 2020-10-29 02:41:09 +00:00
Jason E. Aten
aa47cf5bf4 fix verify test 2020-10-29 02:35:38 +00:00
Jason E. Aten
0e354aedf2 error expected trying to migration to roaring 2020-10-29 02:29:57 +00:00
Jason E. Aten
f9f94aab0d fix typo 2020-10-29 02:15:43 +00:00
Jason E. Aten
bceabc9127 error on un-implemented migrate to roaring 2020-10-29 02:11:28 +00:00
Jason E. Aten
3baa8ea0f6 check if roaring data present with RoaringHasData 2020-10-29 01:47:26 +00:00
Jason E. Aten
639e9b3bf0 quiet 2020-10-29 01:32:04 +00:00
Jason E. Aten
6e20e6439f more logging 2020-10-29 01:30:16 +00:00
Jason E. Aten
b1ff3791b8 better error when green does not exist on migration 2020-10-29 01:20:41 +00:00
Jason E. Aten
85fc785296 timed progress 2020-10-29 01:12:19 +00:00
Jason E. Aten
509348260f better migration logging 2020-10-28 22:54:58 +00:00
Ben Johnson
e332f6fd7e
Merge pull request #1018 from molecula/rbf-cli 2020-10-28 06:26:27 -06:00
jaten-molecula
6386587f12
Merge branch 'master' into rbf-cli 2020-10-28 05:35:43 -05:00
jaten-molecula
9d233c4849
Merge pull request #1040 from jaten-molecula/rowcache_lock
pilosa: write lock the fragment when rowcache used
2020-10-27 20:11:10 -05:00
Jason E. Aten
062bd5c8c7 pilosa: write lock the fragment when rowcache used
- only allocate the rowcache if it is in use (avoid allocation per fragment)
 - when the rowcache is use, fragment.go intRowIterator must write lock the
   fragment because the f.rowCache will be updated.
 - eliminate unused bitmapCache interface to keep the linter happy.
 - fixes #1035
2020-10-28 00:53:38 +00:00
jaten-molecula
1784384153
Merge pull request #1039 from jaten-molecula/rbf_logging
pilosa server --rbf-checkpoint-dur to 0 by default
2020-10-27 18:19:04 -05:00
Jason E. Aten
1ca8a45357 pilosa server --rbf-checkpoint-dur to 0 by default
- we return to checkpointing after every commit, by default.
  - the internal rbf logic is not ready to have
    checkpoints deferred. Doing so results in
    references to WAL segments that are not
    in the current slice of live segments.
2020-10-27 22:38:18 +00:00
jaten-molecula
c9912e3607
Merge branch 'master' into rbf-cli 2020-10-27 15:03:43 -05:00
Cody Soyland
7855cd4d21
Merge pull request #1038 from jaten-molecula/dockerfile_go1.14.10
use go1.14.10 for now because of https://github.com/molecula/pilosa/issues/1036
2020-10-27 14:04:03 -05:00
Jason E. Aten
24bd1543f3 Makefile to go1.14.10 2020-10-27 18:07:13 +00:00
Jason E. Aten
2fb53b982a update .circleci/config.yml to use go1.14.10 2020-10-27 18:00:15 +00:00
Jason E. Aten
03f24303d7 use go1.14.10 for now because of https://github.com/molecula/pilosa/issues/1036 2020-10-27 17:44:53 +00:00
Jason E. Aten
85f3fa9836 merge with the update to rbf to take a Config parameter 2020-10-27 15:11:45 +00:00
jaten-molecula
4543237e09
Merge branch 'master' into rbf-cli 2020-10-27 10:09:24 -05:00
jaten-molecula
305f81a310
Merge pull request #1034 from jaten-molecula/rbf_checkpoint_rb
performance tuning: rbfcfg package, binary search for wal segment
2020-10-27 10:07:47 -05:00
Ben Johnson
2b1d790625 Fix tree key formatting; add --with-tree option 2020-10-27 08:24:56 -06:00
Ben Johnson
7fa1a5edd2 Add RBF CLI commands 2020-10-27 08:12:22 -06:00
Jason E. Aten
957cba1768 performance tuning: rbfcfg package, binary search for wal segment
- rbfcfg package holds Config for --rbf- command line flags
- wal.go: replace linear search with bisection for wal segment
2020-10-27 00:49:56 +00:00
jaten-molecula
14cb29ea7d
Merge pull request #1024 from jaten-molecula/avoid_excessive_directory_scans
pilosa: avoid re-scanning shards during holder open
2020-10-23 20:31:05 -05:00
Jason E. Aten
233b3cbc0f versioned map cleanup 2020-10-24 00:35:25 +00:00
Jason E. Aten
c5e46e4618 one copy of shard map during a reload 2020-10-24 00:20:30 +00:00
Jason E. Aten
8ff6e8e0fd versioned readonly shards map 2020-10-23 23:50:22 +00:00
Jason E. Aten
2503fe5b66 pilosa: avoid re-scanning shards during Holder.Open()
- view.openFragmentInTx was forcing a directory scan
   for shards on every open fragment during Holder.Open().
   Seen by pprof profile having excessive allocations
   from dbshard.go listDirUnderDir().
2020-10-23 23:50:22 +00:00
seebs
2b6cb5fc6c
Merge pull request #1020 from seebs/distinctSet
Distinct operations on set fields
2020-10-23 17:09:23 -05:00
seebs
7558afd7ec
Merge branch 'master' into distinctSet 2020-10-23 16:41:00 -05:00
Cody Soyland
35706a3372
Merge pull request #1017 from codysoyland/docker-build
Fix Docker build target, enable when building Linux releases
2020-10-23 16:31:30 -05:00
Seebs
765d28bf9d add test case and bug fix for distinct code
The "seenThisRow" value was never getting cleared, which meant that
if the first container on a row didn't happen to contain any post-filter
bits, the rest of the row wouldn't get evaluated.
2020-10-23 16:18:19 -05:00
Cody Soyland
fc29bf5ec8 Fix Docker build target, enable when building Linux releases 2020-10-23 14:27:00 -05:00
Seebs
a2151358ba Partial implementation: Distinct() supporting set fields
Add an exported IntersectionAny() from roaring to let us quickly
check whether two containers have overlap, so we can avoid performing
intersections we don't need to when evaluating containers within
the same row as a previous match. (IntersectionCount on the whole
bitmap would imply doing up to 16 intersections even if we find a bit
right away.)

We also allow ForeignIndex to be set on set, mutex, and time fields,
since all of those could now be reasonable operands for Distinct
ops.

Not yet present: Handling time quantums, but that seems really
desireable.
2020-10-23 13:36:40 -05:00
Nia
c1db627593
Merge pull request #989 from niaow/translate-maybe
Allow querying without creating keys
2020-10-23 14:23:35 -04:00
Nia
3b93b767fa
Merge branch 'master' into translate-maybe 2020-10-23 13:59:10 -04:00
Nia Weiss
ddee7ae35e
address review comments for #989 translate-maybe 2020-10-23 10:59:08 -04:00
jaten-molecula
8f1aae9a69
Merge pull request #1016 from jaten-molecula/rowcache_optional
pilosa server --rowcache-off disables the row cache
2020-10-22 22:14:51 -05:00
Jason E. Aten
8d2ad048fa pilosa server --norowcache disables the row cache
- this can lessen memory pressure
- certain backends may not need it
- enables performance benchmarking and tuning
2020-10-22 22:20:48 +00:00
seebs
9eb251ec85
Merge pull request #69 from seebs/ctbench
Container op benchmarks
2020-10-22 15:03:09 -05:00
Seebs
8095455355 container performance benchmarking
This adds a series of archetypal containers that represent the
common use cases (arrays, bitmaps, or runs of various cardinalities)
and runs the basic operations against them for benchmarking purposes.

`benchpretty` is an app to snatch the BenchmarkCt* lines from
benchmark runs and display them in a possibly more usable form,
mostly as a precursor to cool analysis things.

This also adds a test to verify that intersectionCount(a, b)
is the same as intersect(a, b).N() for all the archetypal
containers.

This also includes a performance fix for intersectBitmapRun which
was spotted while running these tests.
2020-10-22 14:33:03 -05:00
Nia Weiss
afbbc93047
add basic tests for queries with missing keys 2020-10-22 13:19:05 -04:00
Nia Weiss
9204298d06
fix broken rebase on GetNodeUsage 2020-10-22 11:10:36 -04:00
Nia Weiss
8340b9ea64
re-apply single equals fix 2020-10-22 11:03:51 -04:00
Nia Weiss
b0a588f8dd
finish translate-if-exists 2020-10-22 10:50:09 -04:00
Nia Weiss
a74eda1eb0
cleanup of key translation fix 2020-10-22 10:50:08 -04:00
Nia Weiss
c60c699664
deal with linter false positives 2020-10-22 10:47:30 -04:00
Nia Weiss
da52b134bf
the tests pass now 2020-10-22 10:47:30 -04:00
Nia Weiss
5315579378
add some validation 2020-10-22 10:47:29 -04:00
Nia Weiss
1fe0edae08
fix find & create foreign index keys 2020-10-22 10:47:29 -04:00
Nia Weiss
a6e3723190
it mostly works now 2020-10-22 10:47:29 -04:00
Nia Weiss
13a19aeb42
query translation WIP 2020-10-22 10:47:29 -04:00
Nia Weiss
15036787a7
test new translation paths via API 2020-10-22 10:47:29 -04:00
Nia Weiss
8a130c150e
address review comments 2020-10-22 10:47:28 -04:00
Nia Weiss
22d6011d05
apply "maybe" key translation WIP 2020-10-22 10:47:25 -04:00
jaten-molecula
d12639b890
Merge pull request #1014 from jaten-molecula/log_in_utc
log in UTC in fixed width microseconds RFC3339 format
2020-10-21 20:01:36 -05:00
Jason E. Aten
aad38d1c60 log in UTC in fixed width microseconds 2020-10-21 18:36:28 +00:00
seebs
4cea813ae8
Merge pull request #999 from seebs/pqlCleanup
Fixes some minor PQL issues; adds support for case-insensitive PQL calls
2020-10-20 16:02:42 -05:00
jaten-molecula
fb6feb04f9
Merge branch 'master' into pqlCleanup 2020-10-20 15:21:54 -05:00
seebs
a1e1d86ae3
Merge pull request #1012 from seebs/ignoreCN
Supply "subject alternative name" for TLS certificates
2020-10-20 14:50:18 -05:00
Travis Turner
52b91b2df1
Merge branch 'master' into pqlCleanup 2020-10-20 14:19:45 -05:00
Seebs
5e36638e10 Supply "subject alternative name" for TLS certificates
Go killed off using the common name for hostnames starting with 1.15,
but this can be addressed by recreating the certs using a Subject
Alternative Name for the domain for "localhost". This allows tests
to pass without hanging, at least for me.
2020-10-20 14:14:26 -05:00
jaten-molecula
d929c945bc
Merge pull request #1011 from jaten-molecula/rbf_default
rbf is the default Tx type. Dogfood it.
2020-10-20 13:53:21 -05:00
Jason E. Aten
18ad4a8230 rename test-golang-1.14 -> test-golang-1.14.9 2020-10-20 13:37:13 -05:00
Travis Turner
4db93db0c1
Merge branch 'master' into pqlCleanup 2020-10-20 13:19:55 -05:00
Jason E. Aten
275779173d go1.15.3 support with GODEBUG=x509ignoreCN=0 2020-10-20 12:23:35 -05:00
Jason E. Aten
996b2ccb23 go1.13.15 -> go1.15.3 as our supported versions 2020-10-20 11:58:12 -05:00
Jason E. Aten
1216b93d73 rbf is the default Tx type. Dogfood it. 2020-10-20 11:07:07 -05:00
tgruben
c356498c19
Merge pull request #1010 from jaten-molecula/avoid_bg_deadlock
CI catches red blue-green tests. Qcx write flag
2020-10-20 11:01:53 -05:00
Travis Turner
7be3cd6a36
Merge branch 'master' into pqlCleanup 2020-10-20 10:59:48 -05:00
Jason E. Aten
2b4e6d25f5 CI catches red blue-green tests. Qcx write flag
- fix a CI/Makefile issue that was hiding red tests in CI.
 - the testv and testv-race targets now require /bin/bash
 - In executor.go, the top-level query context Qcx now
   has a write flag. It will upgrade read-Tx to write-Tx
   when Store() wraps some inner local-read operations,
   to avoid deadlocking against its own query. This deadlock
   happens in TestExecutor_Execute_SetRow/Set_NewRow
   under rbf_lmdb blue-green testing without the upgrade.
2020-10-20 10:38:40 -05:00
jaten-molecula
0dd3486b95
Merge pull request #1005 from jaten-molecula/bglimits
Fix blue-green Tx cleanup and document single import at once
2020-10-20 07:31:54 -05:00
Jason E. Aten
d9783406bd Fix blue-green Tx cleanup and document single import at once
- correct string constants for txtype so that
  blue-green cleanup correctly detects when
  2nd transaction in a pair has Committed and
  thus the blue-green RWMutex can be relased

- test that txtype.String() is consistent with
  the corresponding string constants.

- document in bluegreentx.go the current limitations
  of blue-green testing: only one github archive import
  (a single writing client) is supported by blue-green
  testing. Multiple importers will deadlock eventually
  on the DBShard.mut RWMutex. We could fix this by
  ordering the write locks and obtaining them in
  strictly increasing order (by shard number), but
  that would require alot of change to the executor
  and that would introduce more risk for a test-only
  pathway.
2020-10-20 07:09:08 -05:00
jaten-molecula
2ebff707a9
Merge pull request #1004 from jaten-molecula/bluegreenlock
introduce a per shard blue-green RWMutex
2020-10-19 18:02:29 -05:00
Jason E. Aten
81013999e5 introduce a per shard blue-green RWMutex
- allows blue-green testing with concurrent readers/writers.
 - otherwise we don't start/end the blue and green Tx
   together, and they get split by a read/write concurrently.
2020-10-19 17:28:35 -05:00
Travis Turner
f37a63e5cf
Merge branch 'master' into pqlCleanup 2020-10-19 15:45:04 -05:00
Cody Soyland
d3ae2614ce
Merge pull request #1001 from seebs/updateLint
uprev golangci-lint, fix a minor lint in rbf
2020-10-19 14:54:38 -05:00
Seebs
ed309821ae uprev golangci-lint, fix a minor lint in rbf
I have a newer staticcheck and golangci-lint on my laptop, and it started
complaining about something. The first comment added disables the check
in staticcheck-as-a-command, the second disables it when it's being done
by golangci-lint, which invokes the analysis passes directly and displays
the output differently, and also doesn't recognize the hints used by
staticcheck.

Newer golangci-lint doesn't find anything else that it wants to complain
about.
2020-10-19 14:25:11 -05:00
Seebs
6e725d50ce check error return from peg parser Init
The updated peg tool produces an Init that can return an error. As
of this writing, the error can't be non-nil unless you specified an
option which itself returned an error, but that could change later,
so let's be careful.
2020-10-19 13:47:42 -05:00
Seebs
e84d2d2a59 refactor number parsing a bit
There are subtle inconsistencies, like "01" being a valid decimal but not
a valid integer, which vaguely bug me. Cleaning this up, and the corresponding
parser logic.

A number can't have leading spaces because the grammar doesn't
put spaces in them in the first place, so stop accepting them in the
number syntax. This should never have any impact on anything,
it's just simpler.

Update a couple of test cases to reflect this -- no longer testing
that trailing spaces are okay, now testing that they're not, for
instance.
2020-10-19 13:37:21 -05:00
Seebs
c19d571112 more consistent spacing
We still prohibit a space before a leading '(', which maybe we shouldn't,
but we now allow spaces on both sides of a closing ')' more consistently.
Drop the unneeded "sp" before "close" in the special handling after
null, true, and false, because close now implies that.

Also, refactored the two instances of "sp '=' sp" into a thing called eq,
which may not be worth it.
2020-10-19 13:35:49 -05:00
Seebs
28d18920b9 Make PQL case-insensitive about call names.
Use "" strings for fixed string names. In startCall(), look up the
lowercase conversion of a call name in a table mapping all-lowercase
representations to canonical case, so we don't have to chase down
everyplace in the rest of the code base that assumes "Row" is
capitalized exactly like that.
2020-10-19 13:35:49 -05:00
Seebs
18ed02d7fb drop float/decimal distinction in PQL
PQL always produces decimals, which have effectively-arbitrary range,
but can convert them to floats when required; the executor then requests
this conversion in the handful of cases (SetRowAttrs and SetColumnAttrs)
where it wants floats rather than decimals.

Not yet fixed: The "Range" call may also be wrong now. It was specifying
an "fvalue" but is now effectively getting what used to be called a
"dvalue". However, so far as I can tell, that didn't work before either.
2020-10-19 13:35:49 -05:00
Seebs
b8bb438b69 trivial cleanup of PEG grammar
Drop irrelevant (), simplify the expression of the sp rule.

Perhaps shockingly, this *does not change the generated grammar at all*. The
generated code for:
	sp <- [ \t\n]*
and is identical to the code for:
	sp <- ( ' ' / '\t' / '\n' )*

And in fact, is spelled the latter way in the generated comments.
2020-10-19 13:35:49 -05:00
Seebs
0ea324129f update to new version of peg tool
Want to do some PQL cleanup. A new version of peg turns out to dramatically
alter performance in some cases, so I'm doing the commit for "don't change any
PQL, just change the version of peg" checkin separately.
2020-10-19 13:35:49 -05:00
tgruben
092177aea4
Merge pull request #997 from molecula/bolt_in_badger_out
Use boltdb instead of badger as our all-Go Tx oracle
2020-10-16 17:41:23 -05:00
Jason E. Aten
266b92c025 Use boltdb instead of badger as our all Go Tx oracle.
- remove all badgerdb code.
 - use boltdb instead.
2020-10-16 17:21:21 -05:00
Cody Soyland
3dff03907d
Merge pull request #942 from codysoyland/grpc-web
Add grpc-web to http handler
2020-10-16 15:46:25 -05:00
Cody Soyland
b86b0519af go mod tidy 2020-10-16 15:10:48 -05:00
Cody Soyland
a2ee047868 grpc-web cors test 2020-10-16 11:36:41 -05:00
Cody Soyland
31cd119a7e Move grpc.Server creation into initializer
This fixes an issue with the grpc-web middleware using a nil
grpc.Server instance.
2020-10-16 11:36:41 -05:00
Cody Soyland
04b1152224 Add grpc-web to http handler 2020-10-16 11:36:41 -05:00
jaten-molecula
8da638f685
Merge pull request #979 from seebs/bbolt
switch to etcd boltdb fork
2020-10-15 19:58:31 -05:00
Seebs
0905e858cd uprev golang to 1.14.9
1.14 had a bug in the checkptr code (well, not exactly a bug) which
made it enforce alignment requirements on x86. This turns out not
to be the problem I was seeing, but we should be on 1.14.9 anyway.

To keep this from breaking CI integration with Github, we also
use explicit job names instead of matrix-generated ones, and fix
the CI config syntax up a bit after almost getting that right the
first try. (This patch includes fixes contributed by Cody, and since
I had to rebase and re-approve AGAIN anyway, I might as well squash
the commit history up.)
2020-10-15 19:13:28 -05:00
Seebs
76fe49d390 un-disable checkptr by fixing the memory problems
Step one: switch to etcd.io's bbolt fork of boltdb.

The etcd-io fork of boltdb isn't archived, and has fixes for boltdb's
interactions with checkptr, allowing us to drop the checkptr-disabling
hackery.

This seems to be a drop-in replacement; etcd/bbolt says that the file
format is "fixed" (I believe in the sense of "unchanging"), and I can
run pilosa on an existing data directory with this.

Step two:

Fix missing caps in roaring.go that were also triggering the same
issues.
2020-10-15 19:12:47 -05:00
tgruben
de642ed90c
Merge pull request #993 from molecula/fix983
bluegreentx: default to not dumping full databases
2020-10-15 17:31:55 -05:00
Jason E. Aten
e5a56f1cd2 bluegreentx: default to not dumping full databases 2020-10-15 16:23:19 -05:00
Matthew Jaffee
c859a8aa2b
Merge pull request #981 from jaffee/single-equal-data-race
Fix data race on call map in single '=' logic
2020-10-15 11:40:16 -05:00
Matt Jaffee
00f1f70779
fix data race on call map in single = logic
Add test of single = int query over multiple shards which reproduces
the race

move the code which modifies the PQL call object if a Row query
on an int field uses a single = instead of ==. Instead of processing
this at the shard level, we'll process it during the initial
translation step so that it isn't operated on concurrently.
2020-10-15 10:26:14 -05:00
alanbernstein
4bf81444ba
Merge pull request #951 from molecula/shard-dist-api
Add ui-specific endpoint to report shard distribution data
2020-10-14 21:58:05 -05:00
Alan Bernstein
94e82aea86 Consider available shards 2020-10-14 21:11:02 -05:00
Alan Bernstein
ab502a0f8d Add basic test 2020-10-14 21:11:02 -05:00
Alan Bernstein
510902625e Update test hasher implementations 2020-10-14 21:11:02 -05:00
alan
2a902b3a7b Finish basic shard-distribution endpoint 2020-10-14 21:11:02 -05:00
Alan Bernstein
98cd2dc441 WIP shard distribution endpoint 2020-10-14 21:11:02 -05:00
Cody Soyland
8c9569faca
Merge pull request #982 from codysoyland/circle-config-fix
Fix PR detection logic in CI
2020-10-14 21:07:23 -05:00
Cody Soyland
316bd87a8a Fix PR detection logic in CI 2020-10-14 20:36:04 -05:00
jaten-molecula
fa21c81a27
Merge pull request #963 from molecula/rbf_config
rbf: add DBConfig
2020-10-14 20:12:46 -05:00
jaten-molecula
577600d17b
Merge branch 'master' into rbf_config 2020-10-14 19:57:05 -05:00
jaten-molecula
39a31cc63e
Merge pull request #976 from seebs/impossible
Check more carefully for, and also fix, containers with invalid N
2020-10-14 18:57:02 -05:00
jaten-molecula
eb4b9974ac
Merge branch 'master' into impossible 2020-10-14 18:40:50 -05:00
alanbernstein
ca223c4a79
Merge pull request #934 from alanbernstein/cluster-usage
Collect size-on-disk usage data from all nodes
2020-10-14 16:33:03 -05:00
Alan Bernstein
aff5c0fc57 Skip missing directories 2020-10-14 15:46:19 -05:00
Jason E. Aten
244ba21da7 better names 2020-10-14 13:19:14 -05:00
Jason E. Aten
3a40b586b3 disk usage per index 2020-10-14 12:53:35 -05:00
Alan Bernstein
e2cafd98ef Move index size calculation to TxFactory 2020-10-14 12:20:05 -05:00
Alan Bernstein
7a32eadc64 Move disk capacity lookup to gopsutil wrapper package 2020-10-14 12:20:05 -05:00
Alan Bernstein
6a298590be Set omitempty for disk capacity json 2020-10-14 12:20:05 -05:00
Alan Bernstein
ba4ca1a93e Include disk capacity in usage response 2020-10-14 12:20:05 -05:00
Alan Bernstein
2a44bdd25c Collect size-on-disk usage data from all nodes 2020-10-14 12:20:05 -05:00
Cody Soyland
e1e0b298f5
Merge pull request #964 from codysoyland/check-changelog-label
Check for changelog label in CI
2020-10-14 10:11:45 -05:00
Seebs
c88192b4ac Check more carefully for, and also fix, containers with invalid N
In rare cases, RBF can produce containers which have a recorded N value which
is incorrect. This rarely affects anything, but on some particular queries,
this can result in very strange outcomes, like array containers with more
than 1<<16 entries.

To fix this, we have toContainer specify that it doesn't know the correct
N for the bitmap containers it's creating, which costs extra time for counting,
and should be considered a temporary workaround.

Also, we add a CheckN() function which is controlled by the
roaringparanoia flag, and add a number of calls to it, for instance, as
deferred calls after every container operation when roaringparanoia is
enabled. This means that we get improved confidence that we've caught
the relevant errors, but is not suitable for production use.
2020-10-14 10:03:18 -05:00
Cody Soyland
ca5dbe8d39 Fix pull request filter 2020-10-14 09:42:07 -05:00
Cody Soyland
2ac09af894 Only run on pull requests 2020-10-14 09:29:51 -05:00
Cody Soyland
4e66bbb76b Check for changelog label in CI 2020-10-14 09:29:51 -05:00
seebs
6c14aa965d
Merge pull request #895 from seebs/appendSemantics
use append-like semantics consistently for *Container
2020-10-14 09:04:56 -05:00
jaten-molecula
3d4da8f51b
Merge branch 'master' into appendSemantics 2020-10-14 07:24:18 -05:00
jaten-molecula
20d4268676
Merge branch 'master' into rbf_config 2020-10-14 07:19:37 -05:00
jaten-molecula
a997e713dc
Merge pull request #977 from molecula/readers
pilosa-fsck: the -readers flag controls parallelism
2020-10-13 19:14:09 -05:00
Jason E. Aten
50a8db854d pilosa-fsck: the -readers flag controls parallelism
- add path info to the panic if we find a corrupt boltdb
   translation store.
2020-10-13 18:58:38 -05:00
J
3ad8ec7f0f rbf: add runtime options to DB struct
- FsyncEnabled and DoAllocZero moved to DB struct.
 - deletes unused xrbrsupport.go and cmd/convert
 - fixes #941
2020-10-12 18:35:00 -05:00
tgruben
db9e8cd8ef
Merge pull request #966 from molecula/fix843
pilosa/dbshard: allow deleteIndex and then re-use of index
2020-10-12 18:10:04 -05:00
Jason E. Aten
b3e6cdc0d8 pilosa/dbshard: allow deleteIndex and then re-use of index
- fixes #843
2020-10-12 17:46:06 -05:00
jaten-molecula
54255eae15
Merge pull request #957 from molecula/fix-tx-deadlock
Fix deadlock in importWorker()
2020-10-12 17:04:11 -05:00
Ben Johnson
676208ea73 Add test to reproduce deadlock 2020-10-12 12:32:58 -06:00
Ben Johnson
9b83b71c8f Fix error check reference in importWorker() 2020-10-12 11:28:18 -06:00
Ben Johnson
bfe570ef9d Fix deadlock in importWorker()
This commit wraps the work for a view in a function so that the
defer on the finisher executes after each view is processed instead
of at the end.
2020-10-12 11:25:28 -06:00
Cody Soyland
279be13a46
Merge pull request #958 from codysoyland/grpc-compat-update
Better VDSM gRPC compatibility
2020-10-12 09:27:04 -05:00
Cody Soyland
e40b224a4f
Merge branch 'master' into grpc-compat-update 2020-10-12 09:08:42 -05:00
tgruben
d3811634d6
Merge pull request #959 from molecula/parallel_fsck
pilosa-fsck: parallelize Index.ComputeTranslatorSummary
2020-10-09 16:12:36 -05:00
Jason Aten
8da15b18d4 pilosa-fsck: parallelize Index.ComputeTranslatorSummary
- add -index option
2020-10-09 15:53:29 -05:00
Cody Soyland
6c10917ceb Use vdsm.QueryPQLRequest for vdsm.QueryPQLUnary 2020-10-09 13:34:20 -05:00
Cody Soyland
3039aa44af Use copy of vdsm InspectRequest for better compatibility 2020-10-09 13:22:24 -05:00
Cody Soyland
a58c345232 Use copy of vdsm QueryPQLRequest for better compatibility 2020-10-09 13:22:24 -05:00
tgruben
d3ce4fa70e
Merge pull request #948 from molecula/randomquery
pilosa/cmd/random-query: generate random queries from existing schema/data
2020-10-09 12:16:28 -05:00
Jason Aten
937a1afade pilosa/cmd/random-query: generate random queries from existing schema
- fixes molecula/molecula#136
 - flag -n 0 does continuous queries until process is killed.
2020-10-08 18:38:01 -05:00
jaten-molecula
06ee8b16ac
Merge pull request #940 from molecula/fsck_2index_repair
pilosa-fsck: multi-index counts and repairs.
2020-10-07 07:35:08 -05:00
Jason Aten
a63c6ee2b2 pilosa-fsck: multi-index counts and repairs.
- multiple indexes repaired at once could crosstalk. Fixed.
 - the counts of keys and ids are now broken down by index.
2020-10-06 21:10:52 -05:00
tgruben
d41de17503
Merge pull request #931 from molecula/fsck_msg
pilosa-fsck: correct repair -> analysis in pilosa-fsck logging
2020-10-06 13:21:21 -05:00
jaten-molecula
bc8fc23ac9
Merge branch 'master' into fsck_msg 2020-10-06 13:12:46 -05:00
tgruben
3a742a0caa
Merge pull request #938 from molecula/ci_rbf
run CI on rbf and rbf_lmdb
2020-10-06 12:52:33 -05:00
Jason Aten
83f230bbfb run CI on rbf and rbf_lmdb 2020-10-06 12:22:03 -05:00
Ben Johnson
ab6581446d
Merge pull request #889 from molecula/rbf-immutable-wal 2020-10-06 10:45:47 -06:00
Ben Johnson
3b7758a4f2 Add comment explaining rbf.DB.checkpoint() args 2020-10-06 10:36:57 -06:00
Ben Johnson
2440413c49 Remove test skips based on race detector 2020-10-06 10:29:17 -06:00
Ben Johnson
7b64abbb53 Reduce default RBF DB size to 4GB; remove race skips 2020-10-06 09:36:31 -06:00
Ben Johnson
644969e6a9 Add log.* and tourna.* to gitignore 2020-10-06 09:36:31 -06:00
Ben Johnson
265452cf3d Add rbf.SyncEnabled 2020-10-06 09:36:31 -06:00
Ben Johnson
cf208cfaa3 Increase test http client timeout
The timeout was increased to allow additional time for RBF to process
a lot of individual `Set()` commands in `TestMain_RecalculateHashes`.
2020-10-06 09:36:31 -06:00
Ben Johnson
3852ac79c4 Add rbf.DB.TxN() function and test check 2020-10-06 09:36:31 -06:00
Ben Johnson
c978e2242e Skip some RBF tests during race detection 2020-10-06 09:36:31 -06:00
Ben Johnson
c51ba69c09 Fix RBF sync calls 2020-10-06 09:36:31 -06:00
Ben Johnson
3429421148 Fix RBF checkpoint off-by-one WAL ID issue 2020-10-06 09:36:31 -06:00
Ben Johnson
d826cec4b3 fix RBF WAL segment reference error 2020-10-06 09:36:31 -06:00
Ben Johnson
cdd10a26f0 Refactor RBF to use immutable list of WAL segments.
Previously, RBF shared a list of WAL segments between the DB & Tx.
However, this increased the need for mutexes to access the data.

WAL segments are effectively immutable on-disk so the list of segments
has been refactored so that changes to the segment list are done via
copy-on-write which allows read transactions to access segment data
without a mutex.

The database checkpointing can remove early, unused segments and
there is an update/add check to make sure that Tx segments pushed back
to the DB do not include removed segments.
2020-10-06 09:36:31 -06:00
tgruben
8dbfaf5045
Merge pull request #932 from jaten-molecula/pr919
api usage should ignore tx stores. fixes #919
2020-10-06 09:45:43 -05:00
jaten-molecula
ce3a51eb87
Merge branch 'master' into fsck_msg 2020-10-06 09:28:36 -05:00
Jason Aten
6ddeec2ee8 print time at end of fsck run, even if we return 1 to shell 2020-10-06 08:19:53 -05:00
jaten-molecula
4ca42d7f14
Merge branch 'master' into pr919 2020-10-05 21:29:28 -05:00
Travis Turner
12cd4ea69e
Merge pull request #922 from travisturner/design-typos
adjust some wording and typos in the fsck design doc
2020-10-05 20:57:44 -05:00
Jason Aten
c82a1593ff api usage should ignore tx stores. fixes #919
- avoid deadlock in txfactory_internal_test w Qcx
2020-10-05 20:51:38 -05:00
Jason Aten
2f9c51349a correct repair -> analysis in pilosa-fsck logging 2020-10-05 19:59:32 -05:00
Travis Turner
69e15b4719
Merge branch 'master' into design-typos 2020-10-05 18:45:24 -05:00
jaten-molecula
d55ce8723b
Merge pull request #917 from molecula/pr224
ensure that Inspect generates a result for every header item
2020-10-05 17:37:15 -05:00
jaten-molecula
a14b81edbf
Merge branch 'master' into design-typos 2020-10-05 17:29:42 -05:00
Kuba Podgórski
5e2a14400a
Add an extra else branches 2020-10-05 17:13:48 -05:00
Jason Aten
fd87e8d2f3
follow suggestion on https://github.com/molecula/idk/issues/224, does it fix Q2 delete consumer issue?
- apparently not, but this might still be worth doing.
2020-10-05 17:13:48 -05:00
Nia
84830d8361
Merge pull request #921 from niaow/fix-rows-type-error
Generate errors for all unsupported types in Rows calls
2020-10-05 16:32:37 -04:00
Travis
675373f0f3
adjust some wording and typos in the fsck design doc 2020-10-05 14:38:57 -05:00
Nia Weiss
bb0d8bea14
generate errors for all unsupported types in Rows calls 2020-10-05 15:37:05 -04:00
jaten-molecula
9d7945e5be
Merge pull request #918 from molecula/fsck-design
DESIGN.md for pilosa-fsck
2020-10-05 13:36:57 -05:00
Jason E. Aten
05d4b664df groom 2020-10-05 13:29:29 -05:00
Jason E. Aten
9dacbccf8d DESIGN.md for pilosa-fsck
- Orient users to pilosa-fsck by giving an overview of its operations.
2020-10-05 12:51:26 -05:00
jaten-molecula
75b8fa0aa8
Merge pull request #900 from molecula/pilosa-fsck-rb
pilosa-fsck: fsck-like scan and repair of pilosa backups
2020-10-02 16:53:57 -05:00
Jason E. Aten
fe425a84c0 pilosa-fsck: scan and repair of pilosa backups
- the -fix flag repairs replication errors by copying from the primary.
 - the -fixkeys flag repairs any string key translation issues.
 - make pilosa-fsck installs pilosa-fsck and builds release-pilosa-fsck.COMMIT.GOOS.tar.gz release tarbar
2020-10-02 16:47:56 -05:00
jaten-molecula
d9f8de170c
Merge pull request #906 from jaffee/revert-confirm-down-retries
retrun confirmDownRetries to 10, tourney mac compatible
2020-10-02 15:15:57 -05:00
Matt Jaffee
c2a93cc886
retrun confirmDownRetries to 10, tourney mac compatible
At some point the cluster code was modified to do 120 tries to confirm
if a node was down which is a bit excessive for production. My
understanding is that this was done to help trigger or fix a problem
during testing which is hopefully no longer relevant.

Also changed the tournament script to use what I think are more
standard bash-isms that work on mac. Please confirm this still works
on Linux as well.
2020-10-02 14:24:16 -05:00
seebs
df89ebf394
Merge pull request #844 from seebs/setRowCache
Update cache entries when setting a row even in shards with no new data
2020-10-02 13:20:54 -05:00
Seebs
aec11ff713 Update cache entries when setting a row even in shards with no new data
The logic assumes that the lack of a corresponding rowSegment means that
there's no changes, but that's not true -- we just deleted all the
existing data! Update to match clearRow behavior better.

Also, add a corresponding test case for this.

Also, change references to 'defaultSnapshotQueue' to use
[fragment].holder.SnapshotQueue, because defaultSnapshotQueue was
the queueless queue, but holders were getting a snapshot queue,
meaning that "awaiting" a snapshot could result in moving on
and closing the holder before the actual snapshot queue finished
snapshotting.
2020-10-02 12:57:18 -05:00
Nia
7006233cf8
Merge pull request #915 from niaow/distinct-error-handling
Fix panic when "field" parameter not present on Distinct call (error instead)
2020-10-01 14:58:29 -04:00
Nia
ad0f0b1725
Merge branch 'master' into distinct-error-handling 2020-10-01 14:47:16 -04:00
alanbernstein
afc6f432bd
Merge pull request #647 from alanbernstein/ui-feature-support
Add more info to some endpoints to support UI
2020-10-01 13:19:35 -05:00
Nia Weiss
415f7d20b8
add field parameter error handling to Distinct 2020-10-01 12:09:34 -04:00
Alan Bernstein
8a3d93fe78 Move TransactionList endpoint to UI namespace 2020-10-01 10:45:08 -05:00
Alan Bernstein
46d010fe18 Remove 'NodeStates' from /status until it is reliable 2020-10-01 10:43:49 -05:00
Alan Bernstein
1a04e94fc1 Remove clusterID and default name to ID 2020-10-01 10:43:49 -05:00
Alan Bernstein
5705b45864 Add a CLI flag for cluster name and include it in /status response 2020-10-01 10:43:49 -05:00
Alan Bernstein
8fad2b92c2 Add all node states to /status response 2020-10-01 10:43:49 -05:00
Alan Bernstein
664c911791 Include replicaN in /info response 2020-10-01 10:43:49 -05:00
alanbernstein
11e4bbfc67
Merge pull request #907 from alanbernstein/cluster-data-endpoint
Add single-node 'bytesOnDisk' object to /status response
2020-10-01 10:42:27 -05:00
Alan Bernstein
b6af0e5219 Move usage data to new ui-specific endpoint 2020-10-01 10:25:48 -05:00
Alan Bernstein
6f2fe3afe1 Address linter issue 2020-10-01 10:25:48 -05:00
Alan Bernstein
474261f12f Add single-node 'bytesOnDisk' object to /status response 2020-10-01 10:25:48 -05:00
Nia
cac89f4c96
Merge pull request #913 from niaow/misaligned-distinct
Fix misaligned bitmaps in Distinct
2020-10-01 11:17:29 -04:00
Nia Weiss
e0f9870378
fix misaligned bitmaps in Distinct
This resolves an issue in which a filter would eliminate all bits in all shards other than shard 0.
2020-10-01 10:07:08 -04:00
Nia
f030792b8a
Merge pull request #905 from niaow/syncrace
Fix race condition when resetting translation
2020-09-30 09:26:43 -04:00
Nia
c31d2f143c
Merge branch 'master' into syncrace 2020-09-30 09:17:59 -04:00
Cody Soyland
86c5e040b1
Merge pull request #870 from codysoyland/grpc-compat
Fully backwards-compatible VDSM gRPC interface
2020-09-29 15:58:20 -06:00
Jason Aten
0e8564e2be fix license exceptions for vdsm/proto 2020-09-29 15:30:41 -05:00
Cody Soyland
949ca882fc Remove unused helper func 2020-09-29 15:30:12 -05:00
Cody Soyland
fdce92eb40 Use shared types from pilosa proto file and enable generic proxying of query calls 2020-09-29 15:30:12 -05:00
Cody Soyland
83704e7973 Proxy unary methods to Pilosa gRPC handler 2020-09-29 15:29:37 -05:00
Cody Soyland
c1e50ed909 Remove VDS references from pilosa proto file, begin porting VDSM service 2020-09-29 15:29:37 -05:00
Nia Weiss
1b210080c6
fix race condition when resetting translation
Previously, we never waited for translation sync goroutines to stop.
That issue should be mostly harmless in the normal path.
Additionally, this waits for the translation sync to shut down when stopping the server.
2020-09-29 13:54:16 -04:00
Seebs
fbb648b1fb enforce append-like semantics for *Container more consistently
The copy-on-write/rowCache changes require that functions that
modify containers be able to generate new containers. Once that
became possible, some significant pool of other operations
started relying on it -- for instance, operations might return
a new container even though they're in theory "in place" operations.

I developed a tool for checking for unused function return
values (github.com/molecula/noticeme), and ran it on this, and
picked out the places where `*Container` values were generated
but not used, and some of them seem to be potentially-real
bugs, and a few are probably harmless. Updated code to make
those diagnostics go away.
2020-09-28 12:19:24 -05:00
tgruben
82cdffd309
Merge pull request #899 from jaten-molecula/better_err_on_closed_db
better error reporting when making Tx on closed lmdb
2020-09-26 09:59:15 -05:00
Jason Aten
5a3170a9a4 better error reporting when making Tx on closed lmdb
- return error instead of panic.
2020-09-26 06:24:58 -05:00
Nia
3d07d13a7c
Merge pull request #897 from niaow/no-explicit-recalculate
Stop explicitly recalculating caches in tests
2020-09-25 11:18:37 -04:00
Nia Weiss
79f68f7a2e
stop explicitly recalculating caches in tests
This is no longer necessary, as caches now recalculate on read.
Also, in general a user will not explicitly request recalculation, so it would make sense for our tests to reflect that.
2020-09-25 10:36:00 -04:00
jaten-molecula
f026c43649
Merge pull request #891 from molecula/optimize_directadd
Bitmap.DirectAdd avoids returning overfull containers
2020-09-23 17:25:29 -05:00
Jason Aten
7ae8accfa8 Bitmap.DirectAdd avoids returning overfull containers
- log Debugf when we repair a fragment block
- better run-run roaring testing for over-sized containers
- add which fragment path to panic on container too big
- include container contents in roaring hash for pilosa-chk/pilosa-check-backup
2020-09-23 17:13:25 -05:00
Nia
4b5572da1d
Merge pull request #893 from niaow/limit-cluster
Fix Limit when used as an input to another query
2020-09-23 13:51:11 -04:00
Nia
e0f25f1d20
Merge branch 'master' into limit-cluster 2020-09-23 13:46:02 -04:00
Nia Weiss
2158417f34
fix Limit when used as an input to another query 2020-09-23 13:08:48 -04:00
Kuba Podgórski
8b855b97d0
Merge pull request #892 from kuba--/fix-545
Translate field IDs on coordinator
2020-09-23 17:49:15 +02:00
Kuba Podgórski
ba5438e02b Trabnslate field IDs on coordinator 2020-09-23 15:21:33 +02:00
jaten-molecula
f7afd5d2e1
Merge pull request #875 from molecula/remove_debug_tooling
turn on row cache. tx: remove debug tooling
2020-09-22 15:56:02 -05:00
Jason Aten
55d4c29933 turn off debug machinery on tx backends
- enable row cache again. Was off for tx perf measurement.
 - centralize UseRowCache choice to just one point, in rbf.EnableRowCache
2020-09-22 15:47:52 -05:00
Kuba Podgórski
886318931d
Merge pull request #882 from kuba--/fix-869
Fix holes in grpc response for inspect
2020-09-22 14:20:10 +02:00
Kuba Podgórski
b54a289cd8 Fix holes in grpc response for inspect 2020-09-22 12:02:26 +02:00
jaten-molecula
ba2851229d
Merge pull request #871 from molecula/bluegreen_verify
blue_green verify accepts empty fragments
2020-09-18 11:55:22 -04:00
Jason Aten
57be5392cf blue_green verify accepts empty fragments 2020-09-18 10:49:36 -05:00
tgruben
8ec10ec1e8
Merge pull request #868 from molecula/with_primary_instead_owner
Translate only on coordinator/primary
2020-09-17 17:12:56 -05:00
Kuba Podgórski
650244214d Translate only on coordinator/primary
- This is the commit message #3:
2020-09-17 17:03:29 -05:00
jaten-molecula
7dc59c4d9c
Merge pull request #831 from molecula/rbf-tx-cursor-stack-allocate
Stack allocate return from rbf.Tx.cursor()
2020-09-16 23:40:32 -04:00
Jason Aten
9b99009871 allow rbf-tx-cursor-stack-allocate to merge; fix conflict with the new HasData code 2020-09-16 22:35:30 -05:00
jaten-molecula
c04642f34b
Merge branch 'master' into rbf-tx-cursor-stack-allocate 2020-09-16 23:21:35 -04:00
jaten-molecula
0f5838816b
Merge pull request #857 from molecula/rbf-fix-direct-write-corruption
Fix RBF write corruption during direct write.
2020-09-16 23:07:57 -04:00
jaten-molecula
71033f78b5
Merge branch 'master' into rbf-fix-direct-write-corruption 2020-09-16 22:32:47 -04:00
Nia
16c5212e2d
Merge pull request #858 from niaow/deprecate-inspect
Add a deprecation warning to Inspect
2020-09-16 15:31:57 -04:00
Nia Weiss
f03ba0e682
add a deprecation warning to Inspect 2020-09-16 12:30:12 -04:00
Ben Johnson
6918fe2f60
Merge branch 'master' into rbf-fix-direct-write-corruption 2020-09-16 09:55:26 -06:00
jaten-molecula
75b01ba87d
Merge pull request #850 from molecula/db_has_data_rb
blue_green verification and migration capabilities.
2020-09-16 11:22:34 -04:00
Ben Johnson
458984c756 Fix RBF write corruption during direct write.
This commit fixes an issue where direct writes would overwrite the
source page where data was being copied from because writes are
immediate (instead of going to the WAL first).
2020-09-16 08:59:53 -06:00
Jason Aten
31d54010f8 blue_green verification and migration capabilities.
- on startup in blue_green mode, we will migrate
   blue to green if blue is empty.
 - otherwise, when blue has data, we verify
   against green before proceeding with the
   blue_green run.
 - small optimization in the rbf cursorx.go to
   short-circuit processing on a nil bitmap.
   This avoids a roaringparanoia tag panic.
 - back out holdbkg.go, was too slow.
   add a distinct Holder.imu lock instead.
2020-09-16 09:41:05 -05:00
Kuba Podgórski
dd4ffff704
Merge pull request #853 from kuba--/fix-translate_index_keys
Fix translation index keys
2020-09-16 15:21:31 +02:00
Kuba Podgórski
fa159c9a17
Merge branch 'master' into fix-translate_index_keys 2020-09-16 15:09:49 +02:00
jaten-molecula
47926092ee
Merge pull request #855 from molecula/cleanup_cherrypick
cleanup lmdb tests that were leaving test directories behind
2020-09-16 07:51:23 -04:00
Jason Aten
c093aa9d0e cleanup lmdb tests 2020-09-16 06:46:12 -05:00
Kuba Podgórski
ab26134992
Merge branch 'master' into fix-translate_index_keys 2020-09-16 10:39:32 +02:00
Kuba Podgórski
a8a9a73b6c Fix translation index keys 2020-09-16 10:36:44 +02:00
tgruben
793cf934b8
Merge pull request #848 from molecula/fix_843
don't panic if dbs is reopened under roaring only. fixes #843
2020-09-15 16:54:52 -05:00
Jason Aten
439ac243b4 don't panic if dbs is reopened under roaring only. fixes #843 2020-09-15 15:52:05 -05:00
Kuba Podgórski
e83e18832a
Merge pull request #847 from kuba--/fix-writable
Fix TranslateStore writable
2020-09-15 20:36:27 +02:00
Kuba Podgórski
dcc237413e
Merge branch 'master' into fix-writable 2020-09-15 19:14:24 +02:00
Nia
c7a0b17600
Merge pull request #846 from niaow/typenames
Fix type names in PQL Extract and SQL Show
2020-09-15 13:13:32 -04:00
Kuba Podgórski
76715a3f9d Fix TranslateStore writable 2020-09-15 19:09:59 +02:00
Nia Weiss
19e0f3601b
fix type names in PQL Extract and SQL Show 2020-09-15 12:53:21 -04:00
tgruben
9649819c09
Merge pull request #845 from molecula/rebal_together_rb
fine tune Tx placement, make it lazier so we don't create extra shards.
2020-09-14 18:34:15 -05:00
jaten-molecula
28fa1d036e
Merge branch 'master' into rebal_together_rb 2020-09-14 19:21:31 -04:00
Jason Aten
748f6a61bc fine tune Tx placement, isolate Tx backends more.
- Tx creation is lazier so we don't create xtra shards.

 - Then the dir scan for blue-green state checking finds only the right shards.
2020-09-14 18:03:25 -05:00
Travis Turner
37196b5e03
Merge pull request #834 from travisturner/metric-typos
Fix typos in transaction metric names
2020-09-14 17:32:53 -05:00
Travis Turner
d2c6a8ddde
Merge branch 'master' into metric-typos 2020-09-14 17:20:49 -05:00
seebs
72b1eec2e9
Merge pull request #829 from seebs/storeDistinct
Test cases for Store(Distinct)
2020-09-14 14:28:05 -05:00
Seebs
e7b239d2d8 Test cases for Store(Distinct)
This adds testing for Store(Distinct(...)) with and without filters, to verify that
we can, in fact, store the results of a Distinct() query directly. This was at one
point unsupported, now we think it should work so we're testing it.

The change to the testdata is because the specific structure used for this test doesn't
work with a keyed index, and changing things to be "foreign keys" seems annoying and
more complicated, but possibly that should become part of a future test.

There was talk of testing this with non-BSI fields, but they don't seem to
actually work with Distinct right now, so that will be later.
2020-09-14 14:15:29 -05:00
seebs
88d0f541aa
Merge pull request #841 from seebs/setMapped
export SetMapped from roaring, use it in Tx stores
2020-09-14 14:15:11 -05:00
Seebs
8ab9174a09 export SetMapped from roaring, use it in Tx stores
Thaw() is supposed to always provide writable storage, which it does
by ensuring that containers aren't frozen, but also by cloning or
copying their data if the data is marked as being memory-mapped.

But only the roaring backend had the ability to mark data as memory-mapped,
because that wasn't exported. Fixed this, and added corresponding code
to badger, lmdb, and rbf.
2020-09-14 14:08:00 -05:00
Ben Johnson
343c446b7e
Merge pull request #839 from molecula/rbf-fix-checkpoint
Remove tx before issuing checkpoint.
2020-09-14 12:56:03 -06:00
jaten-molecula
d0ce2e1207
Merge branch 'master' into rbf-fix-checkpoint 2020-09-14 13:58:24 -04:00
alanbernstein
1e40af7e25
Merge pull request #830 from alanbernstein/metrics-json-fix
Fix panicking metrics.json endpoint
2020-09-14 12:28:39 -05:00
Ben Johnson
77661a891d Remove tx before issuing checkpoint. 2020-09-14 10:36:59 -06:00
Alan Bernstein
18a593008a Add simple tests for metrics endpoints 2020-09-14 11:34:59 -05:00
Alan Bernstein
acbd6ec37c New channel per node 2020-09-14 11:34:59 -05:00
jaten-molecula
fc2518e2d1
Merge pull request #838 from molecula/fix_rr_leaks
fix TestImportClearRestart resource leaks under roaring, better skipForRoaring func
2020-09-14 11:38:26 -04:00
Jason Aten
7028bcfc9d fix resource leaks in fragment_internal_test.go under roaring, better skipForRoaring func
- add tournament.sh to do all pair-wise comparisons of blue-green backends.
- isolate txstores away from roaring index/ directories with indexname.index.txstores@@@ dirs.
2020-09-13 22:45:39 -05:00
Travis Turner
a0db828701
Merge pull request #835 from travisturner/port-vdsm-metrics
Port over VDSM metrics
2020-09-11 16:28:36 -05:00
Travis
41e0465eda
Port over VDSM metrics 2020-09-11 16:03:50 -05:00
Travis
75644af64d
Fix typos in transaction metric names 2020-09-11 15:18:39 -05:00
tgruben
5e00dbadc2
Merge pull request #812 from molecula/bgdev_rb
blue_green migration; holdbkg.go holder goroutine.
2020-09-11 14:32:09 -05:00
Jason Aten
2eb097c14d blue_green migration. holdbkg.go holder goroutine.
- blue_green for doing migration. Called before Holder.Open finishes.
- holdbkg.go added for index lookup. Less wedging between a deadlock and a race.
- fix fault under read-only map under lmdb at
  TestExecutor_Execute_Row_Range/RowIDColumnID by doing cow in roaring.
- roaring -tags gofuzz builds again
- roaringparanoia build tag added to make test targets in Makefile
- add rbf.NewDBWithAllocZero for out-of-bounds memory checks
- .circleci/config.yml test-shardwidth-22 with large run container, kept OOM-ing we suspect.

Fixes #819
2020-09-11 14:24:15 -05:00
Antonio Navarro Perez
a68ee73f00
Merge pull request #782 from ajnavarro/remove-blake3-duplicated-code 2020-09-11 11:23:25 +02:00
jaten-molecula
2cc7c829cd
Merge branch 'master' into remove-blake3-duplicated-code 2020-09-11 01:14:02 -04:00
Ben Johnson
5b72a68628 Stack allocate return from rbf.Tx.cursor() 2020-09-10 14:58:22 -06:00
Nia
be944a6247
Merge pull request #822 from niaow/inspect-panic
Fix inspect panic from incorrect handling of the many types of empty argument
2020-09-09 09:39:20 -04:00
Nia Weiss
6ff224308d
fix inspect panic from incorrect handling of the many types of empty argument 2020-09-09 09:11:21 -04:00
seebs
db578423bb
Merge pull request #818 from seebs/roaringDoc
Roaring documentation updates and fixes resulting from them
2020-09-08 22:41:48 -05:00
Seebs
c079d4764b Check for possibly-dirty N values in containers modified in-place
After documenting the semantics, I noticed an arguable hole in them,
which is that you could Freeze() a dirty container, and then Repair()
wouldn't work on it. On further study, I added a roaringparanoia
check for attempts to access the N of dirty containers.

It turns out there's several such. But also, it turns out, there's
circumstances where unionInPlace is relying on the assumption that
N is valid, which it isn't always for dirty containers. Also, there's
at least one case where we rely on the assumption that forcibly
thawing a container, then calling unionInPlace on it, always modifies
that container. But that's not supposed to be true for an empty
container -- an empty container might be better handled by just
returning the container it's being unioned with. So, we drop the
unnecessary thaw (all the *InPlace ops are already thawing if/when
they need to), but we use the return from unionInPlace.
2020-09-08 13:16:54 -05:00
Seebs
17ba2e35a9 call helper functions every time to get new run slices
If you just stash the results of the function when defining the test cases, the
outcome is in part that you are reusing the same slices for multiple things. So,
for instance, if you perform a union on the OddBitsSet slice, with the EvenBitsSet
slice, the result is to overwrite the first entry in that slice with the 0-ffff
run... But the original slice still exists, and then we reuse it and get a slice
with a bit count of around 98,000. The underlying issue is that doContainer()
is calling NewContainerRun(), which is simply using the provided slice, not
copying it -- which is intentional, but the test has to be careful about it.

We call repair on the one we think should be a bitmap. Theoretically
maybe we should also repair the other one in case unionRunRun some day
starts returning unrepaired bitmaps, which in principle it's allowed to
do...
2020-09-08 12:38:49 -05:00
Seebs
ecacbf65d4 Document copy-on-write semantics, at all.
The copy-on-write semantics were previously documented only in
the 125-line commit log from the patch which introduced them. Add
documentation for them in a few likely places.
2020-09-08 11:44:56 -05:00
Ben Johnson
8397696a59
Merge pull request #813 from molecula/import-roaring-direct
Add direct import option; remove tx from fragment.Open()
2020-09-08 08:19:24 -06:00
Ben Johnson
a51c530763 Add direct import option; remove tx from fragment.Open() 2020-09-08 08:13:16 -06:00
Ben Johnson
62635e7668
Merge pull request #811 from molecula/fix-wal-replay
Fix RBF WAL replay/truncation
2020-09-08 08:11:00 -06:00
Ben Johnson
f2bde49d25 Fix RBF WAL replay/truncation 2020-09-07 09:39:26 -06:00
Antonio Navarro Perez
ca340f7e62 [hash] Remove duplicated blake3 code
blake3 code is used in several places on the code. The file was
duplicated on root and rbf package.

To avoid cyclic dependencies, I moved it to hash package. Some methods
must be public to use them in different places.

HashOfDir method was removed. Not used.

Signed-off-by: Antonio Navarro Perez <antnavper@gmail.com>
2020-09-07 12:24:22 +02:00
jaten-molecula
1994560cf6
Merge pull request #803 from molecula/fgen_refined
refine our f.gen nil handling to support blue/green
2020-09-04 18:52:32 -04:00
Jason Aten
63187f59ee refine our f.gen nil handling to support blue/green 2020-09-04 17:28:38 -05:00
Ben Johnson
278d518f03
Merge pull request #800 from molecula/rbf-exclusive-write
Add RBF exclusive lock mode
2020-09-04 13:41:52 -06:00
Ben Johnson
90663e070e
Merge branch 'master' into rbf-exclusive-write 2020-09-04 13:10:02 -06:00
jaten-molecula
bb101ebef2
Merge pull request #802 from molecula/fgen
restore former f.gen nil behavior
2020-09-04 14:56:45 -04:00
Ben Johnson
159d01b55d Add exclusive write option for RBF.
This commit adds the ability to start a transaction with an exclusive
lock for the entire database. This ensures no other read or write
transactions can run at the same time. Writes in this mode write
directly to the database and skip the WAL entirely.
2020-09-04 12:46:42 -06:00
Jason Aten
4b6e773c7c panic if f.gen is nil because it means the storage wasn't open 2020-09-04 13:19:46 -05:00
jaten-molecula
aebe028854
Merge pull request #777 from molecula/dbshard2
database per shard, HolderConfig, rbf bit-wise import speedups.
2020-09-04 14:04:13 -04:00
Ben Johnson
150c8a5b06 database per shard, HolderConfig, rbf bit-wise import speedups.
- introduce Query Context (Qcx) for managing database-per-shard.
- replaces the MultiTx, so mtx.go is retired and removed.
- introduces the HolderConfig struct and all Holders now have
  a path from birth.
- rbf speedups on bitwise writes
- badgerdb is removed due to unresolvable write conflicts.

fixes #703 #676
2020-09-04 13:00:33 -05:00
Nia
60da12a8e5
Merge pull request #784 from niaow/remove-sql-limit
Remove SQL artificial limit
2020-09-04 08:46:33 -04:00
Nia
ec41481523
Merge branch 'master' into remove-sql-limit 2020-09-04 08:11:48 -04:00
jaten-molecula
921f15991b
Merge pull request #788 from molecula/fldschk
pilosa-chk: show col, row translation, -v for fragment details
2020-09-03 19:20:11 -04:00
Jason Aten
1e87d113f1 pilosa-chk: col, row translation reported, -v shows fragment checksum 2020-09-03 18:14:49 -05:00
tgruben
17ddcacbd4
Merge pull request #787 from molecula/chktrans
cmd/translatorchk checksums and summarizes key counts from column key translators
2020-09-03 15:29:31 -05:00
Jason Aten
56803e6632 pilosa/cmd/translatorchk checksums and summarizes key counts from column key translators.
opens ~/.pilosa/index/_keys boltdbs and hashes the StringKey->ID mappings.
2020-09-03 15:23:05 -05:00
Nia Weiss
b676292d67
add named returns to clarify extractLimitOffset 2020-09-03 10:37:09 -04:00
Nia Weiss
daa784319c
remove SQL artificial limit 2020-09-03 09:14:11 -04:00
jaten-molecula
d3485dbdb3
Merge pull request #776 from niaow/configure-slurp
Configure slurp
2020-09-03 02:33:58 -04:00
jaten-molecula
0a548a9009
Merge branch 'master' into configure-slurp 2020-09-03 01:56:30 -04:00
Nia
82e5f09cb3
Merge pull request #779 from niaow/pg-race
Fix race condition in pg cancellation test
2020-09-02 19:07:28 -04:00
Nia Weiss
c9995bfae1
fix race condition in pg cancellation test 2020-09-02 19:01:47 -04:00
Nia
bd1b76e25e
Merge pull request #760 from niaow/pg-local-cancel
Add support for local cancellation to postgres endpoint
2020-09-02 15:05:31 -04:00
Nia
219e22cc64
Merge branch 'master' into pg-local-cancel 2020-09-02 14:02:18 -04:00
Nia Weiss
95d261b4d7
configure slurp 2020-09-02 12:53:16 -04:00
jaten-molecula
c213cb5457
Merge pull request #762 from molecula/optimize-import
rbf bitwise import optimizations
2020-09-01 21:44:46 -04:00
jaten-molecula
55cb448c37
Merge branch 'master' into optimize-import 2020-09-01 21:18:57 -04:00
Travis Turner
836d055978
Merge pull request #759 from travisturner/join-bug
fix bug on left/right join mapping
2020-09-01 19:06:49 -05:00
Kuba Podgórski
6255c3f07f
Merge branch 'master' into join-bug 2020-09-02 01:51:48 +02:00
Travis Turner
72c068a64f
Merge pull request #773 from travisturner/row-pointer
return *Row instead of Row on empty key result
2020-09-01 18:31:29 -05:00
Travis
32b5826d1a
fix bug on left/right join mapping 2020-09-01 18:22:55 -05:00
Travis
d02cb10687
return *Row instead of Row on empty key result 2020-09-01 17:34:29 -05:00
alanbernstein
58007ab7ef
Merge pull request #765 from alanbernstein/lattice-release
Add embedded UI to default release process
2020-09-01 13:27:08 -05:00
Alan Bernstein
007b3ffc3c re-re-arrange error checks 2020-09-01 11:32:58 -05:00
Alan Bernstein
4ce3879e2c Revert UI->Lattice name change, correct the ordering of error checks in statikHandler 2020-09-01 10:16:31 -05:00
Alan Bernstein
a597a79e2d Add embedded UI to default release process 2020-09-01 10:16:31 -05:00
Kuba Podgórski
2d202d6aa3
Merge pull request #766 from kuba--/fix-null
support null results
2020-09-01 15:33:02 +02:00
Kuba Podgórski
ceb72fc3a9 support null results 2020-09-01 13:00:30 +02:00
Nia
1e5f0ecc5d
Merge pull request #763 from niaow/sql-leak
Fix SQL memory leak
2020-08-31 14:38:23 -04:00
Nia Weiss
d421558f58
fix SQL memory leak 2020-08-31 13:10:49 -04:00
Nia
96a8fd5cb7
Merge pull request #724 from niaow/pg-primitive
Add primitive types to pg encoder
2020-08-31 13:04:39 -04:00
Nia Weiss
9f368b06bd
add licesnse header to pg formatter test 2020-08-31 12:42:37 -04:00
Nia Weiss
542eb2dba3
pg formatter tests 2020-08-31 12:39:55 -04:00
Nia Weiss
cfcc1da0bb
add primitive types to pg encoder 2020-08-31 12:27:10 -04:00
Nia
9b12c01c2b
Merge pull request #736 from niaow/dirtycache
Force ranked cache recalculation in Top after a skipped invalidation
2020-08-31 11:56:50 -04:00
Nia
ac57a0bd42
Merge branch 'master' into dirtycache 2020-08-31 11:28:52 -04:00
Ben Johnson
1672158e65
Merge branch 'master' into optimize-import 2020-08-31 08:58:12 -06:00
Ben Johnson
627b50d89d misc import optimizations 2020-08-31 08:46:53 -06:00
alanbernstein
b2cad11c99
Merge pull request #733 from alanbernstein/enlattice
Embed lattice via statik
2020-08-31 09:40:02 -05:00
Alan Bernstein
b14ebcadae Unexport statik filesystem 2020-08-31 09:02:46 -05:00
Cody Soyland
a3e122dc5e Fix CORS support by applying middleware to router. 2020-08-31 09:02:46 -05:00
Alan Bernstein
f9d3040827 Update gitignore and makefile 2020-08-31 09:02:46 -05:00
Alan Bernstein
384b6511cd Replace null with [] in /schema field response 2020-08-31 09:02:46 -05:00
Alan Bernstein
659a2bb560 Silence stderr in makefile 2020-08-31 09:02:46 -05:00
Alan Bernstein
6e917b6a9a Log lattice version info 2020-08-31 09:02:46 -05:00
Alan Bernstein
99869ce792 Minor fixes 2020-08-31 09:02:46 -05:00
Alan Bernstein
b039bd50a1 Switch to mux PathPrefix matcher entirely 2020-08-31 09:02:46 -05:00
Alan Bernstein
19ee27fdb6 Use SPA handler to serve from filesystem, to test routing behavior 2020-08-31 09:02:46 -05:00
Alan Bernstein
d912403eb2 Add missing file 2020-08-31 09:02:46 -05:00
Alan Bernstein
88a288e775 Embed lattice via statik 2020-08-31 09:02:46 -05:00
Kuba Podgórski
b88c5cd5ea
Merge pull request #753 from kuba--/grpc-errcode
Add rich error types to gRPC interface
2020-08-31 15:45:24 +02:00
Kuba Podgórski
bfff643f34 Fix error code for PostVDS 2020-08-31 12:16:07 +02:00
Kuba Podgórski
f7a0e5f536 Fix error code for DeleteVDS 2020-08-31 12:14:58 +02:00
Kuba Podgórski
bf17385409
Merge branch 'master' into grpc-errcode 2020-08-31 10:25:35 +02:00
Nia Weiss
bf7e4b5583
add support for local cancellation to postgres endpoint 2020-08-29 14:56:49 -04:00
Nia
9c0d45a2d9
Merge pull request #752 from niaow/batch-translate
Batch the translation of field keys in results
2020-08-28 15:25:19 -04:00
Nia
5bc5991bd7
Merge branch 'master' into batch-translate 2020-08-28 15:09:05 -04:00
Kuba Podgórski
25ead95967
Merge branch 'master' into grpc-errcode 2020-08-28 20:42:28 +02:00
Nia Weiss
8549b74421
merge groupcount translations check into an else-if 2020-08-28 14:22:35 -04:00
Nia
a872548b01
Merge pull request #756 from niaow/postgres-sql-empty
Write empty column headers in postgres when there is no response
2020-08-28 14:20:22 -04:00
Nia Weiss
57fdabfe40
write empty column headers in postgres when there is no response 2020-08-28 13:28:50 -04:00
Travis Turner
81d32a6eeb
Merge pull request #751 from travisturner/sql-mapper-stuff
add sql mapper routes for count(*) on joins
2020-08-28 11:55:49 -05:00
Travis Turner
27c2368b9a
Merge branch 'master' into sql-mapper-stuff 2020-08-28 11:43:16 -05:00
Nia Weiss
0a9699490b
batch the translation of field keys in results 2020-08-28 11:56:37 -04:00
Kuba Podgórski
2b1c9950f4 Add rich error types to gRPC interface 2020-08-28 17:24:49 +02:00
jaten-molecula
f2fb2c98f8
Merge pull request #737 from molecula/bulk-import-value
Implement TxBitmap to cache up many bit changes in a Tx
2020-08-28 00:43:46 -04:00
Travis
ae07aacd51
add sql mapper routes for count(*) on joins 2020-08-27 22:49:54 -05:00
jaten-molecula
4883424205
Merge branch 'master' into bulk-import-value 2020-08-27 21:43:32 -04:00
Nia
9c621d7622
Merge pull request #742 from niaow/pg-require-tls
Fix postgres configuration
2020-08-27 12:49:45 -04:00
Nia Weiss
f0f8336f57
rename postgres.addr to postgres.bind for consistency 2020-08-27 11:35:54 -04:00
Ben Johnson
e3db6c9cec Implement bulk value import 2020-08-27 09:20:23 -06:00
Nia Weiss
68542ddc35
force ranked cache recalculation in Top after a skipped invalidation 2020-08-27 11:05:18 -04:00
Nia Weiss
288593231f
require TLS when configured 2020-08-27 10:59:40 -04:00
Nia Weiss
47dd6b5b8f
fix postgres endpoint config 2020-08-27 10:49:43 -04:00
Nia Weiss
c2823a933b
add a config option to require TLS on postgres 2020-08-27 10:49:43 -04:00
Kuba Podgórski
475a9e3910
Merge pull request #732 from kuba--/translatekey-writable
Add writable argument to TranslateKey functions.
2020-08-27 16:15:49 +02:00
Kuba Podgórski
248ec3a296
Merge branch 'master' into translatekey-writable 2020-08-27 16:10:29 +02:00
Kuba Podgórski
6b9eed3af4
Merge pull request #743 from kuba--/fix-info
Pass name to newNotFoundError
2020-08-27 16:08:40 +02:00
Kuba Podgórski
2ca8ca0605 Pass name to newNotFoundError 2020-08-27 15:35:23 +02:00
Nia
c3174efdeb
Merge pull request #740 from niaow/xor-array-array
Speed up xorArrayArray
2020-08-26 17:30:57 -04:00
Nia Weiss
6e06cc0c30
speed up xorArrayArray 2020-08-26 17:14:24 -04:00
Kuba Podgórski
9cf58ddb1c Add writable argument to TranslateKey functions. 2020-08-26 09:57:50 +02:00
Kuba Podgórski
ca5e27ad8e
Merge pull request #721 from kuba--/invalid-query/fix-706
Fix https://github.com/molecula/pilosa/issues/706
2020-08-25 21:33:54 +02:00
Kuba Podgórski
b67148ccfc
Merge branch 'master' into invalid-query/fix-706 2020-08-25 20:38:30 +02:00
Nia
6db4c8bb3a
Merge pull request #725 from niaow/storekeyed
Fix Store() into a keyed set
2020-08-25 14:08:46 -04:00
Kuba Podgórski
24980c7af8
Merge branch 'master' into invalid-query/fix-706 2020-08-25 19:30:03 +02:00
Nia Weiss
a8e7c2d0ef
fix Store() into a keyed set 2020-08-25 11:39:03 -04:00
jaten-molecula
8ad3dd2de6
Merge pull request #728 from molecula/skip_cluster_tests_bg
TestClusterResize_AddNode,AddNodeConcurrentIndex skipped blue-green roaring
2020-08-25 04:06:49 -04:00
tgruben
06281fc0c8
Merge branch 'master' into skip_cluster_tests_bg 2020-08-25 02:30:00 -05:00
tgruben
15315e47ee
Merge pull request #729 from molecula/fix683
test roaring.Container.UnionInPlace does not overflow
2020-08-25 02:29:29 -05:00
jaten-molecula
bc18e4dc42
Merge branch 'master' into skip_cluster_tests_bg 2020-08-25 03:28:00 -04:00
Jason Aten
ca7552b4a8 test that roaring.Container.UnionInPlace does
not overflow two big arrays into an invalid array.

recreate badloader from git history, at 85fa67e8. Could not
reproduce this, but lots of container usage
also got updated in the meantime.

Fixes #683
2020-08-24 17:06:41 -05:00
jaten-molecula
c5660ed712
Merge pull request #701 from molecula/fix-rbf-race
Remove duplicate checkpoint() invocation
2020-08-24 16:19:20 -04:00
jaten-molecula
ec02d5a00e
Merge branch 'master' into fix-rbf-race 2020-08-24 16:06:25 -04:00
jaten-molecula
0e6b65d3c2
Merge branch 'master' into skip_cluster_tests_bg 2020-08-24 16:02:13 -04:00
Jason Aten
48f31ddce7 TestClusterResize_AddNode and TestClusterResize_AddNodeConcurrentIndex skipped under blue-green test with roaring 2020-08-24 14:57:08 -05:00
alanbernstein
14a9a838eb
Merge pull request #690 from alanbernstein/prometheus-json
Add metrics.json endpoint using prometheus/prom2json
2020-08-24 14:29:27 -05:00
Alan Bernstein
9b027a89b9 Return dict of all node metrics 2020-08-24 14:04:02 -05:00
Alan Bernstein
d9b670c83c Add metrics.json endpoint using prometheus/prom2json 2020-08-24 14:04:02 -05:00
Sarah
6c4e8aeff5
Merge branch 'master' into fix-rbf-race 2020-08-24 13:23:56 -05:00
jaten-molecula
0e4144c302
Merge pull request #709 from molecula/audit3rb
testhook: Leak auditing infrastructure
2020-08-24 12:32:20 -04:00
Seebs
cecaf99ee4 testhook: leak auditing infrastructure
The testhook/ package provides an easy way to set up multiple
hooks to run before/after tests are run.

The audit hooks track open and closes of storage backends,
files, indexes, and holders, for example. A tempdir wrapper
creates temporary directories which are automatically cleaned up
when the test ends. Any kind of resource creation that
should be closed at test conclusion can be tracked. We
will complain at the end of the TestMain if resources are
leaking.

Leaks under go1.13:

We use a wrapper function which is a no-op for go 1.13, but actually
calls testing.TB.Cleanup in go1.14, so we can still build with 1.13 even though
tests will leak files all over the place there. Because of this,
don't run the testhook tests when using 1.13, as they'll always fail.

- the test/pilosa.go http client now times out after 10 seconds
to help diagnose hung server situations.

- Makefile targets added to get better progress reports.
2020-08-24 11:26:39 -05:00
jaten-molecula
7e326fb833
Merge pull request #722 from tgruben/rbf-add
copied optimized add/remove to rbf
2020-08-24 11:42:57 -04:00
Todd Gruben
edf6608129 copied optimized add/remove to rbf
use large test container for CI
2020-08-24 10:37:25 -05:00
Kuba Podgórski
397d37a129 Fix https://github.com/molecula/pilosa/issues/706 2020-08-24 15:34:09 +02:00
Kuba Podgórski
bc3f329e8f
Merge pull request #720 from kuba--/sql-drop
Add support for SQL 'drop table...'
2020-08-24 14:52:49 +02:00
Kuba Podgórski
ceda4ab61b Add support for drop table 2020-08-24 13:24:18 +02:00
tgruben
9caa670c90
Merge pull request #719 from tgruben/lmdb-add
copied badger addRemove implementation to lmdb
2020-08-23 08:50:07 -05:00
tgruben
9e94c86932
Merge branch 'master' into lmdb-add 2020-08-23 08:40:16 -05:00
Todd Gruben
4dd4b441c4 copied badger addRemove implementation to lmdb 2020-08-23 08:39:03 -05:00
jaten-molecula
4a6d8bc5df
Merge branch 'master' into fix-rbf-race 2020-08-22 23:23:57 -04:00
Nia
0ce9062588
Merge pull request #716 from kuba--/sql-show
Add support for SHOW queries
2020-08-21 19:46:47 -04:00
Kuba Podgórski
ad9e3338b5 Add support for SHOW queries 2020-08-22 01:25:35 +02:00
Nia
c137efbe44
Merge pull request #707 from niaow/pg-sql
Add SQL to postgres endpoint
2020-08-21 19:13:26 -04:00
Kuba Podgórski
cb89a4a831
Merge branch 'master' into pg-sql 2020-08-22 01:01:22 +02:00
Kuba Podgórski
37a4cda454
Merge pull request #563 from kuba--/fix-210/translate-entry
Fix 210/translate entry
2020-08-22 00:52:53 +02:00
Kuba Podgórski
7c13fa71d7
Merge branch 'master' into fix-210/translate-entry 2020-08-22 00:18:34 +02:00
Kuba Podgórski
6d0baa82b3
Update http/translator_test.go
Co-authored-by: Travis Turner <travis@pilosa.com>
2020-08-22 00:18:25 +02:00
Kuba Podgórski
cc66775ea4
Update http/translator_test.go
Co-authored-by: Travis Turner <travis@pilosa.com>
2020-08-22 00:18:19 +02:00
Nia
842377ba17
Merge pull request #713 from niaow/dont-log-100000-times
Stop logging in TestStartupInvalidLength
2020-08-21 17:29:52 -04:00
Nia Weiss
dd73bf3960
stop logging in TestStartupInvalidLength 2020-08-21 17:20:10 -04:00
jaten-molecula
8d397babdf
Merge pull request #710 from molecula/clear_importvals
ImportRequest.Clear and ImportValuesRequest.Clear respected by api.Import() and api.ImportValues()
2020-08-21 15:42:24 -05:00
tgruben
056d5990da
Merge branch 'master' into clear_importvals 2020-08-21 15:33:04 -05:00
Jason Aten
40a9d01f46 ImportRequest.Clear and ImportValuesRequest.Clear respected by api.Import() and api.ImportValues()
- tested in TestAPI_ClearFlagForImportAndImportValues api_test.go
2020-08-21 15:17:44 -05:00
Nia Weiss
49c3bd9701
add SQL to postgres endpoint 2020-08-21 14:47:59 -04:00
Ben Johnson
91c8c5b5e9
Merge branch 'master' into fix-rbf-race 2020-08-21 10:33:43 -06:00
Kuba Podgórski
a2f24bc115
Merge pull request #663 from kuba--/sql-mapper
SQL mapper
2020-08-21 17:24:59 +02:00
Ben Johnson
162bd0303a Remove duplicate checkpoint() invocation 2020-08-21 08:25:04 -06:00
Kuba Podgórski
17fa1e578b Add benchmark for translation reader 2020-08-21 15:15:41 +02:00
Kuba Podgórski
bbbeb22d31
Merge branch 'master' into sql-mapper 2020-08-21 03:17:46 +02:00
tgruben
57babb3269
Merge pull request #700 from molecula/atomicrecord
AtomicRecord allows the client to request atomic updates.
2020-08-20 17:42:15 -05:00
Jason Aten
df5dd1557e AtomicRecord allows the client to request atomic updates.
- Atomic record contains multiple ImportRequest and
   ImportValueRequest, plus ability to Clear individual requests.
 - adds http handlers for importing AtomicRecord.
2020-08-20 17:33:52 -05:00
Kuba Podgórski
0e0a405184
Merge branch 'master' into sql-mapper 2020-08-20 21:53:59 +02:00
Nia
272f6708a3
Merge pull request #679 from niaow/pg
Add a postgres endpoint to pilosa
2020-08-20 14:24:56 -04:00
Kuba Podgórski
6fa69fb81a
Merge branch 'master' into sql-mapper 2020-08-20 20:17:53 +02:00
Nia Weiss
14676c0713
remove postgres debugging types 2020-08-20 14:17:50 -04:00
Nia
44061923c7
Update pg/message/io.go
Co-authored-by: Travis Turner <travis@pilosa.com>
2020-08-20 14:14:14 -04:00
Nia Weiss
128e02046a
add a postgres endpoint to pilosa 2020-08-20 11:29:16 -04:00
alanbernstein
68b17312e0
Merge pull request #687 from alanbernstein/unicode
Use _buffer instead of buffer
2020-08-19 17:12:26 -05:00
Alan Bernstein
6fc5465aaa Use rune slice in all cases, add tests 2020-08-19 16:32:27 -05:00
Alan Bernstein
0a96043c23 Use _buffer instead of buffer 2020-08-19 16:32:27 -05:00
Kuba Podgórski
0be436e18d
Merge branch 'master' into sql-mapper 2020-08-19 21:33:58 +02:00
Ben Johnson
0a37f396f8
Merge pull request #671 from molecula/wal-write-cache
Add RBF WAL write cache
2020-08-19 09:54:55 -06:00
Ben Johnson
9dbb82cf3e WAL mutex fixes 2020-08-19 08:42:53 -06:00
Ben Johnson
51504f4fe4 Add WAL write cache mutex; update name; add benchmarks 2020-08-19 08:33:19 -06:00
Ben Johnson
5849794a1b Add RBF WAL write cache 2020-08-19 08:33:19 -06:00
Cody Soyland
776de471db
Merge pull request #693 from molecula/ubuntu_dockerfile
ubuntu 20:10 image instead of alpine, for cgo support
2020-08-18 20:40:26 -05:00
Jason Aten
ef156f6172 ubuntu 20:10 image instead of alpine, for cgo support 2020-08-18 16:55:44 -05:00
Kuba Podgórski
2b34976c22 porting sqlmapper from vdsm 2020-08-18 15:53:26 +02:00
Cody Soyland
039780a85e
Merge pull request #685 from molecula/discard-test-386
no more test-386
2020-08-17 20:15:14 -05:00
Jason Aten
aea7d4a812 no more test-386
- lmdb won't build under 386
 - still have to keep lmdb_other.go for arm/arm64
2020-08-17 19:54:50 -05:00
jaten-molecula
ba1d5e9f9f
Merge pull request #681 from molecula/fix-rbf-reopen
Fix RBF checkpoint on reopen. Fixes #673
2020-08-17 19:16:17 -05:00
jaten-molecula
3e4da916e8
Merge branch 'master' into fix-rbf-reopen 2020-08-17 19:09:55 -05:00
tgruben
d84ae88944
Merge pull request #677 from molecula/lmdb_as_backend
add lmdb, Tx call stats, and prep for db/shard.
2020-08-17 18:43:57 -05:00
Jason Aten
123ce41840 add lmdb, Tx call stats, and prep for db/shard.
- lmdb as a backend (lmdb.go)
   (lmdb is the fastest known transactional storage backend)
 - per Tx call statics report enabled with PILOSA_CALLSTAT=true (stattx.go)
 - framework for per-shard db (dbshard.go)
 - txfactory handles any pair under blue-green testing (txfactory.go)
 - enable CGO in Dockerfiles for lmdb
2020-08-17 18:26:58 -05:00
seebs
4f7e4feaf8
Merge pull request #680 from seebs/cachefix
when updating a container, drop the single-container cache
2020-08-17 15:03:10 -05:00
Ben Johnson
b1e806a518 Fix RBF checkpoint on reopen.
Previously, the `rbf.DB.opened` flag was set after `checkpoint()`
when reopening, however, this flag is checked by `checkpoint()` so
it was not properly executing.
2020-08-17 13:28:28 -06:00
Seebs
16ba54293a when updating a container, drop the single-container cache
This can cause incredibly weird and hard-to-debug problems if the previous
container value is still in the cache after an update, and in particular,
can result in having a stale container value cached after a roaring import
that modified the container. Coupled with another bug which could corrupt
containers on a delete, this produces a very strange bug where a value is
present in a fragment, but an attempt to delete it reports failure.
2020-08-17 11:23:47 -05:00
Travis Turner
c5e9b8d917
Merge pull request #674 from travisturner/container-corrupt
stop setting N on a frozen container
2020-08-16 15:59:58 -05:00
Travis
ff5925aa8b
stop setting N on a frozen container 2020-08-14 17:25:14 -05:00
Matthew Jaffee
cd83f7965c
Merge pull request #672 from molecula/remove-transaction.md
remove old transactions doc
2020-08-14 14:40:10 -05:00
Matt Jaffee
b11bdeab87
remove old transactions doc 2020-08-14 11:57:28 -05:00
jaten-molecula
05ca83c2f5
Merge pull request #669 from molecula/autocommit2
build-tag out lmdb-go from builds; lower autocommit limits to avoid ErrTxnTooBig; improve txn size estimation
2020-08-13 11:52:42 -05:00
Jason Aten
c90c4c275d lower autocommit limits to avoid ErrTxnTooBig; improve txn size estimation 2020-08-13 16:42:47 +00:00
Travis Turner
1f53e86e47
Merge pull request #662 from travisturner/transaction-list
transactions as a list endpoint; added Transaction.CreatedAt
2020-08-13 08:40:57 -05:00
Travis
1837811ce1
transactions as a list endpoint; added Transaction.CreatedAt 2020-08-12 23:11:54 -05:00
tgruben
0dea018fb7
Merge pull request #665 from molecula/autocommit
tested working autocommit approach
2020-08-12 21:56:03 -05:00
Jason Aten
5e49e10cda tested working autocommit approach 2020-08-13 01:12:14 +00:00
jaten-molecula
c7d6229380
Merge pull request #664 from molecula/bigtx
PutContainer() handles large txn with autocommit
2020-08-12 19:18:19 -05:00
Jason Aten
6dc9727796 PutContainer() handles large txn with autocommit 2020-08-12 23:15:23 +00:00
jaten-molecula
973e9a7955
Merge pull request #659 from molecula/rbf_99pct
all test green on rbf. WOOT.
2020-08-12 16:13:47 -05:00
Todd Gruben
547ee14f5b all test green on rbf. WOOT.
- rbf had races around the new rootRecords cache in tx
 - rbf tx needed a write lock on the db now that rootRecords are written
 - added a global registry for rbfDB to correctly dedup instances
 - implement DeleteFragment, DeleteIndex for rbf
 - use badger style keys for rbf to allow content checksumming to be list
    containers in the same order
 - lots of other integration of rbf into pilosa layer.
2020-08-12 21:08:30 +00:00
Cody Soyland
123e0b6376
Merge pull request #644 from codysoyland/grpc-crd-sql
Add gRPC methods to support the functionality of VDSM
2020-08-12 14:55:24 -05:00
Cody Soyland
21456827ae
Merge branch 'master' into grpc-crd-sql 2020-08-12 14:21:53 -05:00
Cody Soyland
3361033c5b Add PostVDS and DeleteVDS 2020-08-12 14:21:22 -05:00
Cody Soyland
3771bd43b5 Add note about gRPC unary methods and futures 2020-08-12 14:08:30 -05:00
Nia
50a2e3c5fa
Merge pull request #650 from jaddr2line/limit
Add a "Limit" query
2020-08-12 14:04:12 -04:00
Nia
911c039991
Fix incorrect negative in Limit query documentation
Co-authored-by: Travis Turner <travis@pilosa.com>
2020-08-12 13:53:57 -04:00
Jaden Weiss
40a6fbd79e
add a "Limit" query 2020-08-12 12:27:23 -04:00
Nia
2a1fafb227
Merge pull request #652 from jaddr2line/constrow
Add ConstRow query
2020-08-12 12:22:06 -04:00
Nia
a19ca16810
Merge branch 'master' into constrow 2020-08-12 11:47:46 -04:00
Travis Turner
683b8dfe31
Merge pull request #660 from travisturner/error-rows-int
error on Rows(field=<int>)
2020-08-11 19:44:46 -05:00
Travis
eae038a07e
error on Rows(field=<int>) 2020-08-11 18:07:03 -05:00
Jaden Weiss
0e16192aeb
add ConstRow query 2020-08-11 08:25:32 -04:00
Jaden Weiss
96c2364c1b
Merge pull request #642 from jaddr2line/query-extract
Add an "Extract" query
2020-08-10 19:32:36 -04:00
Jaden Weiss
590bd07995
add an "Extract" query 2020-08-10 09:33:55 -04:00
Cody Soyland
559a2c6864 Add GetVDS and GetVDSs gRPC implementations 2020-08-07 16:28:06 -05:00
Ben Johnson
2bc3442ae8
Merge pull request #649 from molecula/fix-rbf-rollback
Fix RBF checkpoint high water mark
2020-08-07 14:29:31 -06:00
Ben Johnson
faa1662bf6 Fix RBF checkpoint high water mark
Previously, the `checkpoint()` function determined the segments to drop
based on the current active transactions' WAL ID references. However, if
no transactions are active then the checkpoint would drop segments too
aggressively.

This changes the determination by using the highest WAL ID that is
actually checkpointed to disk to determine the high water mark. If no
page are checkpointed then no segments can be dropped.
2020-08-07 10:07:33 -06:00
Ben Johnson
5644867507
Merge pull request #643 from molecula/fix-rbf-hanging-tests
Fix RBF/pilosa hanging tests
2020-08-07 10:06:57 -06:00
Cody Soyland
35675a3e59 Add stubbed gRPC types to support python-molecula
This adds the following rpc calls: GetVDS, GetVDSs, PostVDS, DeleteVDS, QuerySQL, QuerySQLUnary

Currently, they are not implemented.
2020-08-06 14:19:16 -05:00
Ben Johnson
78f9fbe332 Fix RBF/pilosa hanging tests 2020-08-06 11:43:41 -06:00
alanbernstein
5306426b19
Merge pull request #641 from alanbernstein/more-docs-updates
Fix various docs issues
2020-08-05 12:34:57 -05:00
Alan Bernstein
5f26290f92 Fix various docs issues 2020-08-05 12:26:22 -05:00
Ben Johnson
3fb2cccb00
Merge pull request #637 from molecula/rbf-fixes
Multiple RBF test fixes
2020-08-05 09:02:06 -06:00
Ben Johnson
f8cacd8081 Multiple RBF test fixes 2020-08-05 08:20:34 -06:00
Travis Turner
df709af10e
Merge pull request #638 from travisturner/fix-executeclearrow
safe cast of bool in executeClearRow
2020-08-04 15:52:48 -05:00
Travis
ddee2cb0a8
safe cast of bool in executeClearRow 2020-08-04 15:16:23 -05:00
tgruben
7f6c6956d8
Merge pull request #635 from molecula/efence_off
DetectMemAccessPastTx flag added, default false.
2020-08-03 22:03:36 -05:00
tgruben
86b5d89bc9
Merge branch 'master' into efence_off 2020-08-03 21:24:23 -05:00
alanbernstein
dede69f04d
Merge pull request #590 from alanbernstein/improve-tx-error-messages
Improve 'shouldn't ever happen' error messages
2020-08-03 12:44:30 -05:00
alanbernstein
27edff60b0
Merge branch 'master' into improve-tx-error-messages 2020-08-03 12:34:22 -05:00
Jason Aten
b806322c5a DetectMemAccessPastTx flag added, default false. Allow badger to run at full speed rather than with debugging code on by default 2020-08-01 21:40:54 -04:00
tgruben
0820babc44
Merge pull request #634 from molecula/unionfix
Follow roaring.Union() with optimize() to avoid overly large containers.
2020-08-01 08:58:47 -05:00
Jason Aten
394b8522d1 Follow roaring.Union() with optimize() to avoid overly large containers.
The cmd/loader is a preliminary sketch of the load testing tool.
2020-07-31 20:10:43 -04:00
tgruben
d0c2b80021
Merge pull request #631 from molecula/rbf_thurs
rbf: OffsetRange, ImportRoaringBits, CountRange work
2020-07-30 19:30:04 -05:00
Jason Aten
a3d802f8a3 rbf: OffsetRange, ImportRoaringBits, CountRange work
green:
TestFragment_RowsIteration/combinations
TestFragment_RoaringImportTopN

red: (needs Ben's attention)
PILOSA_TXSRC=rbf go test -v -run TestFragment_TopN_IDs  -tags=' shardwidth20'  "-gcflags=all=-d=checkptr=0"

also red: (one for Ben)
TestCursor_FirstNext_Quick/9 is throwing
  panic: cannot find segment containing WAL page: 1
  as we check the error back from checkpoint() in Rollback().
2020-07-30 20:20:38 -04:00
jaten-molecula
2fb76ba919
Merge pull request #626 from molecula/rbf_dump
rbf Dump() and DumpString() debug methods.
2020-07-30 13:57:30 -04:00
Jason Aten
8903d8c117 rbf Dump() and DumpString() debug methods. 2020-07-30 13:50:36 -04:00
jaten-molecula
d2586210a2
Merge pull request #621 from molecula/bluegreen_atg
blueGreenTx roaring vs badger is all tests green (atg)
2020-07-30 12:04:11 -04:00
Jason Aten
72c893a3d1 blueGreenTx roaring vs badger is all tests green (atg).
back to github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200709123515-8e896a7af361  b/c github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200718033852-37ee16d8ad1c had issues with CI on 386 and arm
2020-07-30 11:50:25 -04:00
Cody Soyland
5c5cd1e1fd
Merge pull request #620 from codysoyland/go-mod-tidy
Add CI job to ensure go mod files are tidy
2020-07-29 18:18:37 -05:00
Cody Soyland
55313bd69d Add CI job to ensure go mod files are tidy. 2020-07-29 15:28:58 -05:00
Ben Johnson
55c0785215
Merge pull request #597 from molecula/rbf-tx
Implement pilosa.Tx for RBF
2020-07-29 11:42:20 -06:00
Ben Johnson
64de208170 Implement pilosa.Tx for RBF 2020-07-29 11:25:41 -06:00
jaten-molecula
4cdf62ab89
Merge pull request #615 from molecula/parallelized_open_frag
Parallelize view.OpenFragmentsInTx
2020-07-28 11:18:13 -04:00
Jason E. Aten
71eccd121d fix race in view.openFragmentsInTx 2020-07-28 07:55:28 -04:00
Jason Aten
38eea9b4a7 reparallelize view.go openFragmentsInTx() 2020-07-28 07:55:28 -04:00
jaten-molecula
1a89fc27a1
Merge pull request #605 from molecula/badger_atg
Tx integration milestone
2020-07-27 20:44:12 -04:00
Jason Aten
ac7be132ef Tx integration milestone
a) All tests green under -race for both PILOSA_TXSRC=roaring and PILOSA_TXSRC=badger.

b) Distinct is merged back into mainline pilosa.

Seebs notes on the Distinct work:

merge Distinct plugin back into main source tree, convert to Tx

We drop all references to the Preemptively Deprecated Don't You Dare
Use This extension interface, and move the one and only extension we had
(Distinct) into the main executor.

Also this fixes an arguable bug, which is that Container.AsBitmap()
would panic on a nil parameter, but it should have returned an empty
bitmap, because a nil *Ccontainer is a valid empty container. This
simplifies logic significantly in Distinct.

Fixes #569 #570 #571 #572 #573 #584 #585
2020-07-27 19:29:46 -04:00
Ben Johnson
efc90a6d36
Merge pull request #596 from molecula/rm-rbf-fun-dot
Remove generation of rbf/fun.dot in tests
2020-07-23 10:12:17 -06:00
Ben Johnson
75930ed82f Remove generation of rbf/fun.dot in tests 2020-07-23 09:37:50 -06:00
alanbernstein
7c94828a3c
Merge pull request #589 from alanbernstein/broken-index-translation
Fix broken index key translation in state DEGRADED
2020-07-22 21:22:06 -05:00
Alan Bernstein
5ffc7d7b59 Move checkClusterStatus to test package 2020-07-22 19:54:44 -05:00
Travis
339b76a091 use c.Topology, when available, to determine partitionNodes 2020-07-22 16:41:53 -05:00
Alan Bernstein
a727c74d35 Use nodes from topology to calculate partitionNodes 2020-07-22 16:41:53 -05:00
Alan Bernstein
6c8e2e9450 Add test 2020-07-22 16:41:53 -05:00
Alan Bernstein
fa77a36e83 Improve 'shouldn't ever happen' error messages 2020-07-22 16:36:02 -05:00
Jaden Weiss
fd65384faa
Merge pull request #583 from jaddr2line/bsi-test
Test every possible BSI comparison up to 6 bits
2020-07-21 15:58:31 -04:00
Jaden Weiss
9072b4c290
test every possible BSI comparison up to 6 bits 2020-07-21 13:56:36 -04:00
seebs
f5a228e56d
Merge pull request #579 from seebs/shutdownresize
Handle cluster shutdown during a resize
2020-07-21 12:45:18 -05:00
seebs
aae1d25152
Merge branch 'master' into shutdownresize 2020-07-21 12:27:33 -05:00
Jaden Weiss
adde0fa0bc
Merge pull request #564 from jaddr2line/fix-between-common-bits
Fix BSI range queries with nonzero common upper bits and oversized BSI queries
2020-07-21 13:24:00 -04:00
Jaden Weiss
9fb5f8b349
Merge branch 'master' into fix-between-common-bits 2020-07-21 13:19:15 -04:00
Seebs
99420b564e Handle cluster shutdown during a resize
The new logic to send resize instructions more makes it easier
to hit this, but it's probably always been a theoretically possible
bug to hit: If you are shutting a cluster down, then you stop accepting
connections, which means that if you have an existing resize job, you
can't get responses for it. Which means that the other nodes will
fail to notify you of the success or failure of resize instructions,
so the code waiting on the resize job's status waits forever.

When closing, we bail immediately on that; we don't need to wait for
those notifications. We still have a buffer, and a reasonable confidence
that we'll never write more than one result status, so if one of them
*does* somehow show up and cause the job to have a status,
writing the status won't block.
2020-07-21 12:16:38 -05:00
jaten-molecula
4c0d66703e
Merge pull request #582 from molecula/fix575b
remove premature configuration of Txsrc in test config
2020-07-20 21:04:46 -04:00
Jaden Weiss
e6b4cc2f32
fix oversized rangeEQ 2020-07-20 20:43:50 -04:00
Jaden Weiss
b9b0dd293f
fix rangeBetween when there are nonzero common upper bits and oversized rangeGT 2020-07-20 20:38:32 -04:00
Jason Aten
f5688fa700 remove premature configuration of Txsrc in test config 2020-07-20 20:22:47 -04:00
jaten-molecula
d23ea94ec0
Merge pull request #581 from molecula/fix575
pilosa server --tx compatible with PILOSA_TXSRC. fixes #575
2020-07-20 18:48:40 -04:00
Jason Aten
7fe7ed907f env PILOSA_TXSRC is reinjected into the env if pilosa server --tx overrides it. fixes #575
1. Tests can choose the Tx engine desired by setting the PILOSA_TXSRC
      env variable. For example:

         PILOSA_TXSRC=badger go test -v -run TestImportClearRestart

   2. pilosa server --tx is enabled now.

     Examples:
      pilosa server --tx roaring    # gives the legacy approach.
      pilosa server --tx rbf        # will activate RBF
      pilosa server --tx badger     # will activate BadgerDB
      pilosa server --tx badger_rbf # will run Blue-Green badger to RBF comparisons.

      and so forth. See pilosa server -h or txfactory.go for all valid --tx choices.

   3. Mechanism that makes both tests(1) and pilosa server(2) work at once:
       pilosa/server/server.go injects PILOSA_TXSRC into env to
       communicate with NewIndex in pilosa/index.go.
2020-07-20 17:56:12 -04:00
jaten-molecula
22fd24566a
Merge pull request #580 from molecula/fix568
green TestImportClearRestart on PILOSA_TXSRC=badger. fixes #568
2020-07-20 17:24:29 -04:00
Jason Aten
8a1dabb3ba 60m timeout on race 2020-07-20 17:09:32 -04:00
Jason Aten
f59f8a369e green TestImportClearRestart on PILOSA_TXSRC=badger. fixes #568 2020-07-20 16:32:34 -04:00
jaten-molecula
1c9ef3d321
Merge pull request #565 from molecula/tx_roaring_badger
integration of Tx, RoaringTx and BadgerTx implementations.
2020-07-20 16:01:45 -04:00
Jason Aten
97b530ca78 integration of Tx, RoaringTx and BadgerTx implementations.
- all tests green on RoaringTx
  - RoaringTx on by default
  - blueGreenTx testing framework available for A-vs-B comparison
    of Tx implementations
  - flag -tx added to server command line but not wired to
    change NewIndex() selection yet.
  - 918 green tests, 14 tests red on BadgerTx.

    A full list of the 14 red tests on BadgerTx follows.
    Note that these red tests represent not defects in BadgerDB
    or BadgerTx but rather failures of the pre-existing pilosa infrastructure to yet
    be fully adapted from files to using a transactional storage engine.

    As such these are tests that RBF should not be expected to
    pass yet either.

    Fixing the pilosa infrastructure to allow these tests
    to go green under Badger is the next and highest priority
    order of business, but RBF can get much testing benefit
    from the 918 green tests we do have, and hence we merge
    as much as we have today.

    The 14 red tests when NewIndex() is set to use
    BadgerTx are as follows. Note in particular
    that pilosa cluster resizing is not working yet under a
    transactional store.

     TestCluster_ResizeStates/Multiple_nodes,_with_data
     TestImportClearRestart/0MaxOpN10000
     TestImportClearRestart/1MaxOpN10000
     TestImportClearRestart/2MaxOpN10000
     TestImportClearRestart/3MaxOpN10000
     TestExecutor_Execute_Existence/Row
     TestExecutor_ForeignIndex
     TestExecutor_Execute_CountDistinct/Distinct
     TestExecutor_Execute_CountDistinct/Count(Distinct)
     TestExecutor_Execute_CountDistinct/GroupBy(Distinct)
     TestExecutor_BareDistinct
     TestExecutor_Execute_TopNDistinct/TopN
     TestHolderSyncer_IntField/BasicSync
     TestHolderSyncer_IntField/MultiShard
2020-07-20 15:50:08 -04:00
seebs
ab6a3aff10
Merge pull request #458 from seebs/eaddrinuse
Fix very-sporadic EADDRINUSE failures in CI testing (and related cluster test issues)
2020-07-20 10:58:48 -05:00
Seebs
364b533ead various cluster test fixups/cleanups
Some cluster tests failed sporadically. In order to fix them, I
introduced some debugging-related functionality, which revealed
several new bugs that were actually existing bugs we just happened
not to hit in testing. This combines various fixes.

We start with "make the nodes used in testing have distinct names
based on the test case name", which lets us discover that we are
leaking clusters, which continue to sit around talking with each
other. That in turn causes significantly higher load on access to
ephemeral ports, which causes sporadic failures when we shut a
node down and try to restart it, but something else has gotten assigned
its ephemeral port number since then.

Part of the fix is to try to rebind on port 0 if an attempt to
bind to a specified port over 32k fails. This is a guess; the
actual ephemeral port range could be 16k+, 32k+, or 48k+, or just
about anything else really, but it seems reasonable in
practice.

There were bugs in the oft-repeated loops to await the cluster
achieving a given state, and it could hang forever if it didn't,
so we add a timeout and a standard function on the test.Cluster
type to handle that. Note that the timeout seems irrelevant; in
every case I've tried, a timeout of 0 is fine because the node
start doesn't complete until the cluster state has changed.

Add a method to test.Command to run a query, expecting a specific
result. Also clean up some of the formatting and generation of
queries, and allow parameterized (badly) queries. This lets us fix
a subtle bug, which is that test cases were depending on assumptions
about shardwidths. Also improve the diagnostic output from some of
these functions so test failures are more comprehensible.

But actually that dependency on shardwidths was ALSO revealing a
genuine underlying bug, which is that a node resize did not correctly
propagate the schema to a new node if there was no data present
on shards that node would own. We now also have a test case that
hits that (or would, if we hadn't fixed it).

Add comments explaining the server options parameters for MustNewCluster
and MustRunCluster.

Also, we implement the ReadFrom and WriteTo behaviors for
InMemTranslateStore, without which some of the cluster resize tests
fail. Props to the comment for specifically stating that they wouldn't
work if that happened, which probably saved me several hours of
debugging. The implementations may not be robust, but
InMemTranslateStore is intended to be used only in lightweight
and transient testing.
2020-07-20 10:45:59 -05:00
Seebs
ef8b054367 move Cluster type and methods into existing almost-empty cluster.go 2020-07-20 10:45:47 -05:00
tgruben
1067d29784
Merge pull request #547 from tgruben/refactor-putleaf
refactor putleaf
2020-07-17 13:39:35 -05:00
Todd Gruben
cd4bd016f9 skipping a test for ci issue 2020-07-17 09:30:07 -05:00
Todd Gruben
848359a36b skipped test for race wip 2020-07-17 09:15:20 -05:00
Todd Gruben
e61f0f6b1f missing liscense 2020-07-17 09:09:42 -05:00
tgruben
88bf29ea40
Merge branch 'master' into refactor-putleaf 2020-07-17 09:06:31 -05:00
Todd Gruben
9bbceb931a missing license 2020-07-17 09:05:53 -05:00
Todd Gruben
7c5c693fcb linter fix 2020-07-17 09:03:32 -05:00
Todd Gruben
59d2d89a5c removed leafArg and added conatinertypebitmapptr; lint fixes 2020-07-17 02:16:35 -05:00
Jaden Weiss
3f45a90582
Merge pull request #549 from jaddr2line/microseconds
Add microseconds to log
2020-07-16 17:13:10 -04:00
Jaden Weiss
de061ff21d
Merge branch 'master' into microseconds 2020-07-16 17:01:50 -04:00
Todd Gruben
bbd83e4618 . 2020-07-16 08:31:09 -05:00
Kuba Podgórski
2ee5e3d8e5
Merge pull request #538 from kuba--/translation-coordinator
Translation coordinator
2020-07-15 22:53:18 +02:00
Kuba Podgórski
ce628c96cd
Merge branch 'master' into translation-coordinator 2020-07-15 22:45:43 +02:00
Kuba Podgórski
1d6a4dea88
Merge pull request #554 from kuba--/i0-i1
Remove test leftovers
2020-07-15 14:06:23 +02:00
Kuba Podgórski
559e71f62a Remove test leftovers (holder's path). 2020-07-15 13:50:59 +02:00
Jaden Weiss
5579d632c7
Merge pull request #539 from jaddr2line/remove-arm64-binary
remove an arm64 pilosa binary that was committed for some reason
2020-07-14 20:08:23 -04:00
Kuba Podgórski
1edd773e90
Merge branch 'master' into remove-arm64-binary 2020-07-15 01:42:26 +02:00
Jaden Weiss
c99d35a793
Merge pull request #552 from jaddr2line/queries-docs
Add /queries endpoint to API reference
2020-07-14 18:26:27 -04:00
Jaden Weiss
b442ff8336
add /queries endpoint to API reference 2020-07-14 15:58:59 -04:00
Jaden Weiss
9036a16b08
add microseconds to log 2020-07-14 11:44:55 -04:00
Todd Gruben
e68f0a0d8f missing license 2020-07-14 09:00:09 -05:00
Todd Gruben
b61bdbd7a0 fix linter errors 2020-07-14 08:54:30 -05:00
Todd Gruben
374a4ec9ce fixed missing refactor test;refactor GetBitmap 2020-07-14 07:57:02 -05:00
Todd Gruben
b8fe08c765 simplify Walker interface 2020-07-13 20:34:33 -05:00
Todd Gruben
d6986b78a5 . 2020-07-13 20:08:22 -05:00
Todd Gruben
b2b614a686 refactor cursor.GetBitmap->tx.GetBitmap;rename to WalkRootRecordPages;err check 2020-07-13 19:32:26 -05:00
Todd Gruben
b11b43af43 refactor putleaf 2020-07-13 10:48:25 -05:00
Travis Turner
3c619fa2ef
Merge pull request #543 from travisturner/foreign-index-todos
Get ForeignIndex keys in GroupBy
2020-07-11 19:45:31 -05:00
Travis Turner
9f80619f19
Merge branch 'master' into foreign-index-todos 2020-07-11 18:24:55 -05:00
Kuba Podgórski
92ab76b621
Merge pull request #399 from molecula/alisharawal-patch-1
Update README.md
2020-07-11 21:28:14 +02:00
Kuba Podgórski
51ba2492e9
Merge branch 'master' into translation-coordinator 2020-07-11 19:22:48 +02:00
Kuba Podgórski
51891040d0
Merge branch 'master' into alisharawal-patch-1 2020-07-11 19:17:30 +02:00
Travis
a9dc8b8add
translate GroupBy previous value from foreign index 2020-07-11 10:18:45 -05:00
Travis
8f6b876af0
get ForeignIndex keys in GroupBy 2020-07-10 22:48:35 -05:00
alanbernstein
07d3cfe23b
Merge pull request #542 from alanbernstein/validate-transaction-id
Restrict allowed characters in transaction IDs
2020-07-10 20:18:42 -05:00
Kuba Podgórski
0fc7b89b48
Merge branch 'master' into remove-arm64-binary 2020-07-11 01:59:35 +02:00
Kuba Podgórski
010ab5ef8b Extend translate coordinator test 2020-07-11 01:51:03 +02:00
Alan Bernstein
65febc37fd Allow '' in ID regex 2020-07-10 18:07:17 -05:00
Alan Bernstein
7c37ececfd Restrict allowed characters in transaction IDs 2020-07-10 17:58:47 -05:00
Jaden Weiss
a947bc8ad9
Merge pull request #540 from jaddr2line/fix-inspect-query-validate
Fix incorrect validation of query specification in Inspect
2020-07-10 15:56:07 -04:00
Jaden Weiss
2f8d8345c1
fix incorrect validation of query specification in Inspect 2020-07-10 14:35:30 -04:00
Jaden Weiss
8940402e76
remove an arm64 pilosa binary that was committed for some reason 2020-07-10 13:28:27 -04:00
Travis Turner
c7ccd62b3b
Merge pull request #537 from travisturner/grpc-port-0
Replace grpc port code that I removed for some reason
2020-07-10 11:06:40 -05:00
Travis
85bd306eb5
replace grpc port code that i removed for some reason 2020-07-10 10:57:20 -05:00
Jaden Weiss
bb9b3d4e6f
Merge pull request #527 from jaddr2line/like
Add `Rows(like=...)` and `UnionRows` queries
2020-07-09 19:10:29 -04:00
Jaden Weiss
239aacc160
Merge branch 'master' into like 2020-07-09 19:04:03 -04:00
Jaden Weiss
62ce26e7b8
Merge pull request #534 from jaddr2line/inspect-query-v2
Add support for inspecting with a query
2020-07-09 14:09:47 -04:00
Jaden Weiss
f4e9e1688f
add support for inspecting with a query 2020-07-09 13:29:15 -04:00
Travis Turner
85d1fa30dc
Merge pull request #530 from travisturner/advertise-grpc
add --advertise-grpc configuration option
2020-07-09 11:38:18 -05:00
Travis
944a6dca78
add --advertise-grpc configuration option 2020-07-09 11:17:12 -05:00
Ben Johnson
3c021fc2e3
Merge pull request #526 from molecula/rbf
Roaring Bitmap Format
2020-07-08 13:39:37 -06:00
Ben Johnson
de51b538f3 rbf: roaring bitmap format
Co-authored-by: Todd Gruben <todd@pilosa.com>
2020-07-08 13:27:18 -06:00
Jaden Weiss
4bacab6524
update query language docs to include like and UnionRows 2020-07-08 13:08:11 -04:00
Jaden Weiss
c9edccb267
add executor tests and license headers for like & UnionRows 2020-07-08 12:55:18 -04:00
Jaden Weiss
7a16ffff68
add UnionRows query 2020-07-08 12:55:18 -04:00
Jaden Weiss
227881cb67
simplify suffix matching 2020-07-08 12:55:17 -04:00
Jaden Weiss
0611ef3418
initial implementation of Rows like 2020-07-08 12:55:17 -04:00
Jaden Weiss
ed8e5a933e
optimize & document & test like matcher
optimize suffix matching
add some descriptive comments to the like matcher
test all paths in the like tokenizer and matcher
2020-07-08 12:55:17 -04:00
Jaden Weiss
9eb8fcb37f
initial impl of like 2020-07-08 12:55:17 -04:00
alanbernstein
7f1b1f63ef
Merge pull request #506 from alanbernstein/docs-updates
Docs updates
2020-07-07 15:27:07 -05:00
alanbernstein
0aae064310
Merge branch 'master' into docs-updates 2020-07-07 15:17:42 -05:00
Alan Bernstein
94038f4446 Update wording 2020-07-07 03:48:37 -05:00
Ben Johnson
d53d8ef010
Merge pull request #508 from molecula/tx
Tx Interface
2020-07-02 17:20:23 -06:00
Ben Johnson
bf55bbc717 Tx Interface
This commit adds a transaction interface which will be used in the
future to add support to RBF (Roaring B-tree Format).
2020-07-02 10:43:15 -06:00
Kuba Podgórski
549c98dc64
Merge pull request #447 from kuba--/union-run-run
Add unionRunRunInPlace
2020-07-02 11:05:27 +02:00
Kuba Podgórski
751383ecb1
Merge branch 'master' into union-run-run 2020-07-02 10:52:59 +02:00
Jaden Weiss
a52c5d803d
Merge pull request #510 from jaddr2line/roaring-cleanup-3
Roaring cleanup
2020-07-01 17:21:14 -04:00
Jaden Weiss
6d692487ed
Merge branch 'master' into roaring-cleanup-3 2020-07-01 17:15:10 -04:00
Kuba Podgórski
ec73bf5906
Merge branch 'master' into union-run-run 2020-07-01 22:25:06 +02:00
Kuba Podgórski
e777a28283
Merge pull request #513 from kuba--/todo-501
Address the overflow issue with values outside the int64 range
2020-07-01 21:09:09 +02:00
Kuba Podgórski
4bed1df101 Address the overflow issue with values outside the int64 range 2020-07-01 20:40:47 +02:00
Kuba Podgórski
d6caf34c02
Merge pull request #509 from kuba--/todo-503
Add test for Rows on bool
2020-07-01 16:32:24 +02:00
Kuba Podgórski
f0abb5e8b4
Merge branch 'master' into todo-503 2020-07-01 16:23:38 +02:00
Kuba Podgórski
e52b1c04fc
Merge pull request #512 from kuba--/todo-504
FieldValue - check if column arg exists
2020-07-01 16:06:16 +02:00
Kuba Podgórski
5bbb3e2065 FieldValue - check if column arg exists 2020-07-01 11:36:42 +02:00
Kuba Podgórski
58964947d4
Update executor_internal_test.go
Co-authored-by: Travis Turner <travis@pilosa.com>
2020-07-01 10:17:30 +02:00
Kuba Podgórski
f921c5ded0 Add test for Rows on bool 2020-07-01 01:30:36 +02:00
Jaden Weiss
3e0ce32d00
roaring cleanup 2020-06-30 16:38:16 -04:00
Alan Bernstein
3841419907 Use more believable words 2020-06-30 12:23:36 -05:00
Alan Bernstein
31ae30f703 Replace smart quotes 2020-06-30 11:26:25 -05:00
Alan Bernstein
5a6db1cc2b Document topn heuristic behavior 2020-06-30 11:26:16 -05:00
Jaden Weiss
ad2390444a
Merge pull request #500 from molecula/seebs-big-inspect-pr
Improve inspect output, switch roaring over to using new unmarshal, handle inspecting whole holders
2020-06-29 15:31:01 -04:00
Jaden Weiss
31014d11e5
remove unnecesary slice operation when processing holder 2020-06-29 15:22:27 -04:00
Seebs
4d494f6699
shared/generic functionality for iterating holders
This is sort of large, but it's annoyingly difficult to
separate out.

The basic idea is to allow us to have a single holder-iterating
block of code, which is associated with the holder, that can be used
for various things, like the snapshot queue background scan, or
for inspect operations.

We invent the concept of a HolderFilter, which is a thing that
can decide what things in a holder it cares about, and a HolderOperator,
which can also process those things selectively.

In the process, we fix up a couple of subtle bugs in the
inspect logic; specifically, the assumption that the mapped flag could
tell you whether a container was modified by the ops log doesn't
work with mmap, so we have a shiny new flag which is used to track
that, internal to the roaring/container code.

All of this leads to the actual *point* of this exercise, which is
making it easier to create an /inspect endpoint which produces almost
the same data we'd have gotten from `pilosa inspect` on a data directory;
the distinction is that it doesn't try to identify the distinction
between data from disk and data from operations since the file was
loaded. Possibly it should, but it doesn't yet.

The snapshot queue is now implemented using the HolderOperator
design, which requires some subtle changes to how it works, but
overall makes it easier to follow the snapshot queue logic,
and also shares that logic with the way Inspect works.

The holder's snapshot queue is now provided by the server, in
a default environment.

The queueless snapshot queue no longer triggers snapshots on
enqueue -- it turns out that breaks badly, because a key
point about enqueueing a snapshot is that it's safe to do it
*during* a transaction on that fragment, and triggering a
snapshot during a transaction actually causes horrible errors
as the ops log ends up being the old file, which we close.
Related to this, we also need to prevent closed fragments from
trying to snapshot, so we track fragment openness when opening
or closing, and bail on trying to snapshot a fragment which is closed.

We also stop using the queueless snapshot queue during tests,
because that's a horrible idea.

We copy a little bit of the partition logic from the cluster code so
we don't have to expose it all, this lets us check whether the node
we're looking at is the one which should be primary for a given shard,
and if not, identify which node would be. This works only when
pointed at a data directory, for now.

The test cases for the holder have to be internal, because pilosa
doesn't export view/fragment, just Index/Field. This means that the
holder test cases can't just use the test/* package, so they duplicate
some of its logic, approximately.
2020-06-29 15:18:47 -04:00
Seebs
2826ecc0b7
track retries correctly in truncation case 2020-06-29 15:13:51 -04:00
Seebs
176d49e4b5
use syswrap to close file after opening it with syswrap 2020-06-29 15:13:50 -04:00
Seebs
3e7f0b32e9
drop old Call data while processing a list of calls
We don't need the Calls anymore, and especially Precomputed calls
(like Distinct) could be a significant memory load that's increased
as we process additional calls, so we drop the Precomputed references.
We can't drop the calls entirely -- translation can require lookups of
call arguments.
2020-06-29 15:13:50 -04:00
Seebs
7e7051d387
don't try to truncate files when invoked read-only 2020-06-29 15:13:50 -04:00
Seebs
ceaf5c15d1
thread the holder through things, and improve snapshot queue logic
This is logically two separate things, but the individual changes
are thoroughly intertwined in the code.

The first change is a logical change to the design of the snapshot
queue, which is that it now adjusts the maxOpN the background scan
targets, allowing it to lower that value over time when things are
quiet. We do this because it turns out that on large data sets,
this can make a factor-of-four difference in memory usage!

So, in general, on a quiet system, each pass through the holder
aims for about 1/4 of the existing fragments to get snapshotted.
When there's more load, we adjust those values up.

We also make the snapshot queue a bit less chatty, to make testing
less annoying -- we only print stats if the queue enqueues at least
two snapshots, or skips any.

The second change is threading the holder through things. We've
always threaded the logger through, and then added the snapshot
queue, and some of the Inspect-related work led to wanting to
have a way to thread options through, so what if we just threaded
the holder itself through, and removed the direct copying around
of the logger, snapshot queue, and so on. Similarly, everything
can now use holder.PartitionN instead of having to get its own
copy of PartitionN handed out to each index.

This does imply ensuring that test cases always get a reasonable
default holder.

This is a precursor to adding additional information to the holder,
such as whether it's in a special read-only mode, which would imply
not modifying on-disk files. This is already semi-supported for
the specific case of the background snapshot queue and cache flushing,
which are attached to the (created in a previous commit) new
holder Activate method, instead of being automatic on holder Open.

The change to a snapshot queue can also cause races in tests, because
the fragment.Clean method's "sanity check" accesses a fragment without
a lock. Fix that. Since there's a couple of t.Fatalf(), but we need
to release the lock before closing, we use an anonymous function
with a defer to handle that. Whee!
2020-06-29 15:13:50 -04:00
Seebs
121717594b
improve inspect output, switch roaring over to using new unmarshal
At some point the code changeover to use roaring iterators for
unmarshal got dropped, but the old unmarshal code is way harder to
make work for inspect, so this change is back.

This exports some of the names from the things returned by Info,
but also adds a roaring function to use the unmarshalling logic on
arbitrary data, allowing us to get more insight into a file -- in
particular, letting us distinguish between the bitmaps specified by
the roaring data and the bitmaps resulting from applying the ops log.
2020-06-29 15:13:47 -04:00
Jaden Weiss
c1612851af
Merge pull request #498 from jaddr2line/transaction-test-timeout
TestTransactionManager: raise transaction timeouts to avoid sporadic failures
2020-06-29 14:42:21 -04:00
Jaden Weiss
0d05e413d3
TestTransactionManager: raise transaction timeouts to avoid sporadic failures 2020-06-29 13:44:20 -04:00
Jaden Weiss
a175858375
Merge pull request #496 from jaddr2line/molecula-rebrand
Rebrand pilosa binaries
2020-06-26 17:51:21 -04:00
Jaden Weiss
fedb1f7dbd
set version to match Molecula convention 2020-06-26 17:41:19 -04:00
Jaden Weiss
4a4252bb28
Merge pull request #1 from codysoyland/enterprise-removal
Remove a few more enterprise references
2020-06-26 17:24:02 -04:00
Cody Soyland
4f22c388b3 Remove a few more enterprise references 2020-06-26 15:57:21 -05:00
Jaden Weiss
cd317d7e91
tweak VERSION_ID 2020-06-26 16:49:06 -04:00
Jaden Weiss
aa1995073f
rebrand pilosa binaries
This change rebrands the Pilosa binaries from "Pilosa Enterprise" to "Molecula Pilosa" and simplifies the version info string.
2020-06-26 16:49:05 -04:00
Kuba Podgórski
d99971a6c2
Merge branch 'master' into union-run-run 2020-06-26 22:32:07 +02:00
Kuba Podgórski
8b33a073b4
Merge pull request #495 from kuba--/int-eq
Support '=' condition for int/decimal fields
2020-06-26 22:30:03 +02:00
Kuba Podgórski
4351464f84
Merge branch 'master' into int-eq 2020-06-26 21:26:44 +02:00
Jaden Weiss
a78b1cfe19
Merge pull request #446 from jaddr2line/fastrank
Remove allocations from ranked cache when possible
2020-06-26 12:46:58 -04:00
Kuba Podgórski
3782c3ac14 Support '=' condition for int/decimal fields 2020-06-26 18:14:59 +02:00
Jaden Weiss
94b55f8556
remove allocations from ranked cache when possible 2020-06-26 12:12:51 -04:00
Jaden Weiss
079bee5711
Merge pull request #489 from jaddr2line/field-cleanup
Remove unused field code
2020-06-26 11:37:15 -04:00
Jaden Weiss
5523587435
pilosa: remove unused field code 2020-06-26 10:14:56 -04:00
Kuba Podgórski
76324f1498
Merge branch 'master' into union-run-run 2020-06-26 01:28:25 +02:00
Jaden Weiss
0a030e9a77
Merge pull request #482 from jaddr2line/rm-invalid-unsafe
Remove invalid uses of unsafe from roaring containers
2020-06-25 15:02:33 -04:00
Jaden Weiss
934048bb02
roaring: remove invalid uses of unsafe 2020-06-25 11:48:21 -04:00
Kuba Podgórski
47481432ef
Merge pull request #468 from kuba--/fix-DEGRADED
Lets the remote node to proceed
2020-06-25 17:05:30 +02:00
Kuba Podgórski
80cfb5c182
Merge branch 'master' into fix-DEGRADED 2020-06-25 16:41:41 +02:00
Kuba Podgórski
0bdb420b4c
Merge pull request #475 from kuba--/fix-iface-conv
Make a safe cast
2020-06-25 15:36:53 +02:00
Kuba Podgórski
ed86f6ea5d Make a safe cast 2020-06-25 14:44:14 +02:00
Cody Soyland
bc9d13206f
Merge pull request #490 from codysoyland/ci-fixes
Fix/refactor CircleCI config
2020-06-24 13:52:45 -05:00
Cody Soyland
2024228153 Fix/refactor CircleCI config
- Fix incorrect usage of workspaces (vendor dir in current directory no
  longer primary cache of go modules)
- Refactor checkout, github-auth, and mod cache into a reusable command
- Fix issue with dockerhub upload and github authentication
2020-06-24 13:45:17 -05:00
Kuba Podgórski
413ce4f1ab
Merge branch 'master' into fix-DEGRADED 2020-06-24 19:56:24 +02:00
Jaden Weiss
2925101b09
Merge pull request #487 from jaddr2line/cluster-message-error
Differentiate between cluster message request errors and cluster message processing errors
2020-06-24 09:44:03 -04:00
Jaden Weiss
b5b60b8912
http: differentiate between cluster message request errors and cluster message processing errors 2020-06-24 09:23:08 -04:00
Kuba Podgórski
905cda7f08 Add benchmarks 2020-06-23 22:18:13 +02:00
Kuba Podgórski
443f2d8f7c The third attempt to implement unionRunRunInPlace 2020-06-23 22:18:13 +02:00
Kuba Podgórski
eddbb7d0b0 Optimize run intervals by combnining neighbours 2020-06-23 22:18:13 +02:00
Kuba Podgórski
2b0cd2f234 Add unionRunRunInPlace 2020-06-23 22:18:13 +02:00
Jaden Weiss
89ac3f4737
Merge pull request #486 from jaddr2line/handlerfixes
Fix incorrect error handling in HTTP handler and output raw error text when JSON is not selected
2020-06-23 12:57:55 -04:00
Jaden Weiss
66aef92cfa
http: fix incorrect error handling and output raw error text when JSON is not selected 2020-06-23 12:26:37 -04:00
Cody Soyland
16ebdf290d
Merge pull request #479 from codysoyland/ci-dockerhub-fix
Fix and simplify CI DockerHub integration
2020-06-23 09:13:41 -05:00
Cody Soyland
7cb9548097 Fix and simplify CI DockerHub integration 2020-06-19 11:41:54 -05:00
Jaden Weiss
5dc02a3998
Merge pull request #474 from jaddr2line/timeclr
Fix Clear() returning `false` when it should return `true`
2020-06-19 09:03:42 -04:00
Jaden Weiss
a270bff67b
fix Clear() returning false when clearing a bit with no time views 2020-06-19 08:51:08 -04:00
Kuba Podgórski
739935c3f6
Merge branch 'master' into fix-DEGRADED 2020-06-18 16:13:58 +02:00
tgruben
21888703e5
Merge pull request #469 from tgruben/splat-init
Functionalize  fillerBitmap initialization
2020-06-17 09:39:16 -05:00
tgruben
3103da2c19
Update roaring/container_stash.go
Co-authored-by: Jaden Weiss <jaden@jadendw.dev>
2020-06-17 08:51:25 -05:00
Todd Gruben
f47888989a shorten fillerBitmap initialization 2020-06-17 07:12:24 -05:00
Kuba Podgórski
87306d3145 Lets the remote node to proceed, instead of waiting in DOWN state because cluster is in STARTING state. 2020-06-17 11:37:30 +02:00
Jaden Weiss
7f4142497e
Merge pull request #467 from jaddr2line/splat
Optimize `splatRun`
2020-06-16 13:50:27 -04:00
Jaden Weiss
8fc7861148
roaring: optimize splat 2020-06-16 13:04:50 -04:00
Travis Turner
cf24ee161f
Merge pull request #465 from travisturner/document-shift
Add comments warning that Shift() is unsupported
2020-06-15 17:53:38 -05:00
Travis
c5786e1d78
Add comments warning that Shift() is unsupported 2020-06-15 17:16:00 -05:00
alisharawal
4a68b46597
Merge branch 'master' into alisharawal-patch-1 2020-06-15 11:15:14 -05:00
Jaden Weiss
d9baea83f4
Merge pull request #454 from jaddr2line/groupbyoffset
Apply base in GroupBy on BSI
2020-06-11 17:24:39 -04:00
Jaden Weiss
837d0a1465
Merge branch 'master' into groupbyoffset 2020-06-11 12:20:35 -04:00
Jaden Weiss
32e47642ae
address review of "Apply base in GroupBy on BSI" 2020-06-10 17:49:00 -04:00
tgruben
8bc303793c
Merge pull request #453 from tgruben/includes-perf
Optimized performance of row.Includes
2020-06-10 15:50:50 -05:00
Jaden Weiss
535257af75
apply base in GroupBy 2020-06-10 16:16:53 -04:00
Todd Gruben
416f332070 Optimize row.Includes 2020-06-10 13:12:38 -05:00
Jaden Weiss
ec9474114a
Merge pull request #449 from jaddr2line/fixbsioffbyone
Fix BSI comparison match-all-but-one operation
2020-06-10 10:41:35 -04:00
Jaden Weiss
8887927dbd
add regression test for BSI match-all-but-one operations 2020-06-10 10:09:33 -04:00
Jaden Weiss
d478dd9d94
fix BSI comparison match-all-but-one operation 2020-06-10 09:23:39 -04:00
Kuba Podgórski
058ed747c7
Merge pull request #437 from kuba--/err-check
Return error instead of panicking on Store(Distinct())
2020-06-09 14:53:23 +02:00
Kuba Podgórski
9c3b080bf0 Check result before return 2020-06-09 11:39:16 +02:00
Cody Soyland
a519907822
Merge pull request #440 from codysoyland/ci-size
Use xlarge executor in CircleCI
2020-06-08 15:24:31 -05:00
Cody Soyland
8352f5d273 Add configurable resource class, enable only for test-race. 2020-06-08 15:20:31 -05:00
Cody Soyland
ecbb5b2a0d Use xlarge executor in CircleCI 2020-06-08 15:20:31 -05:00
Jaden Weiss
12e6534244
Merge pull request #426 from jaddr2line/simplebsi
Simplify BSI comparisons
2020-06-08 16:14:41 -04:00
Jaden Weiss
ee8036d376
Merge branch 'master' into simplebsi 2020-06-08 16:07:29 -04:00
Jaden Weiss
120cc02536
process BSI ops more efficiently 2020-06-08 14:52:19 -04:00
Jaden Weiss
721a968d63
Merge pull request #438 from jaddr2line/cpumhz
fix CPU speed on non-Intel platforms
2020-06-08 13:34:27 -04:00
Jaden Weiss
a4e53b4bc1
Merge branch 'master' into cpumhz 2020-06-08 13:26:49 -04:00
seebs
c0c027ef67
Merge pull request #429 from seebs/racetime
Improve time requirements for tests with race detector on
2020-06-08 12:25:07 -05:00
Seebs
44569fa210 Reduce iterations in TestFragment_RowsIteration
We don't really learn more from trying every multiple of 10,000 than we do
from trying maybe 32 values, and it's worse at larger shard widths.
2020-06-08 12:10:40 -05:00
Seebs
52aa3e2e23 Improve container/bitmap comparison logic for testing
We have a "deadcode" bitmapsEqual which is actually used in testing but
probably shouldn't be, and we don't have a good container equality test.

Problem is, equality tests are sort of slow in the things-are-equal case,
which is the most common case, so we've got some moderately-specialized
code here; specifically, special comparison code that takes advantage
of knowing that if two containers have the same number of bits, you only
have to check whether all the bits from one are present in the other,
because that can't be true for differing containers with the same number
of bits. This reduces the runtime for the ContainerCombinations case
from about 24 seconds to a bit under 2 on my laptop, or from around
10 minutes to about 37 seconds with the race detector on.

Also simplify the InPlaceWrapper functions not to invoke bitmaps, because
it's not really necessary.
2020-06-08 12:10:40 -05:00
Seebs
3f0c9925f4 Don't test quite so many values for BtreeSeek and BtreeDelete
BtreeSeek is O(N^2) on its N, and there's not a ton of extra utility
to testing a larger range of values, so we reduce N by a bit, cutting
runtime from ~10s to <1s on my laptop. Also reduce the scale of the
BtreeDelete1/BtreeDelete2 tests a bit because, again, lots of runtime
for little marginal information.
2020-06-08 12:10:40 -05:00
Seebs
e223c79ace Don't use a whole shard of values for Execute_All test.
This is pretty expensive even for default shard width, and very expensive
for ShardWidth = 1<<22, and we don't really get much extra benefit from
having a million values instead of a hundred or so.
2020-06-08 12:10:40 -05:00
Seebs
1484674a1c Add and use bitmap-to-slice-or-set comparison functions
The generation of slices from things, and use of reflect.DeepEqual to compare
the slices, is a lot more expensive than it needs to be. Omitting it removes most
of the runtime of the marshal tests.
2020-06-08 12:10:40 -05:00
Seebs
1952a43ed4 Write fewer bits to test the rowcache behavior
The failure mode in question was pretty predictable and tied to number of
snapshots, not to number of bits written, so we can probably use a lot fewer
bits and still get good results, but this is really slow under -race testing.
2020-06-08 12:10:40 -05:00
Seebs
1460756b3f Provide option for adjusting node timeouts, set it for tests.
There's no reason to have 10-20 seconds of delays for testing this,
because in testing, we're running things on the local machine and don't
need to worry about significant network lag. Make retry count and delay
settable options, and set them lower. Moves the Replica2 test in
server/server_test.go from ~21s to ~2s.
2020-06-08 12:10:40 -05:00
Seebs
55ff03a2d6 Lower scale of some random-value tests
The random-value tests can be pathological, and in particular, the
test of arbitrarily-spaced values is in effect O(N^2), and with race
testing on, that test *alone* can take ten minutes to run, but
it's not really all that exciting. We just reduce a bunch of values
and/or test fewer things for these, which doesn't significantly alter
coverage, but reduces test runtime on my laptop with `-race` from
21 minutes to a bit under 5.
2020-06-08 12:10:40 -05:00
Jaden Weiss
7695e63bc5
fix CPU speed on non-Intel platforms 2020-06-08 11:40:53 -04:00
Jaden Weiss
0a17b3713c
Merge pull request #428 from jaddr2line/cleanshutdown
Cleanly shut down the executor
2020-06-05 18:32:57 -04:00
Jaden Weiss
b0a0524ffe
cleanly shut down the executor 2020-06-05 15:25:35 -04:00
Jaden Weiss
0ce5d92407
simplify BSI comparisons 2020-06-05 14:13:29 -04:00
seebs
33728e65d9
Merge pull request #408 from seebs/execontext
thread contexts better through executor
2020-06-04 15:18:12 -05:00
seebs
610d72dbfc
Merge branch 'master' into execontext 2020-06-04 14:57:16 -05:00
Jaden Weiss
0095810e4d
Merge pull request #419 from jaddr2line/trackqueries
track active queries
2020-06-04 14:58:22 -04:00
Jaden Weiss
06517075bf
add unit test to active query tracker 2020-06-04 14:05:06 -04:00
Jaden Weiss
1099a57945
fix pretty printing of active queries list to handle special characters and multiline queries 2020-06-04 14:04:46 -04:00
Jaden Weiss
023efcaba6
track active queries 2020-06-04 10:50:15 -04:00
Seebs
439c710ca9 thread contexts better through executor
When a mapper hits an error, we want it to immediately tell the
other things in that same mapper that they can stop now. But we
don't want to propagate that all the way back up; if a specific
node has a failure executing a query, we will in some cases want
to send a new query to other backup nodes, so the overall
context isn't cancelled yet.

In general, mapFn and reduceFn have been closures that inherit
a context from the function defining them -- but we don't want
that! We want them to be stopped if their specific mapper gets
cancelled, too, because otherwise they can consume a lot of
resources long after the mapper has stopped being interested
in them.  So now those are parameters passed into them,
and mapperLocal puts *those* contexts in the jobs shoved into
the job queue, and the workers pass the context in to the
mapFn/reduceFn.

We also check responses from reduceFn now; both mapReduce
and mapperLocal check for a possible error, and return that,
and reduce functions doing anything nontrivial check their
context.

We also add a few more explicit checks for context cancellation
in various places, especially in the GroupByIterator which is
what bit us that one time. The explicit check against ctx.Err
is officially safe as of Go 1.9 or so. (It was previously
unspecified, but on further study, the Go team concluded that
no actual implementation did anything else, and existing code
was already depending on that.) This also affects the rows
function, because that could potentially take quite a while to
run for a large fragment.
2020-06-03 16:09:01 -05:00
seebs
33a90fd328
Merge pull request #297 from seebs/nofreeze
Don't automatically freeze the results of RowSegment ops
2020-06-03 14:54:12 -05:00
Seebs
6abe7dc12f Don't automatically freeze the results of RowSegment ops
I think when this code was written, I thought "freeze" would be
really cheap. It's not actually that cheap. As a result, freezing
things preemptively when it may be that nothing ever tries to write
to them anyway is possibly disadvantageous, to the tune of being
roughly 20% of a sample profile we were shown. Instead, we don't
mark the components "writable", so if anything wants to write to
them, it'll end up freezing itself new copies of their bitmaps
later. But in practice that probably doesn't happen.
2020-06-03 13:21:41 -05:00
Kuba Podgórski
0bba9c81e8
Merge pull request #417 from kuba--/rev-mtx
Change order of cluster/index locks
2020-06-03 15:37:20 +02:00
Kuba Podgórski
0320228b99 Change order of cluster/index locks 2020-06-03 15:09:56 +02:00
Kuba Podgórski
a8e6846e78
Merge pull request #401 from kuba--/etag
Add (in memory) CreatedAt to index and fields
2020-06-03 15:09:22 +02:00
Kuba Podgórski
bb048da241
Update docs/api-reference.md
Co-authored-by: Matthew Jaffee <matthew.jaffee@gmail.com>
2020-06-03 14:47:51 +02:00
Kuba Podgórski
6a892102e4 Update api-reference.md 2020-06-03 13:26:16 +02:00
Kuba Podgórski
d8a417f657 Move applyCreatedAt from mergeClusterStatus directly to ClusterStatus message, to avoid deadlocks 2020-06-03 13:26:16 +02:00
Kuba Podgórski
0c98c887ac Pass Schema in ClusterStatus message 2020-06-03 13:26:16 +02:00
Kuba Podgórski
ba7f039dd1 Rename etag to createdAt 2020-06-03 13:26:16 +02:00
Kuba Podgórski
3d270f45d2 Add (in memory) ETag to index and fields 2020-06-03 13:26:16 +02:00
tgruben
43195ae2dd
Merge pull request #416 from tgruben/fix-grpc-address
add address for listening
2020-06-02 16:25:53 -05:00
Todd Gruben
fdaae31c2c add address for listening 2020-06-02 15:39:28 -05:00
Matthew Jaffee
31c5e7363d
Merge pull request #412 from jaffee/escape-query-strings-411
modify PQL parser to handle escapes in string values
2020-05-29 08:51:54 -05:00
Matt Jaffee
1c1204fc77
modify PQL parser to handle escapes in string values
This modifies the parser to properly "unquote" incoming strings. So if
a string comes in double or single quoted, we approximately follow Go
rules for removing the quotes and processing escape sequences.

The differences from Go are:
1. we only support backslash, quote, tab and newline escape
sequenences.
2. Single quoted strings are supported and work just like double
quoted strings.
3. The peg parser won't actually accept backquoted strings (I don't
think)

Fixes: #411
2020-05-29 07:58:50 -05:00
tgruben
c177bf831d
Merge pull request #409 from tgruben/transaction-doc-update
correction to endpoint
2020-05-29 07:22:18 -05:00
tgruben
242b3d4b14
Merge branch 'master' into transaction-doc-update 2020-05-29 07:02:02 -05:00
seebs
6aa5041de6
Merge pull request #313 from seebs/roaring4g
Handle file sizes over 4GB
2020-05-28 22:55:24 -05:00
Seebs
1ca9435af9 Handle file sizes over 4GB
We only have 4 bytes for offsets, but what if a file is
over 4GB? Someone came to us with a file with 265 *million* containers,
in a single fragment, which means that over 3GB of their 4.7GB file
is actually just the container headers alone. But we can't easily make
the offsets larger, or change the file format.

So we don't. We just track how many 4GB hunks of the file we've
been through and bump that every time the 32-bit offset wraps. And this
appears to... just work.

This is fixed for both the roaring iterator and the old unmarshalBinary
logic. The logic to handle this will work on 32-bit hosts in the sense
that it will correctly error out for excessively large file sizes or
container counts, but it doesn't actually handle the large files since
it can't.
2020-05-28 17:08:32 -05:00
Todd Gruben
4d1ce90b32 correction to endpoint 2020-05-28 15:36:00 -05:00
tgruben
a777eddfcf
Merge pull request #405 from tgruben/trace-tagging
added some context to tracing
2020-05-28 13:34:19 -05:00
tgruben
5cd15b250a
Merge branch 'master' into trace-tagging 2020-05-27 15:46:51 -05:00
Todd Gruben
4274d2d141 convert to camelCase 2020-05-27 15:23:13 -05:00
Kuba Podgórski
6ed3bde54e
Merge pull request #407 from kuba--/status
Add grpc uri to status
2020-05-27 17:26:32 +02:00
Kuba Podgórski
604f3b3373 Add grpc uri to status 2020-05-27 16:41:38 +02:00
Jaden Weiss
e0291e9d25
Merge pull request #398 from jaddr2line/transactionmetrics
add metrics for transactions
2020-05-27 08:52:53 -04:00
Jaden Weiss
efef42ae97
Merge branch 'master' into transactionmetrics 2020-05-27 08:36:23 -04:00
Todd Gruben
022019c6cc removed shard level tracing tag 2020-05-26 23:23:57 -05:00
Travis Turner
ffa40b1bdb
Merge pull request #387 from travisturner/int-eq-null
Add support for `intfield == null`
2020-05-26 21:05:42 -05:00
Todd Gruben
a2f825a32e added some context to tracing 2020-05-26 17:18:23 -05:00
Jaden Weiss
a4643084bd
Merge branch 'master' into transactionmetrics 2020-05-26 17:55:12 -04:00
Jaden Weiss
5644fb2275
add metrics for transactions 2020-05-26 17:51:28 -04:00
Travis
e4b9293f26
Add support for int == null 2020-05-22 12:29:52 -05:00
Travis Turner
d36f397a35
Merge pull request #404 from travisturner/row-todos
clean up the TODOs and some comments
2020-05-22 12:05:58 -05:00
Travis
041726fbf7
clean up the TODOs and some comments 2020-05-22 11:00:22 -05:00
Travis Turner
2586661812
Merge pull request #402 from travisturner/roaring-tests
Address TODOs in roaring tests
2020-05-21 19:50:38 -05:00
Travis
0a94f8393f
Address TODOs in roaring tests
In addition to adding some tests, this commit moves the
`GenerateUint64Slice()` helper function into  a new `generator` package
so that it can be used in both internal and non-internal tests.
2020-05-21 13:28:54 -05:00
Jaden Weiss
c04143fd61
Merge pull request #394 from jaddr2line/nextdelete
fix use-after-free in b-tree bitmap update
2020-05-20 13:39:05 -04:00
alisharawal
aa0cfecdb3
Update README.md 2020-05-20 11:37:22 -05:00
Jaden Weiss
6e3e513425
roaring: fix use-after-free in b-tree bitmap update 2020-05-20 12:01:21 -04:00
Travis Turner
8eae053fe6
Merge pull request #395 from travisturner/roaring-todos
clarify a few of the TODO comments
2020-05-19 15:16:05 -05:00
Travis
631d3deeed
clarify a few of the TODO comments 2020-05-19 13:50:48 -05:00
Travis Turner
4b4fd590ca
Merge pull request #389 from travisturner/proto-todos
finish implementing PairField proto encoding
2020-05-19 08:54:58 -05:00
Travis
4fb3820afb
finish implementing PairField proto encoding 2020-05-18 20:19:22 -05:00
Travis Turner
231fef27eb
Merge pull request #390 from travisturner/deadline-skew
increase test deadlineSkew to 1s
2020-05-18 20:18:51 -05:00
Travis
42814bb70c
increase test deadlineSkew to 1s 2020-05-18 19:57:44 -05:00
Travis Turner
63dd5e1174
Merge pull request #386 from travisturner/bsigroup-edges
Fix edge case bugs in range queries
2020-05-18 15:02:50 -05:00
Travis
d0de49ef39
handle edge cases in range queries 2020-05-16 10:35:35 -05:00
seebs
c6d61391d2
Merge pull request #249 from seebs/roaringrow
use roaring row support internally
2020-05-15 17:00:17 -05:00
Seebs
0ddc968001 cache result of marshalling import-was-OK message
This message gets generated millions of times and it's unchanging
for the life of the program, and small.
2020-05-15 16:22:08 -05:00
Seebs
79940cf077 encoding/proto: allow distinct serializers
We want to be able to control whether or not we use roaring to
serialize Rows, which means serializers have to be able to be
distinct.

We also make corresponding changes to http/handler.go to have
it use the exported serializers directly rather than the API's
serializer (which is always the base protobuf serializer
right now, and if it weren't, that would be bad because we
were assuming it was).

When we're accepting protobuf from a pilosa server, flag that
we'll accept roaring bitmaps as opposed to the naive column
representation.
2020-05-15 16:22:08 -05:00
Seebs
ce0761050e less spammy snapshotqueue
During testing we spawn a lot of tiny snapshot queues. Make the message
less spammy by printing it only if any enqueues were skipped (shouldn't
ever happen) or more than one thing got enqueued (likely in real usage,
but doesn't happen in testing usually).
2020-05-15 16:22:08 -05:00
Kuba Podgórski
3151d83136
Merge pull request #383 from kuba--/grpc-err/356
Wrap grpc resp.Err
2020-05-15 23:14:41 +02:00
Kuba Podgórski
3fe85d0f91 Move grpc_internal_test to grpc_test 2020-05-15 19:52:03 +02:00
Kuba Podgórski
ea08cce8fe
Update api.go
Co-authored-by: Travis Turner <travis@pilosa.com>
2020-05-15 19:39:36 +02:00
Kuba Podgórski
f40601f6f5 Wrap grpc resp.Err 2020-05-15 18:16:31 +02:00
Kuba Podgórski
e39334ca92
Merge pull request #377 from kuba--/opt-indiagnostics/310
Disable diagnostics by default
2020-05-15 02:09:31 +02:00
Kuba Podgórski
7ee8f80012
Merge branch 'master' into opt-indiagnostics/310 2020-05-15 00:53:33 +02:00
Kuba Podgórski
fc9e75edc2
Merge pull request #378 from kuba--/off-ae/340
Turn anti-entropy off by default
2020-05-13 14:26:13 +02:00
Kuba Podgórski
fd27cbb886
Update server/config.go
Co-authored-by: Matthew Jaffee <matthew.jaffee@gmail.com>
2020-05-12 18:51:53 +02:00
Kuba Podgórski
889f79d11a Turn anti-entropy off by default 2020-05-12 16:49:43 +02:00
Kuba Podgórski
368cb8cb53 opt-in diagnostics 2020-05-12 15:37:47 +02:00
Travis Turner
2f44a16755
Merge pull request #374 from travisturner/todo-fixes
tidy up some of the TODO comments
2020-05-10 21:45:13 -05:00
Travis
bc8244a581
well, put the TODO back, just in a different place 2020-05-10 19:18:54 -05:00
Travis
d546b8ac01
use pilosa.ErrNotImplemented for unused interface implementations 2020-05-10 18:50:05 -05:00
Travis
9255d43e9a
tidy up some of the TODO comments 2020-05-09 22:26:12 -05:00
Cody Soyland
16f0b5eaba
Merge pull request #343 from codysoyland/branch-rename
Rename enterprise branch to master
2020-05-08 17:50:02 -05:00
Cody Soyland
b96666d06a Rename enterprise branch to master 2020-05-08 16:29:17 -05:00
Kuba Podgórski
5578eb8daa
Merge pull request #258 from kuba--/intersect-inplace
The first implementation of intersect in place
2020-05-07 11:23:40 +02:00
Kuba Podgórski
c802caeddd The first implementation of intersect in place 2020-05-06 23:59:07 +02:00
Kuba Podgórski
1a00008dbd
Merge pull request #339 from kuba--/getridof-rowid
get rid of rowID from groupby on ints response
2020-05-06 23:43:56 +02:00
Kuba Podgórski
19df3211f9 get rid of rowID from groupby on ints response 2020-05-06 23:23:43 +02:00
Travis Turner
04f09870ec
Merge pull request #338 from travisturner/remove-lookup
remove extra index lookup
2020-05-06 13:54:18 -05:00
Travis
2ca4e971f1
remove extra index lookup 2020-05-06 13:04:46 -05:00
Travis Turner
994ac5aad8
Merge pull request #337 from travisturner/fieldvalue
add FieldValue call
2020-05-06 12:29:39 -05:00
Travis
a91014c7bb
add FieldValue call 2020-05-06 11:52:27 -05:00
Travis Turner
a72de2c68a
Merge pull request #329 from travisturner/cluster-startup
avoid deadlock on translationSync.Reset during startup
2020-05-05 08:56:23 -05:00
Travis
92a94c9ec1
remove noSleep option 2020-05-05 08:31:43 -05:00
Travis
7b36f417d8
avoid deadlock on translationSync.Reset during startup 2020-05-05 08:31:42 -05:00
Travis Turner
f9cb7abccf
Merge pull request #334 from travisturner/block-limits
Fix off-by-one maxRowID in block limits
2020-05-04 14:02:30 -05:00
Travis
de0785d305 add a test for the "clears" bug 2020-05-01 16:15:42 -05:00
Travis
1f308066a7 fix test error messages 2020-05-01 15:54:56 -05:00
Travis
b25796f532 Fix off-by-one maxRowID in block limits
In the case where a block merge needed to occur
on a replica containing a row on the edge of the block,
the existing logic would inadvertently clear the first
row in the next block. This PR fixes that.
2020-05-01 15:15:47 -05:00
Matthew Jaffee
8e0a787ec0
Merge pull request #332 from jaffee/tracing-off
change default tracing config to 'off'
2020-04-30 21:25:02 -05:00
Matt Jaffee
8aa7a76d31
change default tracing config to 'off'
also fix a typo
2020-04-30 15:02:15 -05:00
Cody Soyland
ea99f32d36
Merge pull request #327 from codysoyland/docker-bind-grpc
Bind to public interface in docker image
2020-04-28 14:45:06 -05:00
Cody Soyland
0c7a83dc0b Bind to public interface in docker image 2020-04-28 14:20:22 -05:00
Travis Turner
95d44dce28
Merge pull request #318 from travisturner/config-cmd
suppress test config arguments from pilosa config output
2020-04-24 13:23:36 -05:00
Travis
9405463911 suppress test config arguments from pilosa config output 2020-04-24 11:02:42 -05:00
Travis Turner
022b135538
Merge pull request #316 from travisturner/json-header
Ensure content-type header is application/json where appropriate
2020-04-23 22:10:44 -05:00
Travis
af6eb94c1d increase deadlineSkew so TestTransactionsAPI doesn't fail during race test 2020-04-23 17:26:35 -05:00
Travis
5da907d469 ensure content-type header is application/json where appropriate 2020-04-23 16:24:40 -05:00
Travis Turner
8cf06bf5d9
Merge pull request #314 from travisturner/decimal-adjust-precision
Adjust decimal precision if we have decimal places to sacrifice.
2020-04-23 08:43:23 -05:00
Travis
432e18d571 return early on mantissa=0 2020-04-22 19:11:23 -05:00
Travis
809a02d986 Adjust decimal precision if we have decimal places to sacrifice. 2020-04-22 17:10:05 -05:00
Travis Turner
2795a3f7e2
Merge pull request #309 from molecula/transactions
Transactions
2020-04-22 15:52:42 -05:00
Matt Jaffee
97ae8e0db7
add kuba testcase, fix race
we fix the race by not returning pointers to the things which we're
keeping in the in-memory store
2020-04-22 15:17:25 -05:00
Matt Jaffee
85f57f9975
fix lint 2020-04-22 14:30:14 -05:00
Matt Jaffee
7c7836f16f
convert transactions to be pointers everywhere
I think this will improve the transaction response messages Kuba
mentioned where it was an empty transaction instead of a nil or not
there... if not it should make it easier to do that anyhow.
2020-04-22 14:01:24 -05:00
Matt Jaffee
9dbc6f89db
address minor feedback from previous PR 2020-04-22 12:31:25 -05:00
Matt Jaffee
7da137277c
tweak comment, add validation TODO 2020-04-22 10:42:59 -05:00
Matthew Jaffee
afcb3e7f96
Merge pull request #2 from travisturner/backups
minor code adjustments during review
2020-04-22 07:35:05 -05:00
Travis Turner
c9b7ed51aa
Update deadline comment
Co-Authored-By: Matthew Jaffee <matthew.jaffee@gmail.com>
2020-04-21 17:49:57 -05:00
Travis
08583cf2d0 minor code adjustments during review 2020-04-21 16:54:17 -05:00
Matt Jaffee
a3c5f4822e
keep the zone info back in deadline strings (but output in UTC)
instead of defining them as being in UTC, but not including the zone
info, we will keep the standard format with zone info, but always
output the time in UTC. This means that we can parse incoming
deadlines that happen to have zone information, though I don't think
we ever need to.
2020-04-21 12:29:19 -05:00
Matt Jaffee
5e29effa93
don't wrap error, dedup compare transactions code 2020-04-20 22:30:14 -05:00
Matt Jaffee
b23d27f507
add cluster state validation to API methods for transactions 2020-04-20 22:22:40 -05:00
Matt Jaffee
662ed4f324
invert if statements and fix typos 2020-04-20 22:12:07 -05:00
Matt Jaffee
432ab57822
add license headers 2020-04-20 14:41:05 -05:00
Matt Jaffee
89c1d48a0f
transaction deadline format UTC, lint
also change "deadlineSkew" comparison in tests to account for race tests in CI
seeing false differences
2020-04-20 14:37:28 -05:00
Matt Jaffee
c36952a0f1
propagate context throughout transaction stuff 2020-04-20 13:30:48 -05:00
Matt Jaffee
41975de6b8
add transactions external documentation
- make sure client reads/closes all bodies
- support blank transaction ID in http handler
2020-04-20 13:30:48 -05:00
Matt Jaffee
210c7239ab
add HTTP handlers and client for transactions 2020-04-20 13:30:48 -05:00
Matt Jaffee
9ad1106647
implement transaction API layer and intra-cluster messaging
also adds a "noSleep" option to the server command to avoid the 5
second sleep we introduced on startup for non-coordinator cluster
nodes. The sleep doesn't seem to be needed in the tests and makes them
much slower.
2020-04-20 13:30:48 -05:00
Matt Jaffee
088e60b830
better defer that Unlock 2020-04-20 13:30:48 -05:00
Matt Jaffee
38cec6f20e
add TransactionManager and TransactionStore for transactions/backups
This all needs to be wired into API/Server/Cluster/Holder etc. but I
think the TransactionManager will be a pretty good building block for
managing transaction state at the coordinator level.
2020-04-20 13:30:48 -05:00
Seebs
c3ef9a1768
draft outline of transaction API 2020-04-20 13:30:47 -05:00
Cody Soyland
a353527705
Merge pull request #307 from codysoyland/copy-reopen
Copy reopen.FileWriter into pilosa
2020-04-20 12:41:09 -05:00
Cody Soyland
761b090f3c Fix linter error 2020-04-20 12:10:44 -05:00
Cody Soyland
5e1c72e6f2 Copy reopen.FileWriter into pilosa 2020-04-20 11:58:09 -05:00
Travis Turner
53500e5ad5
Merge pull request #304 from travisturner/grpc-listener
move gRPC listener creation outside of grpcServer
2020-04-18 13:14:12 -05:00
Travis
7de8399b17 move gRPC listener creation outside of grpcServer
For tests, we need to create the grpc listener with port 0 in order to
automatically assign a port. This PR moves the lister creation outside
of the grcpServer itself so that we can access that auto-created port.
2020-04-18 11:38:15 -05:00
Travis Turner
72d496f5c7
Merge pull request #302 from travisturner/grpc-client-queryunary
add QueryUnary to the grpc client api
2020-04-17 16:32:41 -05:00
Travis
2b98ef5edb add QueryUnary to the grpc client api 2020-04-17 16:12:10 -05:00
seebs
26091bd3ed
Merge pull request #301 from codysoyland/makefile-checkptr
Use makefile variable for NOCHECKPTR
2020-04-17 15:57:33 -05:00
Cody Soyland
3b5908332d Use makefile variable for NOCHECKPTR 2020-04-17 15:15:43 -05:00
seebs
f8f924e972
Merge pull request #300 from seebs/groupby
GroupBy should terminate even if the last result is empty
2020-04-17 15:13:49 -05:00
Seebs
3a7ab3b8eb GroupBy should terminate even if the last result is empty
If you have two criteria, and the last result you generate is
empty, the nextAtIdx iterator for i==1 will try to continue
poking the i==0 iterator. That one produces a nil result, and
declares the entire group-by iterator done... But the nextAtIdx
call above it isn't checking that, and just loops forever.
This causes some queries to become stuck permanently, consuming
ridiculous amounts of resources almost entirely focused on
calling Intersect millions of times to get empty results.
2020-04-17 14:39:03 -05:00
Cody Soyland
398ce117ec
Merge pull request #287 from codysoyland/logger
Reopen log file on SIGHUP
2020-04-17 12:40:25 -05:00
Cody Soyland
1058440cfe Do not reuse error object (data race) 2020-04-16 17:08:50 -05:00
Cody Soyland
e4cd1b8871 Add -v flag to test-race 2020-04-16 17:08:50 -05:00
Cody Soyland
a00e93f699 Add TODO about fork 2020-04-16 17:08:50 -05:00
Cody Soyland
2155d6cab8 Move some things around, fix linter warnings. 2020-04-16 17:08:50 -05:00
Cody Soyland
a66ee26a6b Reopen log file on SIGHUP 2020-04-16 17:08:50 -05:00
Cody Soyland
08fa90e0f3 Reduce duplication of setupLogger arch-specific code 2020-04-16 17:08:50 -05:00
Travis Turner
46fb6859ed
Merge pull request #296 from travisturner/grpc-tabler-rowser
gRPC tabler rowser
2020-04-16 15:19:47 -05:00
Travis
c45a4bf3dc ToTable and ToRows interface for gRPC 2020-04-16 14:16:54 -05:00
Travis Turner
0691b6c015
Merge pull request #294 from travisturner/overflow-fix
error on potential overflow
2020-04-15 16:24:37 -05:00
Travis
80f1bdebd7 error on potential overflow 2020-04-15 15:50:03 -05:00
Travis Turner
468b69fba9
Merge pull request #291 from travisturner/range-problems
fix some range query problems
2020-04-15 15:27:12 -05:00
Travis
8a22a3ede3 fix some range query problems 2020-04-15 14:42:16 -05:00
Travis Turner
37f05fc3ea
Merge pull request #288 from travisturner/upgrade-min-max
upgrade decimal min/max with scale
2020-04-15 08:08:00 -05:00
Travis
79a6c1e5ab upgrade decimal min/max with scale 2020-04-15 00:14:07 -05:00
seebs
f41bced2ec
Merge pull request #286 from seebs/storeauto
in Store/SetRow, create field if it doesn't already exist
2020-04-14 15:33:10 -05:00
Seebs
70bfe86f75 in Store/SetRow, create field if it doesn't already exist
If you try to Store to a nonexistent field, we create an automatic
Set field with no cache for it, assuming it won't be used for TopN
queries. If you want TopN to work, you need to actually create it
yourself.
2020-04-14 15:12:36 -05:00
alanbernstein
18d01dd62a
Merge pull request #280 from alanbernstein/snake-case-node
Snakify
2020-04-11 13:25:58 -05:00
Alan Bernstein
615c1bd186 Snakify 2020-04-10 22:15:43 -05:00
Matthew Jaffee
76404142ba
Merge pull request #246 from molecula/prometheus-improvements
Prometheus improvements
2020-04-10 21:55:35 -05:00
Matt Jaffee
68276159ca
don't access req.Query before knowing req is a QueryRequest 2020-04-10 21:07:43 -05:00
Alan Bernstein
18c6d8f76f
Update a few metric names 2020-04-10 20:59:20 -05:00
Alan Bernstein
71b9762501
Address review feedback again 2020-04-10 20:59:20 -05:00
Alan Bernstein
9947c92e8e
Add stats labels and slow-query log in GRPC endpoints 2020-04-10 20:59:20 -05:00
Alan Bernstein
eaf21eb19b
Add metrics for GRPC request timing 2020-04-10 20:59:19 -05:00
Alan Bernstein
5df680fb9f
Remove old metric from tests 2020-04-10 20:59:19 -05:00
Alan Bernstein
d79f04b7b3
Remove MetricMaximumRow 2020-04-10 20:59:19 -05:00
Alan Bernstein
a7bfacbee2
Revert "Add tags to MaxRow metric"
This reverts commit 6013e7211b3aef93d0880401c8ef74b34a620328.
2020-04-10 20:59:19 -05:00
Alan Bernstein
b37a0addb3
Add tags to MaxRow metric 2020-04-10 20:59:19 -05:00
Alan Bernstein
b1838159f2
Minor fixes 2020-04-10 20:59:19 -05:00
Alan Bernstein
df2503dfa6
Switch to Timing helper function 2020-04-10 20:59:19 -05:00
Alan Bernstein
2423ecf8d5
Update some metric names to follow conventions better 2020-04-10 20:59:19 -05:00
Alan Bernstein
f883d61c28
Consolidate BlockRepair metrics with tags 2020-04-10 20:59:18 -05:00
Alan Bernstein
389acfc8ed
Fix minor issues with metric labels and tests 2020-04-10 20:59:18 -05:00
Alan Bernstein
c2c0a5c32f
Address review feedback 2020-04-10 20:59:18 -05:00
Alan Bernstein
8b405c226c
Add 'prometheus' option in other help text/comments 2020-04-10 20:59:18 -05:00
Alan Bernstein
a3fb1c022b
Use metrics consts in tests 2020-04-10 20:59:18 -05:00
Alan Bernstein
8c9db373d0
Fix some metrics names 2020-04-10 20:59:18 -05:00
Alan Bernstein
5137f56f9c
Use const from package 2020-04-10 20:59:18 -05:00
Alan Bernstein
34c6d42063
Fix broken metrics label and log when others are encountered 2020-04-10 20:59:17 -05:00
Alan Bernstein
3c275681d2
Profile -> Column 2020-04-10 20:59:17 -05:00
Alan Bernstein
b1adcd91fc
Use consistent metric name convention 2020-04-10 20:59:17 -05:00
Alan Bernstein
70111b5604
Define metrics names as constants 2020-04-10 20:59:17 -05:00
Alan Bernstein
84e6a25bad
Update help message 2020-04-10 20:59:17 -05:00
Alan Bernstein
857ddf73c2
Reduce snapshot verbosity 2020-04-10 20:59:17 -05:00
Alan Bernstein
eceef6b42b
Use 'query_' prefix to identify query metrics 2020-04-10 20:59:16 -05:00
tgruben
550fcec9ee
Merge pull request #279 from tgruben/fix-closers
Closed all post request bodies and optimized available shard with new view capabilities
2020-04-10 20:29:04 -05:00
Todd Gruben
ccc7ca3aa5 close not needed 2020-04-10 20:12:32 -05:00
Todd Gruben
5590f1d954 lint fix 2020-04-10 19:07:01 -05:00
Todd Gruben
ef5a8cefef Added request Close and optimized availble shard with new view capabilities 2020-04-10 17:59:44 -05:00
seebs
4325d62fe7
Merge pull request #277 from seebs/between
Return empty rows for impossible ranges
2020-04-10 16:21:09 -05:00
Seebs
017e65cd99 Return empty rows for impossible ranges
If the high end of a range is below the low end of the range, there's
no values in it, so we can short-circuit that. If we don't, if the
low end is zero or higher, and the high end is below zero, we can
get very surprising behaviors, such as accepting values up to the
inverse of the high end. Add a test case for this and treat it the
same as a low range end above the field's maximum or a high end
below the field's minimum, returning an empty row immediately.
2020-04-10 13:24:08 -05:00
seebs
9b002bcc24
Merge pull request #271 from seebs/decimalfault
Handle nonexistent shards in min/max decimal queries.
2020-04-09 22:01:07 -05:00
Seebs
8e662d33a5
Handle nonexistent shards in min/max decimal queries.
If a shard has never had any decimal values in it at all for a
field, the ValCount object returned has no DecimalVal, which could
cause a segfault if we don't check for it. Add a test case which
sporadically triggers that behavior (it's timing/luck related,
unfortunately), and then also fix it.
2020-04-09 21:30:34 -05:00
Matthew Jaffee
179cab91e7
Merge pull request #275 from travisturner/serialize-null
serialize null operation (!= null)
2020-04-09 21:25:05 -05:00
Travis
88b2d79812 serialize null operation (!= null) 2020-04-09 17:56:27 -05:00
alanbernstein
0e6ed80bd4
Merge pull request #269 from alanbernstein/long-query-full
WIP Show full query string when logging slow queries
2020-04-09 16:12:11 -05:00
Alan Bernstein
92416018d5
Use more general log message 2020-04-09 15:53:40 -05:00
Alan Bernstein
5aebb242f8
Fix off-by-one error 2020-04-09 15:53:40 -05:00
Alan Bernstein
e3ace544ca
Show full query string when logging slow queries 2020-04-09 15:53:39 -05:00
tgruben
57b2f32fa1
Merge pull request #260 from tgruben/sync-start
delay start for non-coordinator
2020-04-09 14:42:41 -05:00
Todd Gruben
e7bbc3a0a8 Added delay to allow cooridinator a head start in launch on multi node clusters.
considered using Cluster.Disabled to identify if pilosa was stand alone but settled
on using an empty gossip seeds list
2020-04-09 14:20:01 -05:00
alanbernstein
bdfad719df
Merge pull request #263 from alanbernstein/grpc-multi
Support multiple dialTargets and cycle through on connection reset
2020-04-08 13:39:58 -05:00
alanbernstein
1846d21765
Merge pull request #1 from travisturner/dialtargets
cycle through dial targets regardless of error
2020-04-08 12:55:17 -05:00
Travis
8f0ae1b6f5 cycle through dial targets regardless of error 2020-04-08 12:29:15 -05:00
Alan Bernstein
adb21589f0 Support multiple dialTargets and cycle through on connection reset 2020-04-08 10:09:49 -05:00
seebs
1b5d86c8f0
Merge pull request #240 from seebs/q2perf
improve performance of difference/not
2020-04-07 20:24:51 -05:00
Seebs
be379e7806 cache AvailableShards
The computation of available shards is cheap, because realistically, virtually
no one has enough shards that the resulting bitmap is more than one container.

We don't try to fix this at the field/index levels because it's significantly
harder to do there, but I think the creation of these bitmaps is probably
the most expensive part, and switching the unions to union-in-place probably
reduces cost significantly.

Note that the bitmaps being unioned almost certainly have exactly one small
container in them.
2020-04-07 19:33:21 -05:00
Seebs
6c797e0c4e TODO => TODONE: use masks for runToBitmap
Had the code lying around from mad science elsewhere, backported.
2020-04-07 19:33:21 -05:00
Seebs
d4e496887b make differenceRunBitmap smarter
We avoid using bitmapContains so often because that turns out to be expensive.
Also, if we produce more than runMaxSize runs, we're going to convert to
a bitmap container (or possibly an array container if there were over
2048 items, but they're all singletons), and we can streamline that by just
converting the source to bitmap and returning differenceBitmapBitmap, which
is faster in this case.

This appears to overall take about half as long in the workload I was
looking at.
2020-04-07 19:33:20 -05:00
Kuba Podgórski
7868188670
Merge pull request #200 from kuba--/groupby-int/124
Group by on ints
2020-04-08 01:47:17 +02:00
Kuba Podgórski
1217deee1c Rebase 2020-04-08 01:06:18 +02:00
tgruben
64caffebe8
Merge pull request #196 from tgruben/async-available-shards
limit frequency of writes for available shards broadcast
2020-04-06 09:08:04 -05:00
Todd Gruben
6712f8cf06 removed debug log 2020-04-06 08:43:30 -05:00
Todd Gruben
5470753cb5 fix conflict 2020-04-05 18:37:11 -05:00
Travis
0e2bb550db add deleted (rebalanced) shards to remoteAvailableShards 2020-04-05 18:37:11 -05:00
Todd Gruben
6bd81b87eb unexport availableShardFileFlushDuration 2020-04-05 18:37:11 -05:00
Todd Gruben
000ea90877 unbuffer channel 2020-04-05 18:34:43 -05:00
Todd Gruben
af068e08e7 cleanup and comments 2020-04-05 18:34:43 -05:00
Todd Gruben
40a3dce93c Co-authored-by: Travis Turner <github@calfrope.com> 2020-04-05 18:34:43 -05:00
Todd Gruben
bb04f7f6ac limit frequency of writes for available shards 2020-04-05 18:31:28 -05:00
Cody Soyland
0a86f6a97b Increase gRPC maximum message length 2020-04-03 16:35:57 -05:00
Travis
b7fcfb41d9 add proto OldMin/OldMax for backward compatibility 2020-04-03 12:25:07 -05:00
Travis Turner
48879107b0
Merge pull request #244 from travisturner/fix-race
make sure frag.maxRow() call is lock-protected
2020-04-02 23:52:38 -05:00
Travis
b4f1781d30 make sure frag.maxRow() call is lock-protected 2020-04-02 23:25:57 -05:00
seebs
632f6856ab
Merge pull request #229 from seebs/checkptr
disable checkptr with go 1.14
2020-04-02 22:19:45 -05:00
seebs
a20532073b
Merge branch 'enterprise' into checkptr 2020-04-02 20:48:49 -05:00
alanbernstein
4faacad4cf
Merge pull request #242 from molecula/prometheus-options
Add options to prometheus client to support setting namespace
2020-04-02 20:24:15 -05:00
Alan Bernstein
dad4ccf103 Propagate namespace to tags client 2020-04-02 19:29:46 -05:00
Alan Bernstein
110d2b6024 Restore lost comment 2020-04-02 19:07:43 -05:00
Alan Bernstein
f15d031c55 Add options to prometheus client to support setting namespace 2020-04-02 19:05:55 -05:00
Travis Turner
7432546af8
Merge pull request #241 from travisturner/shardwidth22-tests
alter tests to allow for shardwidth22
2020-04-02 18:25:29 -05:00
Travis
182c1d3c42 alter tests to allow for shardwidth22
also, reset BitDepth on field and bsiGroup during
importRoaringOverwrite
2020-04-02 17:32:49 -05:00
Cody Soyland
da503776e2
Merge pull request #237 from codysoyland/ci-tweaks
CI: Modify Docker Hub rules and use "make test-race" for running race detector
2020-04-02 12:25:40 -05:00
Cody Soyland
6a639efdba Modify Docker Hub deployment filter rules.
The documentation is unclear/incorrect, and these filters aren't
behaving correctly. This is an attempt at fixing that. More info at:
https://discuss.circleci.com/t/job-runs-even-when-tags-ignore-filter-is-triggered-when-combined-with-branches-only/20664/11
2020-04-02 12:14:37 -05:00
Cody Soyland
14319cc54b Use "make test-race" instead of custom test flags in CI 2020-04-02 12:14:37 -05:00
Seebs
c6799bd604 disable checkptr with go 1.14
The checkptr feature is actually probably right about a few
things in roaring and boltdb, but we can ignore them for now, and
that prevents checking for races, so we disable that temporarily.

Also supply NOCHECKPTR in non-race tests because CI uses "make test"
with -race in $TESTFLAGS and we might do that on other occasions.
2020-04-02 11:37:06 -05:00
Matthew Jaffee
44fcbee447
Merge pull request #235 from jaffee/nil-check-indexmeta
add nil check for index meta
2020-04-02 11:33:25 -05:00
Matt Jaffee
35fa26918d
add nil check for index meta 2020-04-02 11:09:07 -05:00
Travis Turner
794bc5b168
Merge pull request #232 from travisturner/decimal-grcp
convert grpc response to use pql.Decimal
2020-04-02 07:55:26 -05:00
Travis
4d985653ae convert grpc response to use pql.Decimal 2020-04-01 23:59:50 -05:00
Travis Turner
0b480f8405
Merge pull request #231 from travisturner/reintroduce-decimal
Reintroduce decimal
2020-04-01 21:44:26 -05:00
Travis
22cca67d6a Revert "back out the pql.Decimal changes"
This reverts commit 741ba9b268.
2020-04-01 17:46:46 -05:00
Matthew Jaffee
e77bf45643
Merge pull request #227 from travisturner/backout-decimal
back out the pql.Decimal changes
2020-04-01 11:32:02 -05:00
Travis
741ba9b268 back out the pql.Decimal changes 2020-04-01 11:10:33 -05:00
Cody Soyland
1284791d22
Merge pull request #224 from codysoyland/ci-fixes
CI fixes: quote TESTFLAGS and fix stable release filter
2020-04-01 09:41:38 -05:00
Cody Soyland
dc5a471939 Downgrade golangci-lint to 1.23.8 (attempt fix for OOM failures) 2020-04-01 09:07:11 -05:00
Cody Soyland
39d83e7b6f Add no_output_timeout for the race detector 2020-04-01 09:07:11 -05:00
Cody Soyland
01033d4fff CI fixes: quote TESTFLAGS and fix stable release filter 2020-04-01 09:07:11 -05:00
Kuba Podgórski
b0f1ee3fce
. (#225) 2020-04-01 15:19:05 +02:00
seebs
c6083d6816
Merge pull request #163 from seebs/distinctshards
Address issues with Distinct failures in testing, or across shards, or in cases where the range of Distinct results is not the same as the range of shards available in any index.
2020-03-31 21:06:16 -05:00
Travis
0374bda45f Adjust bare-distinct logic.
If an index is provided to a bare distinct which happens
to be the index handling the query, then the query needs
to behave as if no index argument was provided.

For example:

When querying against index `i`,
```
Distinct(index="i", field="ints")`
```
should behave exactly like
```
Distinct(field="ints")
```
2020-03-31 19:51:06 -05:00
Seebs
a495b6c227 make Distinct work across nodes, probably
Problem: A top-level bare "Distinct" call returns results only
for shards on the current node.

Analysis: We don't actually want to limit Distinct calls to "available"
shards at all. We just want to run them on everything. But we already
did that in generating the precomputed results; all we need to do is,
if we get a non-shard-specific request for precomputed values, just
return all the values.

It's pretty hard to create logic for this using our fancy mapReduce,
but also we could just... not do that.
2020-03-31 19:51:06 -05:00
seebs
12ba11a437
Merge pull request #215 from seebs/mmap-v-cache
clear container lookup cache when updating every container, handle nils with differenceInPlace, use transaction/ops log for mergeBlock.
2020-03-31 19:49:43 -05:00
Seebs
76e7470559 make mergeBlock use transactions
mergeBlock was bypassing the transaction setup stuff, which means that
if we ran out of open files, mergeBlock wouldn't generate ops log
entries (!), also it didn't update the cache (!). This came up because
it also didn't enjoy the "catch your segfaults and issue a diagnostic"
behavior offered by the generation code.

Switch to computing positions directly and calling importPositions,
which does a transaction.
2020-03-31 16:22:41 -05:00
Seebs
28b9d6d7fc ditch lastKey cache on UpdateEvery
UpdateEvery can change every key, and I think it strongly suggests no
reasonable expectation of repeated access to a previously-accessed key,
but also it can change the containers and replace them.

We were avoiding caching mapped containers in some but not all cases,
and that was causing segfaults. But really, the *problem* is that
the remap operation wasn't clearing (or updating) the cache. Cleaning
that up allows us to take advantage of the caching performance advantage
even when working with read-only/mapped bitmaps.

The only way to hit this:

* Have mmapped containers to begin with.
* Do reads so those containers get frozen.
* Access, either reading or writing, a specific container with key K.
* Snapshot, so the bitmap gets its containers replaced.
* Remember, they have to be frozen -- if they aren't frozen,
  we'll update the containers in place.
* Now have GC run so it actually unmaps the data.
* Now try to write to the container with key K *before reading or
  writing any other key*. You have to get through the whole snapshot
  and GC process without any other reads or writes.
* You get the cached value. You try to use it. You explode.

The sliceContainers code was also setting lastKey to 0 in some cases,
but also setting lastContainer to nil, so this wouldn't have caused
problems, but just to be careful, I've standardized on ^uint64(0)
for everything.
2020-03-31 16:20:05 -05:00
Seebs
1ac00291f3 Add test for the weird remapping/cache interaction.
This test is really a test of a very specific bit of the internals
of containers_btree/containers_slice, but we can't easily test it from
there because they don't have all the logic for remapping files.

The underlying issue is that they maintain a single-item "most recent
container" cache, and this wasn't getting updated during the remap
operations, happening through containers.UpdateEvery. The fix is
probably just to make sure that UpdateEvery invalidates the cache.
2020-03-31 16:20:05 -05:00
Seebs
d26e221a91 don't call isArray on a nil *Container
differenceInPlace wasn't checking for nil containers, which are
theoretically valid empty containers. Also added a couple of other
N==0 checks to streamline the higher-level operation.
2020-03-31 16:20:05 -05:00
Cody Soyland
3b6a26e5c6
Merge pull request #219 from codysoyland/ci-updates
Add updated CircleCI config
2020-03-31 16:18:10 -05:00
Cody Soyland
3a7f385a01 Add updated CircleCI config
- Remove YAML magic
- Remove a lot of duplication
- Update linter
- Use parameterized jobs and matrix build
- Update Docker Hub CD to produce versioned and "latest" images
- Add custom shard width test to workflow
2020-03-31 15:17:11 -05:00
Travis Turner
b5aa280ced
Merge pull request #220 from travisturner/decimal-yaml
yaml marshal/unmarshal for pql.Decimal
2020-03-31 13:53:00 -05:00
Travis
7cb265098f yaml marshal/unmarshal for pql.Decimal 2020-03-31 12:43:42 -05:00
Kuba Podgórski
6dc3837c9a
WIP: fix 'unknown call: Distinct' error (#213) 2020-03-31 16:29:14 +02:00
Travis Turner
dca2120c06
Merge pull request #199 from travisturner/cluster-resize-translation-partitions
include translate partitions in cluster resize instructions
2020-03-30 22:01:09 -05:00
Kuba Podgórski
ac76f6227d
Make internal.IndexMeta.TrackExistence true 2020-03-30 21:24:16 -05:00
Travis
98c5603965
close reader. include all replias in translation partition rebalance 2020-03-30 21:24:16 -05:00
Travis
4c311aa1a7
write to temp partition file. use io.Copy 2020-03-30 21:24:16 -05:00
Travis
2724ecfd5f
WIP: include translate partitions in cluster resize instructions
This commit adds `TranslationSources` to the cluster
`ResizeInstruction`. These are the sources of translation
partitions which the receiving node needs in order to support
partition distribution in the new, resized cluster.

This also fixes a bug where index options were not being
encode in the proto Index object. That meant that the schema
transferred via protobuf was not correct. The reason why
things normally worked is because index creation typically
happens on the CreateIndex message, which does include the
options.

TODO:

- [ ] implement the TranslateStore interface for `InMemTranslateStore`
and `mock.TranslateStore`
- [ ] surely need some more tests around the `ReadFrom` and `WriteTo`
2020-03-30 21:24:16 -05:00
Travis Turner
2ad06f9423
Merge pull request #212 from travisturner/decimal-min-max-args
support pql.Decimal for decimal field min/max arguments
2020-03-30 14:32:02 -05:00
Travis
80da129861 require scale argument for decimal fields 2020-03-30 13:38:50 -05:00
Travis
1da7cf09bb support pql.Decimal for decimal field min/max arguments 2020-03-27 16:02:47 -05:00
Cory LaNou
2a4088e0ec
Merge pull request #206 from corylanou/test-helpers
Make better use of t.Helper
2020-03-25 07:35:35 -05:00
Cory LaNou
391fda9849
Merge branch 'enterprise' into test-helpers 2020-03-24 15:51:01 -05:00
corylanou
f84185230d
make use of t.Helper 2020-03-24 15:00:32 -05:00
Cody Soyland
7ba5c0e3d3
Merge pull request #201 from codysoyland/queryunary
Add QueryPQLUnary gRPC call
2020-03-23 16:58:27 -05:00
Cody Soyland
1aa1ec51aa Add QueryPQLUnary gRPC call 2020-03-23 16:24:31 -05:00
Cory LaNou
69a1365bf0
Merge pull request #202 from corylanou/sum/194
Fixed sum for negative values
2020-03-23 16:22:11 -05:00
corylanou
f32f9f64a4
fix sum for negative values 2020-03-23 14:02:46 -05:00
Travis Turner
532f746f25
Merge pull request #195 from travisturner/sync-mutex-bool
support mutex/bool fields in anti-entropy
2020-03-19 11:47:37 -05:00
Travis Turner
5cfb29beb2
Merge branch 'enterprise' into sync-mutex-bool 2020-03-19 11:28:56 -05:00
Travis Turner
564eee0bdf
Merge pull request #190 from travisturner/decimal-between
serialize decimal between pql for internode queries
2020-03-19 11:26:12 -05:00
Travis
57c30b9dae support mutex/bool fields in anti-entropy 2020-03-19 11:11:33 -05:00
Travis
4d38723fd4 serialize decimal between pql for internode queries 2020-03-19 07:50:43 -05:00
Travis Turner
419c2179b5
Merge pull request #185 from travisturner/decimal-min-max-overflow
Avoid overflow on decimal min/max default values
2020-03-18 08:01:40 -05:00
Travis
298f290e86
Avoid overflow on decimal min/max default values
If the min/max provided are already on the boundary of int64,
then we don't want to operate on them and cause overflow.
there are still overflow scenarios where a user provides a
min/max which is not on the boundary, but overflow once the
scale is applied. This does not address those cases, but at
least it addresses the default case (where a min/max is not
provided)
2020-03-18 07:41:35 -05:00
Kuba Podgórski
73ca124944
Fix runCountRange when range start == interval start (#181)
When the interval is a proper superset of the range with start equal to
interval start, the range must be considered a superset or it will be
completly ignored (since it neither a subset nor it overlaps)

Co-authored-by: Pierre Fersing <pierre.fersing@bleemeo.com>
2020-03-17 20:31:35 +01:00
Travis Turner
ba5b133e1e
Merge pull request #180 from travisturner/mu-anti-entropy
add mutex for anti-entropy and node join/leave
2020-03-17 13:05:53 -05:00
Travis
fbbd474978 add mutex for anti-entropy and node join/leave 2020-03-17 12:46:09 -05:00
Travis Turner
22ae1139d1
Merge pull request #172 from travisturner/float-to-decimal
use pql.Decimal instead of float64
2020-03-16 08:35:19 -05:00
Travis
c60241b5a9 Get rid of Sign from pql.Decimal struct
It turns out that it's not very useful to keep the sign
value as a separate argument in the pql.Decimal struct.
This commit incorporates it into Value, and makes Value
an `int64` (for some bone-headed reason I had made it a
`uint32` before which is just dumb).
2020-03-15 23:00:00 -05:00
Travis
30e08eb532 add error conditions to tests 2020-03-15 16:18:59 -05:00
Travis
513edeae9c fix min/max bug for decimal fields 2020-03-14 22:35:24 -05:00
Travis
963affcc30 WIP: use pql.Decimal instead of float64
This commit introduces a new type: pql.Decimal
We use that instead of float64 in order to ensure
that the string representation is consistent.

One unfortunate discovery during implementation is
that the RowAttrs and ColAttrs support floats, and
the PEG file was treating them as such. So I had
to split the PEG definitions into float-specific
items and decimal-specific items.
2020-03-14 22:33:52 -05:00
Travis Turner
334eb3cd08
Merge pull request #175 from travisturner/int-min-max-with-offset
Fixes the min/max bug for `int` fields with offset.
2020-03-14 15:18:48 -05:00
Travis
3863ab4b41 Fixes the min/max bug for int fields with offset.
Methods `MinForShard` and `MaxForShard` were not adjusting
their return value by the offset.
2020-03-14 14:01:40 -05:00
Travis Turner
0d55d6eab2
Merge pull request #160 from travisturner/int-fragment-sync-better-fix
support fragment sync for int and decimal fields
2020-03-13 18:42:12 -05:00
Travis
a5cba75855 fix typos in comment 2020-03-13 12:15:54 -05:00
Travis
3b676a7cbd fix linter warnings 2020-03-13 12:15:54 -05:00
Travis
cbf80370cb support fragment sync for int and decimal fields
This PR adds support for anti-entropy syncing for integer
and decimal fields. It differs from the logic for other
field types in that it does not rely on a consensus to determine
what the value should be; instead, it considers the correct
values to be those of the primary replica. From there, data
is pushed to all non-primary replicas.
2020-03-13 12:15:54 -05:00
Cody Soyland
8b5848f615
Merge pull request #164 from codysoyland/dockerfile-env-config
Use env variables instead of flags in Dockerfile
2020-03-12 14:44:45 -05:00
Cody Soyland
c6a293f818 Use env variables instead of flags in Dockerfile
This allows you to override those attributes without overriding the
entire command.
2020-03-12 11:22:31 -05:00
Travis Turner
93a804f7ab
Merge pull request #165 from travisturner/range-ast-fix
fix Call stringer for range conditional
2020-03-12 08:29:38 -05:00
Travis
0c6f0e2ba5 fix Call stringer for range conditional 2020-03-11 22:53:37 -05:00
Travis Turner
d76e8fc17c
Merge pull request #162 from travisturner/makefile-helpers
add linter and test-race targets to makefile
2020-03-11 10:10:41 -05:00
Travis
dde1e26baa add linter and test-race targets to makefile 2020-03-11 09:24:00 -05:00
Travis Turner
cdbb274a2b
Merge pull request #157 from travisturner/int-fragment-sync-quick-fix
temporary fix for int field replica sync bug
2020-03-09 15:22:41 -05:00
Travis Turner
f747501473
Merge branch 'enterprise' into int-fragment-sync-quick-fix 2020-03-09 14:56:06 -05:00
Matthew Jaffee
d0aad872e2
Merge pull request #144 from jaffee/min-con-reuse
Min con reuse
2020-03-09 14:54:56 -05:00
Matt Jaffee
f0f86500c7
add minimal fix for connection reuse issue - @tgruben has a more complete fix 2020-03-07 09:46:47 -06:00
Travis
70c3cf1775 include a basic test which covers the temp fix 2020-03-05 21:16:10 -06:00
Travis
86da5c7adf temporary fix for int field replica sync bug 2020-03-05 20:57:16 -06:00
Travis Turner
b99bdb8169
Merge pull request #156 from travisturner/forward-translation-to-coordinator
forward field translation request to coordinator
2020-03-05 17:53:35 -06:00
Travis
3e4f7dd3f3 change translateFieldKeys to variadic function 2020-03-05 15:38:34 -06:00
Travis
d06ffd207f forward field translation request to coordinator 2020-03-05 14:54:23 -06:00
seebs
bbeacbe3c3
Merge pull request #147 from seebs/emptylog
don't fill up empty space with non-functional ops logs
2020-03-04 18:28:55 -06:00
Seebs
eb263b7666 don't fill up empty space with non-functional ops logs
Two changes:

1. Don't write batch/roaring adds or removes when N is 0, because
a write of no bits is not a meaningful write.
2. When unmarshalling roaring things, if a roaring bitmap didn't
change many bits, treat it as having changed at least 1 bit per 8 bytes,
so an 8KB hunk of roaring data counts as 1K changes, which will
nudge us towards snapshotting. This should keep us from having
Large Files show up so much.

This was particularly noticeable on the existence field, which
tends to a steady state of "completely full" very quickly in a lot
of cases.
2020-03-04 18:10:10 -06:00
Kuba Podgórski
53c486fce0
Remove not needed translationSyncer from holder. (#152) 2020-03-05 00:19:58 +01:00
Travis Turner
78327904ff
Merge pull request #145 from travisturner/translate-partition-better-fix
add translationSyncer interface
2020-03-03 19:42:50 -06:00
Travis
842c820366 add translationSyncer interface
This PR adds a translationSyncer interface; I tried to include
comments in the code explaining what's going on. This is taken
from those comments:

translationSyncer provides an interface allowing a function
to notify the server that an action has occurred which requires
the translation sync process to be reset. In general, this
includes anything which modifies schema (add/remove index, etc),
or anything that changes the cluster topology (add/remove node).
I originally considered leveraging the broadcaster since that was
already in place and provides similar event messages, but the
broadcaster is really meant for notifiying other nodes, while
this is more akin to an internal message bus. In fact, I think
a future iteration on this may be to make it more generic so
it can act as an internal message bus where one of the messages
being published is "translationSyncReset".
2020-03-03 14:21:45 -06:00
Travis Turner
92eae8e715
Merge pull request #146 from travisturner/remove-errant-print
remove errant println from test
2020-03-03 12:39:28 -06:00
Travis
7aea54936e remove errant println from test 2020-03-03 11:27:36 -06:00
Travis Turner
13e7679fd9
Merge pull request #138 from travisturner/translate-partition-quick-fix
very crude fix for the translate key read-only bug
2020-02-29 16:18:50 -06:00
Travis
717bd09e97 include the test which covers this scenario 2020-02-29 08:43:10 -06:00
Travis
fb8f612afe very crude fix for the translate key read-only bug
This PR forces the non-coordinator nodes to reset their translation
sync (and therefore their own cosideration of read-only partitions)
any time they receive a `ClusterStatus` message. So basically, as the
cluster grows during the startup process, each node will reset their
translation sync.

This is NOT a good solution log term, but it should address the
immediate problem.

Things to note:
- the coordinator sync isn't getting reset, but that's ok, because the
immediate problem is a partition marked as read-only when it shouldn't
be; i.e. it's ok to have the inverse (a partition not marked as
read-only when it should be) because that partition won't receive
requests anyway.
- the last node to start is already correct and doesn't really need to
reset its sync.
- there are many other scenarios not covered by this fix.

Based on this theory:

```
i have another theory that i’m going to try to test.
this one would only apply in the case where a multi-node cluster is restarted with an existing, keyed index.
- start node0: it thinks it’s responsible for all partitions (nothing is read-only)
- start node1: it thinks it’s responsible for ~1/2 of the partitions and marks the other 1/2 as read-only
- start node2: it thinks it’s responsible for ~1/3 of the partitions and marks the other 2/3 as read-only
now if node0 is the coordinator receiving all translation requests, that still might not explain what’s happening, because in that case it would just do all the translating. i think. but either way, i should make sure that scenario is not happening, but i think it may be.

actually, that might explain it, because what would happen when the coordinator received a translation request, is that it would handle the 1/3 that it owned (now that the cluster is 3 nodes), and it would send the other 2/3 out to the other 2 nodes. but where it sent the requests wouldn’t line up with what the nodes thought they were responsible for based on the restart order
in this example, node 1 would receive requests for the wrong partitions
```
2020-02-29 08:43:05 -06:00
Matthew Jaffee
fcbebcf1b6
Merge pull request #103 from seebs/gencrash
don't mark a source as changed before we've finished remapping
2020-02-21 17:00:19 -06:00
Seebs
d742c67317
avoid race on max count reads and writes 2020-02-21 16:38:36 -06:00
Seebs
ba7db3028b
sanity-check: check whether containers are flagged as mapped before mapping
In the old unmarshal code, the decision to mark a thing as mapped (always
yes) happens separately from setting the mapping. What if this could ever
somehow possibly go wrong? Let's sanity-check that to be extra careful.
2020-02-21 16:38:35 -06:00
Seebs
7841a660a8
make sure setArray isn't copying mapped data addresses by accident in unionInPlace 2020-02-21 16:38:35 -06:00
Seebs
99d865c2ea
ensure that we've unrequested mapping when applying empty storage 2020-02-21 16:38:35 -06:00
Seebs
337e451cc7
lint and review changes
Log an error in the probably-irrelevant case where we ended up with
a file, but Stat failed, which shouldn't ever happen we hope anyway.

Also explicitly discard the status from RemapRoaringStorage in a case
where we don't care.
2020-02-21 16:38:35 -06:00
Seebs
cb686dcad0
make mmap test experiment with different amounts of mapping
This is sort of prototype-ish, but the idea is that we use SetMaxMapCount
from syswrap, which already exists, to let us test edge cases like
"what happens if you only sometimes have mapped data".
2020-02-21 16:38:35 -06:00
Seebs
63fb2f8539
generation testing and paranoia features
We might have a problem with a stale mmap, and to try to narrow it down
a bit, we add some sanity-checking features and panic recovery to the
generation Transaction code.

This is pretty experimental.
2020-02-21 16:38:35 -06:00
Seebs
372389fd30
don't corrupt files when mmap fails
In some cases, after a snapshot, if mmap fails, we could write
a duplicate of the bitmap to the file, creating cryptic "unknown
op type: 60" messages. This doesn't fix those files, but it stops
making them.
2020-02-21 16:38:35 -06:00
Seebs
f9e7fee47d
don't mark a source as changed before we've finished remapping
Also, check the remap operation for errors, and if an error occurs,
try to remap to nil (which shouldn't be able to fail).
2020-02-21 16:38:34 -06:00
Matthew Jaffee
ebab831a43
Merge pull request #113 from molecula/minMaxFloatHandling
min and max should properly scale their output for decimal fields
2020-02-21 16:38:11 -06:00
Matthew Jaffee
8c9b717b05
fix "worhtless" typo in comment 2020-02-21 16:00:31 -06:00
Matt Jaffee
fe46c84d19
also fix Sum query, but don't convert to float until the last step
this avoids compounding floating point errors while summing up the
numbers, and means less logic needs to change. Should probably convert
min and max to use this approach as well, though they don't suffer
from the compounding error issue, it is simpler.
2020-02-21 14:11:30 -06:00
Matt Jaffee
7321f9427c
min and max should properly scale their output for decimal fields
this involved adding an optional float value to the ValCount struct
which complicated result types, necessitated grpc changes, and needed
quite a few tests at different layers.
2020-02-21 14:11:29 -06:00
Matthew Jaffee
d369e3b4bf
Merge pull request #125 from travisturner/time-panic
send nil time value to prevent downstream panic
2020-02-21 14:08:53 -06:00
Travis Turner
52d450cbaf
Merge branch 'enterprise' into time-panic 2020-02-21 10:31:24 -06:00
Travis
13a1b3a2c1 send nil time value to prevent downstream panic 2020-02-18 15:55:40 -06:00
Kuba Podgórski
f3f11f4a44
Let translate keys as empty strings (#120) 2020-02-18 12:54:24 +01:00
Kuba Podgórski
57eb741c24
Don't allow an int and decimal fields to be created with keys=true (#118) 2020-02-14 08:47:31 +01:00
Kuba Podgórski
59f5d4f7d6
Do not clear existence column (#117) 2020-02-13 07:08:45 +01:00
Travis Turner
4713ccd0c8
Merge pull request #110 from travisturner/translatestore-fixes
WIP: Thread OpenTranslateStore through Holder to Index
2020-02-12 11:56:49 -06:00
Ben Johnson
c8cefea897
Fix test performance 2020-02-12 10:25:26 -06:00
Travis
d9ef4c0986
use OpenInMemTranslateStore by default in tests 2020-02-12 10:25:25 -06:00
Travis
4dd530e956
open bolt translate store partitions asynchronously 2020-02-12 10:25:25 -06:00
Travis
49c8bf01a0
WIP: Thread OpenTranslateStore through Holder to Index 2020-02-12 10:25:25 -06:00
Cody Soyland
f9f6fce6b4
Merge pull request #111 from codysoyland/go-1.14-rc
Add Go 1.14-rc to CI
2020-02-10 07:59:48 -06:00
Cody Soyland
88c3477010 Add Go 1.14-rc to CI 2020-02-06 19:46:01 -06:00
Kuba Podgórski
e77c69d212
Update handler.go (#108) 2020-02-05 22:14:25 +01:00
tgruben
fc6fd150ba
Merge pull request #106 from tgruben/bug-105
handle missing index in join properly
2020-02-05 10:29:56 -06:00
Todd Gruben
0acac34fee travis suggetions 2020-02-04 15:21:55 -06:00
Todd Gruben
3140b2d8cb handle missing index in join properly 2020-02-04 11:55:49 -06:00
Kuba Podgórski
3bb45ea2c0
Fix Set operation for float numbers on decimal fields. (#101) 2020-02-03 19:29:15 +01:00
Kuba Podgórski
7c395ac4d1
Simplify Holder's logic for CreateIndex (#104) 2020-02-03 17:41:06 +01:00
Travis Turner
b693688677
Merge pull request #60 from molecula/translation-sharding
Translation sharding
2020-01-31 10:44:51 -06:00
Travis
9b4c7610e6 Merge branch 'enterprise' into translation-sharding 2020-01-31 10:13:44 -06:00
tgruben
eca14d8608
Merge pull request #97 from tgruben/row-difference-in-place
Difference in place at row level
2020-01-30 15:10:23 -06:00
Travis Turner
8626a2a710
Merge pull request #99 from travisturner/translation-sharding
Ensure ForeignIndex key translation happens in API.
2020-01-30 14:42:41 -06:00
tgruben
1be22f13e8
Merge pull request #2 from travisturner/row-difference-in-place
fix differenceInPlace test
2020-01-30 14:31:30 -06:00
Todd Gruben
f498a40d97 go mod tidy 2020-01-30 14:28:44 -06:00
Travis
0bdcb5ab0a fix differenceInPlace test 2020-01-30 13:38:31 -06:00
Travis
61e527251a fix some comments 2020-01-30 10:56:03 -06:00
Todd Gruben
8de32fd62a high level support for difference in place 2020-01-30 08:11:43 -06:00
Travis
e40400b130 Ensure ForeignIndex key translation happens in API.
For Fields with ForeignIndex (which have keys), the API was missing
the logic to do that translation against the translateStore of
the foreign index. This commit adds that logic, as well as some
missing translateStore-related logic in the gRPC code.
2020-01-28 22:46:12 -06:00
tgruben
32b92abd6e
Merge pull request #94 from tgruben/diff-inplace
Adding roaring in place difference support
2020-01-23 08:23:47 -06:00
tgruben
014c3a98a5
Merge pull request #1 from travisturner/diffinplace
tidy comments and remove pilosa dependency
2020-01-22 12:09:50 -06:00
Travis
bb80d1ed2d tidy comments and remove pilosa dependency 2020-01-22 11:59:32 -06:00
Todd Gruben
f40ced47fa included previous patterns for in place testing 2020-01-21 16:56:33 -06:00
Travis Turner
43b8d7827a
Merge pull request #90 from travisturner/translation-sharding
Field.ForeignIndex translation on ImportValue()
2020-01-17 17:25:14 -06:00
Travis
d41ee99052 linter fix 2020-01-17 14:46:43 -06:00
Travis
b620e37e51 move the foreign index key check into applyTranslateStore() 2020-01-17 14:41:41 -06:00
Travis
f001ad199f use translateIndexKeys instead of translateIndexKeySet in ImportValue() 2020-01-17 11:58:15 -06:00
Travis
efffa39c2c check foreign index on field open 2020-01-17 11:42:21 -06:00
Travis
90a2e116a7 update translateResult to translate foreign index keys on SignedRow results 2020-01-17 11:10:42 -06:00
Travis
0ba5b48fca Field.ForeignIndex translation on ImportValue() 2020-01-16 22:23:29 -06:00
Ben Johnson
10ccb6d523 translate foreign index 2020-01-16 13:53:12 -06:00
Ben Johnson
4020f8c73e fix cross-index translation 2020-01-15 14:28:06 -06:00
Travis
54679c12c4 post merge, needs review of TODOs 2020-01-14 22:10:12 -06:00
Travis
df51f07f96 Merge branch 'enterprise' into translation-sharding 2020-01-14 20:05:12 -06:00
Travis Turner
0459285101
Merge pull request #87 from travisturner/grpc-connection-reset
reset grpc connection after TransientFailure
2020-01-14 16:26:02 -06:00
Travis
757df0d284 reset grpc connection after TransientFailure 2020-01-14 15:18:25 -06:00
Travis
f232ec4277 Merge remote-tracking branch 'upstream/enterprise' into enterprise 2020-01-14 15:18:09 -06:00
Matthew Jaffee
12c6cd1c4e
Merge pull request #88 from jaffee/grpc-mutex-string-bug
mutex field data type should be string not []string
2020-01-12 18:27:47 -06:00
Matt Jaffee
ff9ad9a4fe
mutex field data type should be string not []string 2020-01-12 17:38:43 -06:00
tgruben
ba70bf3079
Merge pull request #89 from tgruben/bug-q2-double-delete
bit remove leaves internals corrupt on empty edge case
2020-01-12 16:25:31 -06:00
tgruben
3261ec4dc4
Merge branch 'enterprise' into bug-q2-double-delete 2020-01-12 16:09:12 -06:00
Todd Gruben
99108a1c63 bit remove leaves internals corrupt on empty edge case 2020-01-12 12:00:00 -06:00
Travis
89f6429dac Merge remote-tracking branch 'upstream/enterprise' into enterprise 2020-01-10 15:45:25 -06:00
Travis Turner
9015c00da9
Merge pull request #84 from travisturner/foreign-index
Add FieldOption.ForeignIndex
2020-01-10 15:45:02 -06:00
Travis
a6a2f84bd5 During Holder.Open, apply foreign index after all indexes open
In the case where a field with a foreign index opens before the
foreign index has opened (and is available as a reference in the
holder), push the field into a queue to have its foreign index
applied once all indexes have opened.
2020-01-10 12:28:28 -06:00
Travis
3d3286a9ca fix an issue caused by empty column list defaulting to IDs 2020-01-10 12:28:28 -06:00
Travis
f79fde43e3 exclude internal fields (i.e. _exists) from Inspect output 2020-01-10 12:28:28 -06:00
Travis
742135dc10 Get ForeignIndex string value when reading BSI field.
In the `Inspect` function in `server/grpc.go`, getting
the value of an `int` field with a foreign index to
an index with `Keys()`, we need to return the string
key value instead of the BSI int value for the field.

This commit also changes the method `Field.keys()` to be
exported as `Field.Keys()` so that it's accessible in
the server package.
2020-01-10 12:28:28 -06:00
Travis
35f9dfa374 remove write portion of extension data race 2020-01-10 12:28:28 -06:00
Travis
881d3bef06 Adjust the FieldOption logic to be in place prior to field.Open().
This commit changes the order of FieldOption application so that
it's always set before field.Open() is called.

This was required because field.Open() now uses some of the values
from FieldOptions to determine if/when to use a particular
translateStore. For example, when FieldOptions.ForeignIndex is set,
the translateStore from the foreign index is retrieved during
field.Open().
2020-01-10 12:28:28 -06:00
Matt Jaffee
4d8f307c5e handle string values in ImportValueRequest sorting 2020-01-10 12:28:28 -06:00
Matt Jaffee
132cf7cc1c add StringValues to proto ImportValueRequest, update proto versions
I ran:

brew upgrade protobuf
GO111MODULE=off go get -u github.com/gogo/protobuf/protoc-gen-gofast

I'm not sure if everything is still going to work, but I'm excited to
find out!
2020-01-10 12:28:27 -06:00
Travis
b22d0143e4 loadNewExtensions is unused, but included for completeness 2020-01-10 12:28:27 -06:00
Travis
1542cbefc0 Add FieldOption.ForeignIndex
This allows a BSI field to have an option indicating
that it is a foreign key to another index. If the foreign
index has column keys, then this field handles string values
by using the foreign index's translate store.
2020-01-10 12:28:27 -06:00
Travis
5ca37bf3f3 reset grpc connection after TransientFailure 2020-01-10 11:49:33 -06:00
Matthew Jaffee
7aef0743e1
Merge pull request #86 from jaffee/field-char-limit
allow field and index names up to 230 characters
2020-01-09 14:43:55 -06:00
Matt Jaffee
0a1de81441
allow field and index names up to 230 characters
The lowest limitation I've seen on any filesystem we care about is 255
characters. 230 leaves enough space that an index or field could be
backed up and have a timestamp and file extension appended while
still allowing for much longer index and field names.
2020-01-09 13:02:22 -06:00
Ben Johnson
cda2729935 fix bitdepth race 2020-01-08 09:47:43 -07:00
Ben Johnson
1f6910b0b8 fix group by test 2020-01-08 09:47:43 -07:00
Ben Johnson
a043490996 add multi-shard translation 2020-01-08 09:47:43 -07:00
Ben Johnson
bdfdeb1291 fix lint 2020-01-08 09:47:43 -07:00
Ben Johnson
a189477ba3 rebase & fix test const 2020-01-08 09:47:43 -07:00
Ben Johnson
1409ab5664 fix data race 2020-01-08 09:47:43 -07:00
Ben Johnson
1b068f75a8 Fix inmem read only translation bug 2020-01-08 09:47:43 -07:00
Ben Johnson
2f76283f03 fix replication errors & test races 2020-01-08 09:47:43 -07:00
Ben Johnson
9647d9b4bb fixing additional tests 2020-01-08 09:47:43 -07:00
Ben Johnson
c0a129979e fix tests 2020-01-08 09:47:43 -07:00
Ben Johnson
e3606d6615 fix id generation 2020-01-08 09:47:43 -07:00
Ben Johnson
82910911dd refactoring id partitioning 2020-01-08 09:47:43 -07:00
Ben Johnson
f31d68739e holder syncer translate implementation 2020-01-08 09:47:43 -07:00
Ben Johnson
b3e86e8394 refactoring stores back into index/field 2020-01-08 09:47:43 -07:00
Ben Johnson
7215bfd16c Implement translator store sharding 2020-01-08 09:47:43 -07:00
Travis Turner
cabfa3c456 add a test for pilosa/#2084 2020-01-08 09:47:43 -07:00
tgruben
457789effd
Merge pull request #62 from tgruben/clearvalue
add support to clear value for column
2020-01-07 13:12:21 -06:00
Todd Gruben
643884aeb3 refactored clear to fix q2;removed unused comment 2020-01-07 12:55:32 -06:00
Todd Gruben
26e3460413 removed view creation from ClearValue 2020-01-07 10:55:45 -06:00
Todd Gruben
d843959904 fixed comments; removed create fragment 2020-01-07 10:39:56 -06:00
Todd Gruben
75e017cf4f Merge remote-tracking branch 'upstream/enterprise' into clearvalue 2020-01-07 10:28:47 -06:00
Cody Soyland
29f7448b89
Merge pull request #83 from codysoyland/expvar-compatibility
Initialize expvar lazily to prevent panic if importing both Pilosa v1 and v2
2020-01-02 17:56:42 -06:00
Cody Soyland
8d32005ede Initialize expvar lazily to prevent panic if importing both Pilosa v1 and v2. 2020-01-02 15:43:34 -06:00
Travis Turner
5643afac47
Merge pull request #82 from travisturner/row-response-sorter
WIP: RowResponseSorter for sorting a list of RowResponse based on sort params
2019-12-31 13:18:05 -06:00
Travis
0fffd9a0cb RowResponseSorter for sorting a list of RowResponse based on sort paraters 2019-12-31 11:52:01 -06:00
Travis Turner
c247805d96
Merge pull request #81 from travisturner/inspect-field-output
Remove empty field check in Inspect()
2019-12-27 13:42:36 -06:00
Travis Turner
98df5672e9
Merge branch 'enterprise' into inspect-field-output 2019-12-27 13:26:38 -06:00
Travis Turner
768de9dc3a
Merge pull request #80 from travisturner/row-response-error
Add StatusError to RowResponse for better error handling.
2019-12-27 13:25:38 -06:00
Travis
3be4141382 Return the correct data type label in grpc header
Based on the pilosa field type, return the correct data type
label in the gRPC column header.
2019-12-27 12:43:45 -06:00
Travis
73090e05c8 Add StatusError to RowResponse for better error handling.
This PR adds a `StatusError` to the `pproto.RowResponse` type, which
allows a stream to pass an error on the stream (encoded into
the `RowResponse.StatusError`). This can be checked downstream
for matching `EOF` or `err != nil` and handled appropriately.

This is helpful mainly with the `RowResponse` reducers which run in
goroutines. Instead of trying to manage a separate channel of errors
from those goroutines, we just follow the grpc model and send the
error with the stream.
2019-12-27 12:43:45 -06:00
Travis Turner
94f97297eb
Merge pull request #77 from travisturner/all-shard
Allow All() to be called at the shard level
2019-12-27 12:35:41 -06:00
Travis
d34e38f134 Remove empty field check in Inspect()
The check for field existence is not necessary; since we
add the `_id` field to every response then at the very
least that field will be returned.

This check was preventin a query like `select _id from ...`
from returning any results.
2019-12-26 22:27:46 -06:00
Travis
586a13e942 Allow All() to be called at the shard level 2019-12-20 22:44:46 -06:00
Cody Soyland
7b30b91448
Merge pull request #74 from codysoyland/docker-build-vendor
Vendor modules before building docker image so private modules can be downloaded
2019-12-20 17:52:26 -06:00
Cody Soyland
d4117f3137 Vendor modules before building docker image so private modules can be downloaded 2019-12-20 16:05:18 -06:00
seebs
47316d9f2f
Merge pull request #70 from seebs/unionAA
Simplify unionArrayArray
2019-12-20 13:49:51 -06:00
seebs
d4d3d75e28
Merge branch 'enterprise' into unionAA 2019-12-20 13:28:57 -06:00
Matthew Jaffee
d86a3c3f2f
Merge pull request #55 from seebs/pluginfix
Pluginfix
2019-12-20 12:53:09 -06:00
Matt Jaffee
fe0f57651e
build with distinct by default 2019-12-20 12:21:47 -06:00
Cody Soyland
49b2029656
Run "go mod vendor" outside of Docker so authenticated modules may use system credentials 2019-12-20 12:21:47 -06:00
Seebs
7e1fd8392f
go.mod/go.sum changes for using molecula/ext
This pins us to the initial external release of molecula/ext, which
with any luck will be the only one. (Narrator: It was not to be the
only one.) We also use GOPRIVATE so we don't need a replace directive.
2019-12-20 12:21:47 -06:00
Seebs
0eba050054
stop using pkg/plugin, start using build tags
After a few experiments with pkg/plugin, I'm ready to concede that the
people warning me it was unsuitable for production use were in fact
correct.

In the brave new world, the "ext" package is moved to its own module
outside pilosa. This means that importing it doesn't imply any need to
version-check against pilosa; we can just use versioned copies of the
ext package, which can be public because it doesn't contain anything
we need to care about keeping proprietary.

Then we can, conditional on build tags, import modules from a
neighboring repo which contains the actual implementations, and if
they're imported, their init functions register them.
2019-12-20 12:21:47 -06:00
Seebs
f51c2dbc42
use extensions through build tags 2019-12-20 12:21:47 -06:00
Travis Turner
179fb910f7
Merge pull request #71 from travisturner/all-limit-offset
Add All() support to PQL, including limit and offset
2019-12-18 22:10:42 -06:00
Travis
361e51cb41 Add All() support to PQL, including limit and offset
This PR is meant to get all columns from an index
based on the TrackExistence row.

`All()` is a PQL function that can be used as a typical
row object. Optional arguments are `limit` and `offset`.
2019-12-18 18:00:15 -06:00
Seebs
83aa505673 Simplify unionArrayArray
Also short-circuit it in some cases.
2019-12-17 15:30:56 -06:00
Travis Turner
1af016df84
Merge pull request #68 from travisturner/linter-fix
Fix an impossible code path raised by the linter
2019-12-17 09:04:37 -06:00
Travis
532caa0fbf fix an impossible code path raised by the linter 2019-12-16 21:56:57 -06:00
Travis Turner
6f556fb880
Merge pull request #63 from travisturner/row-field-label
Wrap return types: RowIdentifiers, Pair, and []Pair
2019-12-16 07:42:20 -06:00
Travis
4f7f4f58b1 add field to SignedRow, and implement its grpc response 2019-12-14 15:56:06 -06:00
Travis
3b7b54094a update clustertests to use v2 (and go 1.13) 2019-12-13 18:45:43 -06:00
Travis
5cb37834a0 Wrap return types: RowIdentifiers, Pair, and []Pair
This PR adds a field name (string) to the return types
which represent the values from a specific field. For example,
a TopN query on field `x` would be `TopN(x)` and have results
like:
```
[]Pair{
  {ID: 14, Count: 10},
  {ID: 3, Count: 8},
  {ID: 7, Count: 3},
}
```
In order to know what field this result type refers to, we wrap
`[]Pair` in a new struct called `PairsField` which contains an
addition `Field` string where `x` is stored.

This is useful for informing the gRPC server how to construct
more appropriate headers for the result stream (in this case,
the column headers can now be "x" and "count").

Similar logic was applied to `RowIdentifiers` and `Pair` as well.
2019-12-13 15:36:51 -06:00
Travis Turner
c5aeed0715
Merge pull request #57 from tgruben/fix-minmax-count
return total match counts for either min or max
2019-12-13 15:28:27 -06:00
Todd Gruben
4523a4d693
removed uneeded test run 2019-12-13 15:10:28 -06:00
Todd Gruben
16171b3e65
return total match counts for either min or max 2019-12-13 15:10:28 -06:00
Todd Gruben
95f2864abe applied suggestions by @travisturner 2019-12-11 12:13:57 -06:00
Todd Gruben
7a2d90ade8 add support to clear value for column 2019-12-09 16:28:03 -06:00
Travis Turner
c4e339f72b
Merge pull request #59 from travisturner/not-found-code
Add grpc NotFound code where applicable
2019-12-03 17:44:30 -06:00
Travis
a5652a182c add grpc NotFound code where applicable 2019-12-03 11:53:20 -06:00
Matthew Jaffee
521ea603d0
Merge pull request #52 from molecula/import-col-attrs
Import col attrs
2019-12-02 01:25:08 -06:00
Matt Jaffee
87ee83f4cb
check errors in test to fix lint 2019-12-01 07:33:25 -06:00
Matt Jaffee
9e3029b969
re run generate-protoc 2019-12-01 07:33:24 -06:00
Alan Bernstein
e470b3276e
Add generated proto 2019-12-01 07:33:24 -06:00
Alan Bernstein
cf7d668b49
Support import column attrs in client 2019-12-01 07:33:24 -06:00
Alan Bernstein
c3e8284f6c
Test for presence of column attrs 2019-12-01 07:33:24 -06:00
Alan Bernstein
628def3db7
Clarify some error messages 2019-12-01 07:33:24 -06:00
Alan Bernstein
aba67364b1
Add support for importing column attrs 2019-12-01 07:33:23 -06:00
Travis Turner
76bb3985f0
Merge pull request #53 from travisturner/having-between
Add support for BETWEEN type conditions in the having clause.
2019-11-30 21:58:45 -06:00
Travis
52debbc389 Add support for BETWEEN type conditions in the having clause.
There is a TODO in the `StringWithSubj` method because the value
types really depend on the subject type (for example, `count` uses
uint64, while `sum` uses int64). I'm waiting to address this
until we decide how to handle sums of floats (Decimal), because
that will affect this logic as well.
2019-11-30 11:59:09 -06:00
Travis Turner
0247a9073c
Merge pull request #51 from travisturner/groupby-having
Add "having" support to GroupBy() queries
2019-11-29 23:01:15 -06:00
Travis
24d02c1920 Add "having" support to GroupBy() queries
This PR adds support for a `having` argument in a `GroupBy` query.
Usage looks like this:
```
GroupBy(Rows(a), having=Condition(count > 10))
GroupBy(Rows(a), aggregate=Sum(field=b), having=Condition(sum > 100))
```
2019-11-28 18:40:57 -06:00
Travis Turner
bc0018b67b
Merge pull request #50 from travisturner/datatype-fixes
Add StreamClient and StreamServer interfaces
2019-11-27 17:21:55 -06:00
Travis
3bc0dc28f0 Add StreamClient and StreamServer interfaces
In order to standardize results as streams of RowResponse,
this PR introduces two interfaces `StreamClient` and
`StreamServer`) which mirror the grpc stream interfaces.
Upstream users (sqlmapper, vdsm, etc) can implement
instances of these interfaces to ensure that results can
stream through the entire sytem in an expected way.

This PR also fixes a couple of missing data types.
2019-11-27 16:10:46 -06:00
Matthew Jaffee
210676c927
Merge pull request #39 from molecula/groupby-sum
Groupby sum
2019-11-27 16:04:42 -06:00
Matt Jaffee
114c1a9df8
remove unecessary span from executor tracing 2019-11-27 11:09:06 -06:00
Matt Jaffee
60397d3e8f
add more tracing around groupBy 2019-11-27 11:00:13 -06:00
Matt Jaffee
f2f9ea01dc
get group by aggregates working with PQL validation and grpc streaming 2019-11-27 11:00:13 -06:00
Ben Johnson
ea9914dba0
Add optional GroupBy() 'aggregate' field.
This commit adds an `aggregate` field that allows a `Sum()` call
to be executed for every returned group.
2019-11-27 11:00:13 -06:00
Matt Jaffee
81a4d32cdd
allow IDs to be passed even when keys enabled
This change allows one to query Pilosa fields and indexes directly
with integer row and column ids even when key translation is
enabled. This was previously disallowed during query
translation... I'm not sure why, but it can be quite useful for
debugging and testing to be able to use IDs directly. I have a test in
go-pilosa which uses this functionality.

I also simplified a bunch of the test code which was of the form:
```
else {
    if blah {
    }
}
```

to be:

```
else if blah {
```

which I think is pretty harmless.

I also changed a snapshot log line that has been bugging me to be
Debug level so that it isn't generating lots of useless logs for long
running Pilosa instances.
2019-11-27 08:41:40 -06:00
tgruben
16d5db97a6
Merge pull request #47 from tgruben/inspect-fix
fixed bug in slice container seek
2019-11-26 11:35:51 -06:00
Todd Gruben
ca0247e35b fixed bug in slice container seek 2019-11-26 09:21:52 -06:00
seebs
407c309640
Merge pull request #46 from seebs/distinctfix
Distinctfix
2019-11-22 17:29:24 -06:00
Seebs
d79ecbad86 recompute shards for cross-index calls
It turns out that we need to recompute the set of shards whenever
a query is cross-index. Otherwise we get partial results in unexpected
ways sometimes.
2019-11-22 16:02:47 -06:00
Seebs
36ef82d7ac Zero bitmap storage when reusing it for container-as-bitmap
If you don't do this, it works fine the first time you use a given
storage, but after that you start seeing spurious bits.
2019-11-22 16:02:42 -06:00
Seebs
397d93e84b provide an empty filter when a filter was empty 2019-11-22 16:02:31 -06:00
Seebs
26326ac74c recompute shards for cross-index queries
When computing results on another index, recompute list of
shards for that index.
2019-11-22 16:02:26 -06:00
Seebs
6ad39a376e handle precalls and cross-index queries better
There's two actual changes here, but they're closely related.

First, handle named parameters for precalls, not just indexed parameters.
Second, when doing translation for a call, check whether it specifies an
index, and if it does, use that index instead of the current index for
the translation.
2019-11-22 16:02:20 -06:00
seebs
16c3cfa727
Merge pull request #42 from seebs/sqdeadlock
use the right lock for Enqueue
2019-11-18 21:53:00 -06:00
seebs
01309e4fc5
Merge branch 'enterprise' into sqdeadlock 2019-11-18 20:51:40 -06:00
Cody Soyland
d3c8728821
Merge pull request #19 from codysoyland/ci-updates
Update CI to support Pilosa Enterprise (private dependencies)
2019-11-18 13:52:20 -06:00
Cody Soyland
58182ff563 Pilosa Enterprise private CI 2019-11-18 13:30:32 -06:00
Travis Turner
295adbfe67
Merge pull request #44 from travisturner/cache-threshold-deletes
Fixed ranked cache logic to support reducing cached values below the threshold
2019-11-18 12:10:46 -06:00
Travis
ed82a535e5 Fix ranked cache logic to support reducing cached values below
the threshold.

Prior to this commit, if a cache value was reduced to a value
that fell below the threshold, the operation would be ignored
and the cached value would remain at the old, higher value.

This commit also fixes logic which reduces a cached value within
the framework of uint64 values by subracting the absolute value
of the negative value (since adding a negitive doesn't work with
unsigned integers).
2019-11-18 11:39:50 -06:00
Travis Turner
32f754899e
Merge pull request #43 from travisturner/translate-race-in-test
allow for translate store race in test
2019-11-15 16:33:30 -06:00
Travis
0a69fca657 allow for translate store race in test (by using retry)
In this case, the test is reading from the translateStore
replica before the translateStore replication has had time to
deliver its log to the replica. The only way to truly address
this in the translate store would be to route all key misses
that happen on a read-only replica to the primary translate
store (or somehow know when the primary is done sending to
replicas) for actual verification that the key does not exist.
That's more involved than we want to do here; this PR just
addresses the problem in the test.
2019-11-15 07:57:47 -06:00
Seebs
f204b37760 use atomics instead of locking for stats 2019-11-14 17:40:14 -06:00
Seebs
bab077199c use the right lock for Enqueue
The request for a non-read lock blocks until all existing read
locks exit, meaning that if an Immediate operation is already
going for a fragment, an Enqueue operation will hang forever
holding the fragment's lock, while the Immediate operation has
probably relinquished the fragment's lock to wait for the
queue worker to process it. But the queue worker can't process
it, because the incoming Enqueue still holds the fragment's
lock. Solution: Don't block the Enqueue operation like that.
It shouldn't coexist with things that actually change the sq
channels, like Stop(), but it is fine for it to coexist with
other queue operations.
2019-11-14 17:00:34 -06:00
seebs
9cec40e69d
Merge pull request #41 from seebs/roaringProto
Stop using Roaring in protobuf messages until it's supported elsewhere
2019-11-14 12:45:35 -06:00
Seebs
6fc6cc4350 Stop using Roaring in protobuf messages until it's supported elsewhere
go-pilosa uses protobuf to talk to us but doesn't support the roaring
format. Conveniently, there's a kill switch.
2019-11-14 10:54:40 -06:00
Matthew Jaffee
fa9c911860
Merge pull request #37 from travisturner/rename-bool-label
Rename bool label from changed to result
2019-11-13 14:30:14 -06:00
Travis
e8cd48155a
Rename bool lable from changed to result 2019-11-13 14:12:52 -06:00
Matthew Jaffee
4dfeb89b43
Merge pull request #36 from travisturner/includescolumn-keys
Add column keys support to IncludeColumn
2019-11-13 14:09:24 -06:00
Travis
625125bac6 add column keys support to IncludeColumn 2019-11-13 11:34:21 -06:00
Matthew Jaffee
013ee21621
Merge pull request #28 from molecula/pql-float-values
Pql float values
2019-11-13 11:04:36 -06:00
Matt Jaffee
0e445db7ff
improve error message checking field type in import roaring 2019-11-13 10:07:24 -06:00
Travis
26fc621f09
Support integer predicates in Decimal field range queries. 2019-11-13 10:07:24 -06:00
Matt Jaffee
0ac778516e
check error, make linter happy 2019-11-13 10:07:24 -06:00
Matt Jaffee
14dfc9e31d
fix comment typo for BTWN_LTE_LT 2019-11-13 10:07:24 -06:00
Matt Jaffee
9c8ad727b5
allow floats in PQL queries for decimal fields
had to workaround some cruft in the parser that was trying to only
support a BETWEEN query as LTE, LTE. Now we have operations for all
combinations of LT and LTE.

unrelated - changed the port a test was binding to as it conflicted
with a port I was using locally.
2019-11-13 10:07:24 -06:00
Matt Jaffee
0fec16a141
fix integer bug on less than queries.
this was introduced recently to fix another bug. the comment above it
is correct, just the logic was off-by-one. The test shows the issue
and was confirmed to reproduce it and then fix it.
2019-11-13 10:07:23 -06:00
Travis Turner
7401fd1333
Merge pull request #33 from travisturner/grpc-logger
Fixed gRPC server logger; pass logger through from main
2019-11-13 09:56:04 -06:00
Travis Turner
afd1c004f9
Merge branch 'enterprise' into grpc-logger 2019-11-13 08:31:26 -06:00
Travis Turner
d3432478f9
Merge pull request #34 from travisturner/bool-returns
Fix makeRows in gRPC hander to handle a bool result
2019-11-13 08:24:43 -06:00
Travis
7fd5248d98 makeRows in gRPC hander now handles a bool result
This PR adds bool support to the makeRows function
in the gRPC handler.
2019-11-12 16:13:52 -06:00
seebs
1fea1ea375
Merge pull request #22 from seebs/fsckSnapshotExtension
This is a collection of changes that have been pending forever. It improves the snapshot queue performance, adds some amount of recovery for corrupt filles, reduces memory usage in the rowcache, and adds an extension interface. Yes, they should probably have happened separately over time, things happened.
2019-11-12 14:50:34 -06:00
Seebs
1e0873c70b lock BufferLogger for reads/writes
With the new addition of the holder background scan, it's possible
for an open holder to write log messages at arbitrary times. The
TestHolder_Open/ErrIndexName test checks the contents of the output
buffer, but those contents could be changing if the background task
happens to run at the right time. Use trivial locking around that
so that this shouldn't happen.
2019-11-12 12:15:13 -06:00
Seebs
03f3f424aa don't lint PEG files
I was pretty sure I'd done this, but I guess not: Skip linting
the PEG files.
2019-11-12 12:15:13 -06:00
Seebs
8cc7a176b5 license header fixups
Fix up license headers for the extension code, and add the proto
file to the list of things we don't check license headers for.
2019-11-12 12:15:13 -06:00
Seebs
c5136b14db ensmarten snapshot queue
The snapshot queue needs a bit more subtlety. In some cases,
we really do want to do a snapshot right now -- these shouldn't
have to wait for possibly a hundred or more other snapshots
to complete.

In other cases, we don't really care that much whether we do
a snapshot, and just dropping it is probably fine.

To accommodate this, we distinguish between "urgent" and
"normal" snapshots, and between "Immediate" (does an urgent
snapshot, waits for it) and "Enqueue" (might enqueue a snapshot
but *also might not* if we're already busy). There's a
corresponding "Await" to wait for a snapshot, if one is
pending, but not if one isn't.

We also have a background scan that checks the holder. It will
scan pretty actively when it's finding fragments that need
snapshots (no enqueued snapshot, opN > MaxOpN). It pauses
for a second after every hundred fragments that didn't need
snapshots, and for a minute after each holder scan that didn't
find any. So, if you don't need snapshots, it does basically
nothing, if you do, it'll be moderately aggressive about
submitting tasks -- but it always waits if there's *any*
requested snapshots in the queues.

Updates since initial draft:

Check results from Await more consistently, and in one case, use Immediate
instead and then check its error.

Fix a race condition.  The race condition comes about if:

1. You have a limited enough worker pool that this can happen.
(In testing we tend to have a worker pool of 1.)
2. A fragment is in the normal, non-urgent, queue already.
3. An immediate request comes in for that fragment. This always
happens *with the fragment lock held*.
4. A worker thread grabs that fragment from the queue.
5. The worker thread now waits on the lock. Meanwhile, the
immediate request blocks on sending the fragment to the urgent
queue.
6. The worker can't read the urgent queue, and the immediate
request can't send it, so the immediate request can't proceed.

What's supposed to happen is that the immediate request sends
the thing, and gets into Await(), which sleeps on a condition
variable using the lock, which is to say, releases the lock.

The obvious resolution is to let go of the lock, send the
message, and then reclaim the lock. But then we have the
possibility that the message sent ends up with a timestamp
right after a snapshot that happened *after* the Immediate
request was started. Oops. So we create the request, then let
go of the lock, then send the request, then reclaim the lock
and go into the Await state. All is well.

This is on top of more general use of wait groups, etcetera,
to allow us to ensure that any holder scans terminate *before*
we close the channels they might otherwise be trying to write to.
So, shutdown process is now:

* grab lock on queue (workers and scanners don't use the lock)
* mark snapshotqueue done
* wait for holder scans to complete/exit
* close and nil out all the channels
* release lock

Anything trying to submit to this needs to hold the lock, unless
it's a holder scan, so either it got the lock before we did and already
submitted the thing, or it will get the lock after this and not find
a channel to write to; it's just the holder scanner that has an
ongoing thing that might have started a write to the channel *without*
a lock held, because it's expected that it might have to wait minutes
or hours before the write will complete because it's a background task.

Also, rework the background holder scan to grab lists of
indexes/fields/views/fragments, then scan the grabbed/copied lists,
rather than iterating over maps, allowing us to grab the lock when
we're about to access a thing and let it go when done.

There might be a simpler/cleaner way to do this but opinions on how
safe it is are very mixed, so in the mean time, I'm making the range
behavior not depend at all on there being no writes to the various tiers
of holder/index/view/fragment during the background scans.
2019-11-12 12:15:13 -06:00
Seebs
3b696da34a plugins and precomputed data
So in some cases, when we do a query, the results of one
part of the query are innately shared-across-nodes; for
instance, a hypothetical Distinct query. More generally,
we allow cross-index queries; calls can have "index=foo"
in them.

This patch lets us handle that without duplicating that
query all over. Before we actually start doing the
separate calls, we run the query once from the coordinating
node, then patch the results in, and send relevant subsets
over to each client, etcetera. Also provides slightly
friendlier (and I hope faster) support for converting
bitmaps to/from sets of rows.

We also add an extension interface, and some fancy stuff
to let us define new calls, which use this. They're sort
of tied together because the first extension I wanted to
implement needed precomputed calls. The extension API
lets us create extensions using `pkg/plugin` (with all its
associated limitations, unfortunately), then query them
at load time for functionality.

This also implies some revamping of the argument
validation for PQL, like verifying that functions exist
and knowing things about their argument types.

So basically this is an overly intrusive patch, and would
be better as separate patches, but they're hard to detangle.

add trivial execution-time profiling

What if you could ?profile=true on a query and get some
numbers back? That'd be really cool.

We already have tracing/spans, but right now, those only generate
any data if you have something set up for them to trace to. Add a
fancy wrapper that lets us generate our own tracing data, and dump
it into the request response, if ?profile=true.

add a sample extension, add missing features to extension interface

Implement a naive probabilistic filter extension as an example of
what an extension looks like. In the process, discover multiple
omissions in the bitmap API. Well, I did *say* it was experimental.
2019-11-12 12:14:29 -06:00
Seebs
b25eb8f596 Sources and Generations: tracking mmapped files
This code represents an attempt at providing reliable tracking
of whether any bitmaps still in use have access to a given block
of mmapped data, allowing us to unmap the data when nothing is using
it anymore.

The basic approach is as follows: Each mmap is associated with
a new object, called a "generation". A generation reflects
a particular instance of a given file being mapped. When a
bitmap is built from an mmapped data source, the bitmap is
given a pointer to the generation as its Source. When bitmap
operations combine containers from other bitmaps, they
produce new bitmaps that are tagged with the combined set of
sources.

When we snapshot a file, or for some other reason wish to remap
it, the corresponding bitmap has all its containers updated to
use the new storage, and the bitmap's source is changed. However,
previously-handed-out containers might still have references to the
old storage. Those containers would be in bitmaps with the old
source.

After a bunch of study of trying to reference-count and track
this, I realized: We don't actually need to do that, because we
already have something suitable for determining whether anything
can reach a given object. It's the garbage collector.

So we set a finalizer on the generation object, which handles
unmapping. There's additional sanity-checks here to confirm things
like "we thought this generation should be expiring", and we
track timestamps. We could also have things check whether a
given bitmap's source was marked as obsolete "a while ago", but
that isn't implemented yet.

There's a debug version of this which tracks finalization, creation,
and ending timestamps, and has a call to provide diagnostics for
this. Identical generation IDs get separated out with random
suffixes in this case -- there's sometimes a second or third
instance of the same name due to a holder closing and reopening,
but this basically only happens in testing.

Note that generations are still used even when there's no mmapping,
but unless debugging is turned on, they shouldn't propagate much --
we don't consider a generation to be the source of a bitmap unless
the bitmap actually mapped things from that generation's mmapped
storage, or debugging is on.

There's a couple of other, possibly more subtle, changes and
bug fixes that got caught by the testing on this:
* If a fragment is partially opened and then opening some later
  part fails, we close the earlier parts before returning the
  error so we aren't leaving it partially open.
* Several operations on segments which were requesting that a
  frozen copy of a bitmap be created are now actually *replacing*
  their bitmap with the frozen bitmap, rather than discarding it.
* intersectRunRun, if it decides to create an array or bitmap,
  will yield that container instead of discarding it.

And why all of this? Why, so we can actually implement the thing
where when a fragment has a valid roaring bitmap, but the ops log
is corrupt, we can truncate the corrupt part of the ops log and
reopen it. Which I did.

When the generationdebug build tag is in use, every generation
has a finalizer all the time. When it's not, they only get finalizers
when we expect them to be done -- say, when closing a fragment.
This is because finalizers appear to be possibly-expensive.

There's some logical cleanup to openStorage here, dividing part
of its work into applyStorage and importStorage, which have a common
case for handling "there's no data in this file".
2019-11-12 12:14:29 -06:00
Seebs
6654466033 partially implement truncation of fragments for corrupt ops log
Which is to say don't actually implement it, because openStorage
is too messy right now, but this is the rest of the framework,
and now I'm going to digress into fixing openStorage.
2019-11-12 12:14:29 -06:00
Seebs
538768ea9d handle truncated/damaged .available.shards
The available shards file is just a hint to save us a bit
of time later; we don't need it to run and it can get updated
pretty easily later. If we have problems reading it, we
should just report the error, nuke the file, and continue
without it.
2019-11-12 12:14:29 -06:00
Travis
b8f665db1a pass logger through to the grpc server and handler 2019-11-12 11:08:25 -06:00
seebs
1262cd18e0
Merge pull request #30 from seebs/intfixes
Fix an error that could cause imported values to keep high-order bits from previously imported values, and another that could cause BSI fields to store extra bits they don't need.
2019-11-12 00:02:04 -06:00
seebs
b3adf2d4f6
Merge branch 'enterprise' into q2-11-7 2019-11-11 21:39:45 -06:00
Cody Soyland
5194ede82c
Merge pull request #31 from codysoyland/proto-pkg-name
Change proto package name to "pilosa" to not conflict with molecula
2019-11-11 20:51:52 -06:00
Cody Soyland
69e5e523c1
Merge branch 'enterprise' into proto-pkg-name 2019-11-11 17:26:44 -06:00
Seebs
a804a0dfb1 always treat BSI fields as having at least their depth
If you imported only small values, BSI fields could end up
not bothering to clear higher bits in existing values, which
produced strange behaviors.

We also move the computation of requiredDepth, and the change
to the field, down, combining it with the other checks of the
values for min/max being in range.

Without this, a data set with a ludicrously large value in it
could break a BSI field's depth even though the import would then
reject it.
2019-11-11 16:55:26 -06:00
Travis Turner
1a44f02e3c reset fragment.rowCache after importValue 2019-11-11 16:54:51 -06:00
Travis Turner
10514f7ced
Merge pull request #25 from travisturner/includes-column
Add an IncludesColumn() function to PQL
2019-11-11 12:55:23 -06:00
Cody Soyland
b9335c9f5c Change proto package name to "pilosa" to not conflict with molecula. Upgrade protoc to 3.10.1 2019-11-11 12:24:38 -06:00
Travis
ed37ef5dcf Add an IncludesColumn() function to PQL
Usage:
`IncludesColumn(Intersect(Row(a=1), Row(b=2)), column=10)`

The above query will return a `bool` indicating whether the
intersection of rows a-1 and b-2 contains column 10. Because
a single column is specified, this executes on a single shard
(shard=0 in this example).
2019-11-11 08:17:28 -06:00
Travis Turner
198626e657
Merge pull request #29 from travisturner/linter-fixes
fix golangci-lint complaints
2019-11-11 08:06:35 -06:00
Travis
87b8edc4c5 fix golangci-lint complaints 2019-11-10 17:58:46 -06:00
Travis Turner
2a5d79ad83
Merge pull request #26 from travisturner/proto-licence-exception
add proto/pilosa.pb.go to license.exceptions list
2019-11-10 16:31:00 -06:00
Travis
3129b1c841 add proto/pilosa.pb.go to license.exceptions list 2019-11-10 11:33:29 -06:00
Travis Turner
6632821617
Merge pull request #24 from travisturner/cache-size-none
fix cacheSize when cacheType is none (and cacheSize is 0)
2019-11-08 22:30:17 -06:00
Travis
a842dd521c fix cacheSize when cacheType is none (and cacheSize is 0)
There was an edge case where setting cacheType to none
wouldn't zero out its cacheSize. This fixes that edge case.
2019-11-08 15:58:49 -06:00
Matthew Jaffee
d6f2196bf1
Merge pull request #16 from pilosa/tls-grpc
use TLS settings when setting up GRPC server or client
2019-10-30 19:35:56 -05:00
Matt Jaffee
6f21887259
use TLS settings when setting up GRPC server or client 2019-10-30 17:38:48 -05:00
Matthew Jaffee
468cf98811
Merge pull request #15 from pilosa/decimal-to-grpc
add decimal field support to Inspect
2019-10-30 13:33:55 -05:00
Matt Jaffee
a414cada4f
add decimal field support to Inspect
I tested this manually with BloomRPC and curl, but need to write real
tests. Also need to get floats for decimal fields coming out of QueryPQL.
2019-10-30 11:13:48 -05:00
Matthew Jaffee
8f64a4f585
Merge pull request #11 from pilosa/decimal-support
support for decimal fields
2019-10-29 16:49:20 -05:00
Matt Jaffee
a9a4d244ef
fix a bug in the "less than" logic 2019-10-29 16:36:15 -05:00
Matt Jaffee
5dcabfcc7f
support for decimal fields
This commit adds a Decimal field type which is implemented mostly with
the Int field. It adds an optional "Scale" value to the Int field
which means that the values stored in that field are actually meant to
be divided by 10^Scale before being interpreted.

In order to make use of this functionality, we extend the importValue
request to allow a slice of floats rather than just int64. If the
slice of floats is present, each float in the slice is multiplied by
10^Scale and converted to an int64 before being imported. If a slice
of int64 is imported to a Decimal field, it is treated normally, and
scale is ignored. This allows the conversion to be handled at the
client side if desired.

Currently there are Field level methods for querying Float values out
of a decimal field, but no support in PQL or the executor for getting
float values. Going to wait until I can use the generic result type
before doing that, so for now, any values queried will be the scaled
integer values.

needed to add client support for importing float values, and did this
by adding a more general and simplified client method for value
imports.

rewrote api.ImportValue to use the new method which should be more
performant and efficient.

allow floats to be "pilosa import"ed into decimal fields
2019-10-29 16:36:14 -05:00
seebs
4ef7f7e26b
Merge pull request #12 from seebs/profile
Profiling and a couple of minor fixes
2019-10-29 15:24:51 -05:00
Seebs
616ed39771 Skip longest tests when running -short
The cluster timeout/down tests are way more than half the total
time for "go test", and are very unlikely to be of interest in regular
usage, although they matter for CI. Skip them when doing short
tests.
2019-10-29 15:24:09 -05:00
Seebs
7a381f7eaf allow years other than 2017 in licenses
Also clean up the license hash checking a bit. We trim vendor early
in find so we don't have to walk the whole vendor tree only to grep
the files out, and we don't check the license hashes of the exceptions,
and the exceptions are now a plain text file of non-regex strings
we match exactly. Also the license hash code is only written once.

This will help us a lot if development on Pilosa continues through
2018 or later.
2019-10-29 15:24:09 -05:00
Seebs
820c5ce220 add trivial execution-time profiling
What if you could ?profile=true on a query and get some
numbers back? That'd be really cool.

We already have tracing/spans, but right now, those only generate
any data if you have something set up for them to trace to. Add a
fancy wrapper that lets us generate our own tracing data, and dump
it into the request response, if ?profile=true.

We track wall-clock execution time, plus possible arbitrary K/V
pairs. Memory stats are not included, because obtaining them is
surprisingly expensive.
2019-10-29 15:23:37 -05:00
Travis Turner
9a2f5b3b4c
Merge pull request #10 from travisturner/grpc
initial gRPC server implementation
2019-10-29 14:39:16 -05:00
Travis
a7bb90fcd0 initial gRPC server implementation
add makeRows() tests
register the gRPC server
use api.Index() instead of api.Schema()

support most field types in Inspect() query

currently, there's no support for `time` fields.
those will be dependent upon the output format
and the ability to materialize the timestamp from
the time views.

this commit also changes the response type of the
`Inspect()` query to be a tabular `RowResponse`.
2019-10-29 14:27:07 -05:00
Travis
77a81eb2e1 fix bug preventing a Rows() query on a bool field 2019-10-29 14:27:07 -05:00
348 changed files with 112476 additions and 13807 deletions

View file

@ -1,195 +1,268 @@
version: 2
defaults: &defaults
working_directory: /go/src/github.com/pilosa/pilosa
docker:
- image: circleci/golang:1.13
environment:
GO111MODULE: "on"
fast-checkout: &fast-checkout
attach_workspace:
at: .
jobs:
setup:
<<: *defaults
version: 2.1
executors:
golang:
parameters:
version:
type: string
default: "1.15.8"
resource_class:
type: string
default: medium
docker:
- image: circleci/golang:<< parameters.version >>
resource_class: << parameters.resource_class >>
working_directory: /go/src/github.com/pilosa/pilosa
commands:
add-github-auth:
steps:
- run: git config --global url."https://${GITHUB_USER}:${GITHUB_PERSONAL_ACCESS_TOKEN}@github.com/".insteadOf "https://github.com/"
- run: git config --global url."https://${GITHUB_USER}:${GITHUB_PERSONAL_ACCESS_TOKEN}@github.com/".insteadOf "git@github.com:"
restore-mod-cache:
steps:
- checkout
- restore_cache:
keys:
- mod-cache-{{ checksum "go.sum" }}
- run: "go mod download"
key: mod-cache-{{ checksum "go.sum" }}
save-mod-cache:
steps:
- save_cache:
key: mod-cache-{{ checksum "go.sum" }}
paths:
- /go/pkg/mod/
- persist_to_workspace:
root: .
paths: "*"
check-license-headers:
<<: *defaults
checkout-plus:
steps:
- *fast-checkout
- add-github-auth
- checkout
- restore-mod-cache
jobs:
setup:
executor:
name: golang
steps:
- checkout-plus
- run: go mod download
- save-mod-cache
check-license-headers:
executor:
name: golang
steps:
- checkout-plus
- run: make check-license-headers
linter:
<<: *defaults
executor:
name: golang
steps:
- *fast-checkout
- run: curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.20.0
- run: sudo cp bin/golangci-lint /usr/local/bin/
- checkout-plus
- run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sudo sh -s -- -b /usr/local/bin v1.31.0
- run: make golangci-lint
test-build-arm:
<<: *defaults
go-mod-tidy:
executor:
name: golang
steps:
- *fast-checkout
- checkout-plus
- run: go mod tidy
- run: git diff --exit-code -- go.mod go.sum
check-changelog-label:
executor:
name: golang
steps:
- run: '[[ -n $CIRCLE_PULL_REQUEST ]] || circleci step halt || true' # Skip if this is not a pull request
- run: curl https://$GITHUB_USER:$GITHUB_PERSONAL_ACCESS_TOKEN@api.github.com/repos/molecula/pilosa/pulls/$(basename $CIRCLE_PULL_REQUEST) | jq "[.labels[] | .name | startswith(\"changelog\")] | any" -e
test-build-arm:
executor:
name: golang
steps:
- checkout-plus
- run: make build GOOS=linux GOARCH=arm GOARM=5
- run: make build GOOS=linux GOARCH=arm GOARM=6
- run: make build GOOS=linux GOARCH=arm GOARM=7
- run: make build GOOS=linux GOARCH=arm64
test-golang-1.13: &base-test
<<: *defaults
test:
parameters:
resource_class:
type: string
default: medium
golang_version:
type: string
default: "1.15.8"
shard_width:
type: string
default: "20"
test_make_target:
type: string
default: "test"
test_flags:
type: string
default: ""
goarch:
type: string
default: amd64
executor:
name: golang
version: << parameters.golang_version >>
resource_class: << parameters.resource_class >>
steps:
- *fast-checkout
- run: sudo apt-get install lsof
- run: make test
test-golang-1.13-shard22:
<<: *defaults
steps:
- *fast-checkout
- run: sudo apt-get install lsof
- run: make test SHARD_WIDTH=22
test-golang-1.13-race:
<<: *defaults
steps:
- *fast-checkout
- checkout-plus
- run: sudo apt-get install lsof
- run:
command: make test TESTFLAGS="-race -v -timeout=30m"
command: make << parameters.test_make_target >> SHARD_WIDTH=<< parameters.shard_width >> GOARCH=<< parameters.goarch >>
no_output_timeout: 30m
test-golang-1.13-386:
<<: *base-test
environment:
GO111MODULE: "on"
GOARCH: 386
test-golang-1.13-enterprise:
<<: *defaults
steps:
- *fast-checkout
- run: sudo apt-get install lsof
- run: make test ENTERPRISE=1
test-golang-1.12:
<<: *defaults
docker:
- image: circleci/golang:1.12
steps:
- *fast-checkout
- run: sudo apt-get install lsof
- run: make test
test-golang-1.11:
<<: *defaults
docker:
- image: circleci/golang:1.11
steps:
- *fast-checkout
- run: sudo apt-get install lsof
- run: make test
cluster-tests:
<<: *defaults
executor:
name: golang
steps:
- *fast-checkout
- checkout-plus
- setup_remote_docker
- run: make clustertests-build
prerelease:
<<: *base-test
steps:
- *fast-checkout
- run: make prerelease
- store_artifacts:
path: build
- persist_to_workspace:
root: .
paths: build
release:
<<: *defaults
executor:
name: golang
steps:
- *fast-checkout
- run: make release
- checkout-plus
- attach_workspace:
at: .
- setup_remote_docker:
version: 19.03.13 # see https://support.circleci.com/hc/en-us/articles/360050934711
- run: echo -n $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
- run: make docker-release
- store_artifacts:
path: build
- persist_to_workspace:
root: .
paths: build
prerelease-upload:
docker:
- image: circleci/python:2.7-jessie
publish_release:
executor:
name: golang
steps:
- run: '[[ -v CIRCLE_PR_NUMBER ]] && circleci step halt || true' # Skip job if this is a PR
- *fast-checkout
- run: sudo pip install awscli
- run: make prerelease-upload
dockerhub-upload:
<<: *defaults
- attach_workspace:
at: .
- run: go get github.com/tcnksm/ghr
- run: ghr -t ${GITHUB_PERSONAL_ACCESS_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${CIRCLE_TAG} ./build/
docker-build:
executor:
name: golang
steps:
- run: '[[ -v CIRCLE_PR_NUMBER ]] && circleci step halt || true' # Skip job if this is a PR
- *fast-checkout
- checkout-plus
- setup_remote_docker:
version: 19.03.13 # see https://support.circleci.com/hc/en-us/articles/360050934711
- run: echo -n $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
- run: make docker GO_VERSION=1.15.8
- run: docker run pilosa:$(git describe --tags) help
dockerhub-upload-unstable:
executor:
name: golang
steps:
- checkout-plus
- setup_remote_docker
- run: make docker
- run: docker tag pilosa:$(git describe --tags) pilosa/pilosa:master
- run: docker login -u $DOCKER_USER -p $DOCKER_PASS
- run: docker push pilosa/pilosa:master
- run: docker run pilosa:$(git describe --tags) help
- run: echo -n $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
- run: make docker-tag-push DOCKER_TARGET=moleculacorp/pilosa:<< pipeline.git.branch >>
dockerhub-upload-stable:
executor:
name: golang
steps:
- checkout-plus
- setup_remote_docker
- run: make docker
- run: docker run pilosa:$(git describe --tags) help
- run: echo -n $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
- run: make docker-tag-push DOCKER_TARGET=moleculacorp/pilosa:<< pipeline.git.tag >>
- run: make docker-tag-push DOCKER_TARGET=moleculacorp/pilosa:latest
workflows:
version: 2
test:
build:
jobs:
- setup
- setup:
context: molecula
filters:
tags:
only: /^v.*/
- linter:
context: molecula
requires:
- setup
- check-license-headers:
context: molecula
requires:
- setup
- go-mod-tidy:
context: molecula
requires:
- setup
- check-changelog-label:
context: molecula
requires:
- setup
- test-build-arm:
context: molecula
requires:
- setup
- test-golang-1.13-enterprise:
- test:
name: test-golang-<< matrix.golang_version >>
resource_class: large
context: molecula
requires:
- setup
- test-golang-1.13-race:
matrix:
parameters:
golang_version: ["1.14.15", "1.15.8"]
- test:
name: << matrix.test_make_target >>
resource_class: xlarge
context: molecula
requires:
- setup
- test-golang-1.13-386:
requires:
- setup
- test-golang-1.13:
requires:
- setup
- test-golang-1.12:
requires:
- setup
- test-golang-1.11:
matrix:
parameters:
test_make_target: ["test-race", "test-txstore-rbf", "test-txstore-rbf_bolt"]
- test:
name: test-shardwidth-22
context: molecula
shard_width: "22"
resource_class: large
requires:
- setup
- cluster-tests:
context: molecula
requires:
- setup
- prerelease:
- docker-build:
context: molecula
requires:
- linter
- check-license-headers
- test-golang-1.13
- setup
- release:
context: molecula
requires:
- linter
- check-license-headers
- test-golang-1.13
- setup
filters:
tags:
only: /^v.*/
branches:
ignore: /.*/
- prerelease-upload:
- publish_release:
context: molecula
requires:
- prerelease
- dockerhub-upload:
- release
filters:
tags:
only: /^v.*/
branches:
ignore: /.*/
- dockerhub-upload-unstable:
context: molecula
requires:
- linter
- check-license-headers
- test-golang-1.13
- setup
filters:
branches:
only: master
- dockerhub-upload-stable:
context: molecula
requires:
- setup
filters:
tags:
only: /^v.*/
branches:
ignore: /.*/

5
.dockerignore Normal file
View file

@ -0,0 +1,5 @@
lattice/.git
lattice/node_modules
lattice/build
statik/statik.go
build

6
.gitignore vendored
View file

@ -4,3 +4,9 @@ vendor
.protoc-gen-gofast
.DS_Store
build
*~
release-pilosa-fsck.*.*.tar.gz
/log.*
/tourna.log.*
pilosa
*.dot

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "lattice"]
path = lattice
url = git@github.com:molecula/lattice.git

3
.golangci.yml Normal file
View file

@ -0,0 +1,3 @@
run:
skip-files:
- pql/pql.peg.go

View file

@ -1,16 +1,46 @@
FROM golang:1.13.0 as builder
ARG GO_VERSION=latest
COPY . pilosa
#######################
### Lattice builder ###
#######################
RUN cd pilosa && CGO_ENABLED=0 make install FLAGS="-a"
FROM moleculacorp/nodejs:latest as lattice-builder
WORKDIR /lattice
FROM alpine:3.9.4
COPY lattice/package.json ./
COPY lattice/yarn.lock ./
RUN yarn install
LABEL maintainer "dev@pilosa.com"
COPY lattice ./
RUN yarn build
######################
### Pilosa builder ###
######################
FROM golang:${GO_VERSION} as pilosa-builder
ARG MAKE_FLAGS
WORKDIR /pilosa
RUN go get github.com/rakyll/statik
COPY . ./
COPY --from=lattice-builder /lattice/build /lattice
RUN /go/bin/statik -src=/lattice -dest=/pilosa
RUN make build ${MAKE_FLAGS}
#####################
### Pilosa runner ###
#####################
FROM alpine:3.13.2 as runner
LABEL maintainer "dev@molecula.com"
RUN apk add --no-cache curl jq
COPY --from=builder /go/bin/pilosa /pilosa
COPY --from=pilosa-builder /pilosa/build/pilosa /
COPY LICENSE /LICENSE
COPY NOTICE /NOTICE
@ -18,5 +48,9 @@ COPY NOTICE /NOTICE
EXPOSE 10101
VOLUME /data
ENV PILOSA_DATA_DIR /data
ENV PILOSA_BIND 0.0.0.0:10101
ENV PILOSA_BIND_GRPC 0.0.0.0:20101
ENTRYPOINT ["/pilosa"]
CMD ["server", "--data-dir", "/data", "--bind", "http://0.0.0.0:10101"]
CMD ["server"]

View file

@ -1,17 +1,14 @@
# This Dockerfile is used for cluster testing - it produces a much larger image
# and includes all of Go as well as some utilities.
FROM golang:1.11
FROM golang:1.13
LABEL maintainer "dev@pilosa.com"
COPY . /go/src/github.com/pilosa/pilosa/
RUN cd /go/src/github.com/pilosa/pilosa \
&& GO111MODULE=on make vendor
RUN cd /go/src/github.com/pilosa/pilosa \
&& CGO_ENABLED=0 make install FLAGS="-a"
&& make install FLAGS="-a -mod=vendor"
# download pumba for fault injection
ADD https://github.com/alexei-led/pumba/releases/download/0.6.0/pumba_linux_amd64 /pumba

263
Makefile
View file

@ -1,23 +1,40 @@
.PHONY: build check-clean clean cover cover-viz default docker docker-build docker-test generate generate-protoc generate-pql gometalinter install install-build-deps install-golangci-lint install-gometalinter install-protoc install-protoc-gen-gofast install-peg prerelease prerelease-upload release release-build test
.PHONY: build check-clean clean build-lattice cover cover-viz default docker docker-build docker-test docker-tag-push generate generate-protoc generate-pql generate-statik gometalinter install install-build-deps install-golangci-lint install-gometalinter install-protoc install-protoc-gen-gofast install-peg install-statik release release-build test testv testv-race testvsub testvsub-race test-txstore-rbf lattice
CLONE_URL=github.com/pilosa/pilosa
MOD_VERSION=v2
VERSION := $(shell git describe --tags 2> /dev/null || echo unknown)
VERSION_ID = $(if $(ENTERPRISE_ENABLED),enterprise-)$(VERSION)-$(GOOS)-$(GOARCH)
BRANCH := $(if $(TRAVIS_BRANCH),$(TRAVIS_BRANCH),$(if $(CIRCLE_BRANCH),$(CIRCLE_BRANCH),$(shell git rev-parse --abbrev-ref HEAD)))
LATTICE_COMMIT := $(shell git -C lattice rev-parse --short HEAD 2>/dev/null)
VARIANT = Molecula
GO=go
GOOS=$(shell $(GO) env GOOS)
GOARCH=$(shell $(GO) env GOARCH)
BINOUT=build
FLAGS=-o $(BINOUT)/pilosa
VERSION_ID=$(if $(TRIAL_DEADLINE),trial-$(TRIAL_DEADLINE)-,)$(VERSION)-$(GOOS)-$(GOARCH)
BRANCH := $(if $(CIRCLE_BRANCH),$(CIRCLE_BRANCH),$(shell git rev-parse --abbrev-ref HEAD))
BRANCH_ID := $(BRANCH)-$(GOOS)-$(GOARCH)
BUILD_TIME := $(shell date -u +%FT%T%z)
SHARD_WIDTH = 20
LDFLAGS="-X github.com/pilosa/pilosa/v2.Version=$(VERSION) -X github.com/pilosa/pilosa/v2.BuildTime=$(BUILD_TIME) -X github.com/pilosa/pilosa/v2.Enterprise=$(if $(ENTERPRISE_ENABLED),1)"
GO_VERSION=latest
ENTERPRISE ?= 0
ENTERPRISE_ENABLED = $(subst 0,,$(ENTERPRISE))
RELEASE ?= 0
RELEASE_ENABLED = $(subst 0,,$(RELEASE))
BUILD_TAGS += $(if $(ENTERPRISE_ENABLED),enterprise)
BUILD_TAGS += $(if $(RELEASE_ENABLED),release)
COMMIT := $(shell git describe --exact-match >/dev/null 2>&1 || git rev-parse --short HEAD)
LDFLAGS="-X github.com/pilosa/pilosa/v2.Version=$(VERSION) -X github.com/pilosa/pilosa/v2.BuildTime=$(BUILD_TIME) -X github.com/pilosa/pilosa/v2.Variant=$(VARIANT) -X github.com/pilosa/pilosa/v2.Commit=$(COMMIT) -X github.com/pilosa/pilosa/v2.LatticeCommit=$(LATTICE_COMMIT) -X github.com/pilosa/pilosa/v2.TrialDeadline=$(TRIAL_DEADLINE)"
GO_VERSION=1.15.8
DOCKER_BUILD= # set to 1 to use `docker-build` instead of `build` when creating a release
BUILD_TAGS += shardwidth$(SHARD_WIDTH)
LICENSE_HASH=$(shell head -13 pilosa.go | shasum | cut -f 1 -d " ")
TEST_TAGS = roaringparanoia
define LICENSE_HASH_CODE
head -13 $1 | sed -e 's/Copyright 20[0-9][0-9]/Copyright 20XX/g' | shasum | cut -f 1 -d " "
endef
LICENSE_HASH=$(shell $(call LICENSE_HASH_CODE, pilosa.go))
UNAME := $(shell uname -s)
ifeq ($(UNAME), Darwin)
IS_MACOS:=1
else
IS_MACOS:=0
endif
export GO111MODULE=on
export GOPRIVATE=github.com/molecula
export CGO_ENABLED=0
# Run tests and compile Pilosa
default: test build
@ -28,14 +45,48 @@ clean:
# Set up vendor directory using `go mod vendor`
vendor: go.mod
go mod vendor
$(GO) mod vendor
# Run test suite
test:
go test ./... -tags='$(BUILD_TAGS)' $(TESTFLAGS)
$(GO) test ./... -tags='$(BUILD_TAGS) $(TEST_TAGS)' $(TESTFLAGS) -v
# Run test suite with race flag
test-race:
CGO_ENABLED=1 $(GO) test ./... -tags='$(BUILD_TAGS) $(TEST_TAGS)' $(TESTFLAGS) -race -timeout 60m -v
testv: topt testvsub
testv-race: topt-race testvsub-race
# testvsub: run go test -v in sub-directories in "local mode" with incremental output,
# avoiding go -test ./... "package list mode" which doesn't give output
# until the test run finishes. Package list mode makes it hard to
# find which test is hung/deadlocked.
#
testvsub:
set -e; for i in boltdb ctl http pg pql rbf roaring server sql txkey; do \
echo; echo "___ testing subpkg $$i"; \
cd $$i; pwd; \
$(GO) test -tags='$(BUILD_TAGS) $(TEST_TAGS)' $(TESTFLAGS) -v -timeout 60m || break; \
echo; echo "999 done testing subpkg $$i"; \
cd ..; \
done
testvsub-race:
set -e; for i in boltdb ctl http pg pql rbf roaring server sql txkey; do \
echo; echo "___ testing subpkg $$i -race"; \
cd $$i; pwd; \
CGO_ENABLED=1 $(GO) test -tags='$(BUILD_TAGS) $(TEST_TAGS)' $(TESTFLAGS) -v -race -timeout 60m || break; \
echo; echo "999 done testing subpkg $$i -race"; \
cd ..; \
done
tour:
./tournament.sh
bench:
go test ./... -bench=. -run=NoneZ -timeout=127m $(TESTFLAGS)
$(GO) test ./... -bench=. -run=NoneZ -timeout=127m $(TESTFLAGS)
# Run test suite with coverage enabled
cover:
@ -44,17 +95,16 @@ cover:
# Run test suite with coverage enabled and view coverage results in browser
cover-viz: cover
go tool cover -html=build/coverage.out
$(GO) tool cover -html=build/coverage.out
# Compile Pilosa
build:
go build -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/pilosa
$(GO) build -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/pilosa
# Create a single release build under the build directory
release-build:
$(MAKE) $(if $(DOCKER_BUILD),docker-)build FLAGS="-o build/pilosa-$(VERSION_ID)/pilosa" RELEASE=1
cp NOTICE README.md build/pilosa-$(VERSION_ID)
$(if $(ENTERPRISE_ENABLED),cp enterprise/COPYING build/pilosa-$(VERSION_ID),cp LICENSE build/pilosa-$(VERSION_ID))
$(MAKE) $(if $(DOCKER_BUILD),docker-)build FLAGS="-o build/pilosa-$(VERSION_ID)/pilosa"
cp NOTICE README.md LICENSE build/pilosa$(VERSION_ID)
tar -cvz -C build -f build/pilosa-$(VERSION_ID).tar.gz pilosa-$(VERSION_ID)/
@echo Created release build: build/pilosa-$(VERSION_ID).tar.gz
@ -64,15 +114,16 @@ ifndef SKIP_CHECK_CLEAN
$(if $(shell git status --porcelain),$(error Git status is not clean! Please commit or checkout/reset changes.))
endif
# Create release build tarballs for all supported platforms. Linux compilation happens under Docker.
release: check-clean
# Create release build tarballs for all supported platforms. DEPRECATED: Use `docker-release`
release: check-clean generate-statik-docker
$(MAKE) release-build GOOS=darwin GOARCH=amd64
$(MAKE) release-build GOOS=darwin GOARCH=amd64 ENTERPRISE=1
$(MAKE) release-build GOOS=linux GOARCH=amd64
$(MAKE) release-build GOOS=linux GOARCH=amd64 ENTERPRISE=1
$(MAKE) release-build GOOS=linux GOARCH=386
$(MAKE) release-build GOOS=linux GOARCH=386 ENTERPRISE=1
# Create release build tarballs for all supported platforms. Same as `release`, but without embedded Lattice UI.
release-sans-ui: check-clean
rm -f statik/statik.go
$(MAKE) release-build GOOS=darwin GOARCH=amd64
$(MAKE) release-build GOOS=linux GOARCH=amd64
# try (e.g.) internal/clustertests/docker-compose-replication2.yml
DOCKER_COMPOSE=internal/clustertests/docker-compose.yml
@ -81,59 +132,143 @@ DOCKER_COMPOSE=internal/clustertests/docker-compose.yml
# running. This will catch changes to internal/clustertests/*.go, but if you
# make changes to Pilosa, you'll want to run clustertests-build to rebuild the
# pilosa image.
clustertests:
clustertests: vendor
docker-compose -f $(DOCKER_COMPOSE) down
docker-compose -f $(DOCKER_COMPOSE) build client1
docker-compose -f $(DOCKER_COMPOSE) up --exit-code-from=client1
# Like clustertests, but rebuilds all images.
clustertests-build:
docker-compose -f $(DOCKER_COMPOSE) down
clustertests-build: vendor
docker-compose -f $(DOCKER_COMPOSE) down -v
docker-compose -f $(DOCKER_COMPOSE) up --exit-code-from=client1 --build
# Create prerelease builds
prerelease:
$(MAKE) release-build GOOS=linux GOARCH=amd64 VERSION_ID=$$\(BRANCH_ID\)
$(if $(shell git describe --tags --exact-match HEAD),$(MAKE) release)
prerelease-upload:
aws s3 sync build/ s3://build.pilosa.com/ --exclude "*" --include "*.tar.gz" --acl public-read
# Install Pilosa
install:
go install -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/pilosa
$(GO) install -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/pilosa
install-bench:
$(GO) install -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/pilosa-bench
# Ensure lattice is cloned and the pinned version is checked out
lattice:
git submodule update --init
# Build the lattice assets
build-lattice: lattice
docker build -t lattice:build ./lattice
export LATTICE=`docker create lattice:build`; docker cp $$LATTICE:/lattice/. ./lattice/build && docker rm $$LATTICE
# Upgrade lattice to the latest version
upgrade-lattice: lattice
git submodule update --remote
# `go generate` protocol buffers
generate-protoc: require-protoc require-protoc-gen-gofast
go generate github.com/pilosa/pilosa/v2/internal
$(GO) generate github.com/pilosa/pilosa/v2/internal
# `go generate` statik assets (lattice UI)
generate-statik: build-lattice require-statik
$(GO) generate github.com/pilosa/pilosa/v2/statik
# `go generate` statik assets (lattice UI) in Docker
generate-statik-docker: build-lattice
docker run --rm -t -v $(PWD):/pilosa golang:1.15.8 sh -c "go get github.com/rakyll/statik && /go/bin/statik -src=/pilosa/lattice/build -dest=/pilosa -f"
# `go generate` stringers
generate-stringer:
go generate github.com/pilosa/pilosa/v2
$(GO) generate github.com/pilosa/pilosa/v2
generate-pql: require-peg
cd pql && peg -inline pql.peg && cd ..
generate-proto-grpc: require-protoc require-protoc-gen-go
protoc -I proto proto/pilosa.proto --go_out=plugins=grpc:proto
protoc -I proto proto/vdsm/vdsm.proto --go_out=plugins=grpc:proto
# TODO: Modify above commands and remove the below mv if possible.
# See https://go-review.googlesource.com/c/protobuf/+/219298/ for info on --go-opt
# I couldn't get it to work during development - Cody
cp -r proto/github.com/pilosa/pilosa/v2/proto/ proto/
rm -rf proto/github.com
# `go generate` all needed packages
generate: generate-protoc generate-stringer generate-pql
generate: generate-protoc generate-statik generate-stringer generate-pql
# Create release using Docker
docker-release:
$(MAKE) docker-build GOOS=linux GOARCH=amd64
$(MAKE) docker-build GOOS=darwin GOARCH=amd64
# Build a release in Docker
docker-build: vendor lattice
docker build \
--build-arg GO_VERSION=$(GO_VERSION) \
--build-arg MAKE_FLAGS="TRIAL_DEADLINE=$(TRIAL_DEADLINE) GOOS=$(GOOS) GOARCH=$(GOARCH)" \
--target pilosa-builder \
--tag pilosa:build .
docker create --name pilosa-build pilosa:build
mkdir -p build/pilosa-$(VERSION_ID)
docker cp pilosa-build:/pilosa/build/. ./build/pilosa-$(VERSION_ID)
cp NOTICE LICENSE ./build/pilosa-$(VERSION_ID)
docker rm pilosa-build
tar -cvz -C build -f build/pilosa-$(VERSION_ID).tar.gz pilosa-$(VERSION_ID)/
# Create Docker image from Dockerfile
docker:
docker build -t "pilosa:$(VERSION)" .
docker-image: vendor lattice
docker build \
--build-arg GO_VERSION=$(GO_VERSION) \
--build-arg MAKE_FLAGS="TRIAL_DEADLINE=$(TRIAL_DEADLINE)" \
--tag pilosa:$(VERSION) .
@echo Created docker image: pilosa:$(VERSION)
# Compile Pilosa inside Docker container
docker-build:
docker run --rm -v $(PWD):/go/src/$(CLONE_URL) -w /go/src/$(CLONE_URL) -e GOOS=$(GOOS) -e GOARCH=$(GOARCH) golang:$(GO_VERSION) go build -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) $(CLONE_URL)/cmd/pilosa
# Create docker image (alias)
docker: docker-image # alias
# Tag and push a Docker image
docker-tag-push: vendor
docker tag "pilosa:$(VERSION)" $(DOCKER_TARGET)
docker push $(DOCKER_TARGET)
@echo Pushed docker image: $(DOCKER_TARGET)
# Install diagnostic pilosa-keydump tool. Allows viewing the keys in a transaction-engine directory.
pilosa-keydump:
$(GO) install -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/pilosa-keydump
# Install diagnostic pilosa-chk tool for string translations and fragment checksums.
pilosa-chk:
$(GO) install -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) ./cmd/pilosa-chk
pilosa-fsck:
cd ./cmd/pilosa-fsck && make install && make release
# Run Pilosa tests inside Docker container
docker-test:
docker run --rm -v $(PWD):/go/src/$(CLONE_URL) -w /go/src/$(CLONE_URL) golang:$(GO_VERSION) go test -tags='$(BUILD_TAGS)' $(TESTFLAGS) ./...
docker run --rm -v $(PWD):/go/src/$(CLONE_URL) -w /go/src/$(CLONE_URL) golang:$(GO_VERSION) go test -tags='$(BUILD_TAGS) $(TEST_TAGS)' $(TESTFLAGS) ./...
# Must use bash in order to -o pipefail; otherwise the tee will hide red tests.
# run top tests, not subdirs. print summary red/green after.
# The \-\-\- FAIL avoids counting the extra two FAIL strings at then bottom of log.topt.
topt:
mv log.topt.roar log.topt.roar.prev || true
$(eval SHELL:=/bin/bash) set -o pipefail; $(GO) test -v -tags='$(BUILD_TAGS) $(TEST_TAGS)' $(TESTFLAGS) 2>&1 | tee log.topt.roar
@echo " log.topt.roar green: \c"; cat log.topt.roar | grep PASS |wc -l
@echo " log.topt.roar red: \c"; cat log.topt.roar | grep '\-\-\- FAIL' | wc -l
topt-race:
mv log.topt.race log.topt.race.prev || true
$(eval SHELL:=/bin/bash) set -o pipefail; CGO_ENABLED=1 $(GO) test -race -v -tags='$(BUILD_TAGS) $(TEST_TAGS)' $(TESTFLAGS) 2>&1 | tee log.topt.race
@echo " log.topt.race green: \c"; cat log.topt.race | grep PASS |wc -l
@echo " log.topt.race red: \c"; cat log.topt.race | grep '\-\-\- FAIL' | wc -l
# Run golangci-lint
golangci-lint: require-golangci-lint
golangci-lint run
golangci-lint run --timeout 3m --skip-files '.*\.peg\.go'
# Alias
linter: golangci-lint
# Better alias
ocd: golangci-lint
# Run gometalinter with custom flags
gometalinter: require-gometalinter vendor
@ -161,9 +296,8 @@ gometalinter: require-gometalinter vendor
# Verify that all Go files have license header
check-license-headers: SHELL:=/bin/bash
check-license-headers:
@! find . -name '*.go' | grep -v '^./vendor' | while read fn;\
do [[ `head -13 $$fn | shasum | cut -f 1 -d " "` == $(LICENSE_HASH) ]] || echo $$fn; done | \
grep -v apimethod_string.go | grep -v pb.go | grep -v peg.go | grep -v lru.go | grep -v btree | grep -v enterprise
@! find . -path ./vendor -prune -o -name '*.go' -print | grep -v -F -f license.exceptions | while read fn;\
do [[ `$(call LICENSE_HASH_CODE, $$fn)` == $(LICENSE_HASH) ]] || echo $$fn; done | grep '.'
######################
# Build dependencies #
@ -175,24 +309,37 @@ require-%:
$(info Verified build dependency "$*" is installed.),\
$(error Build dependency "$*" not installed. To install, try `make install-$*`))
install-build-deps: install-protoc-gen-gofast install-protoc install-stringer install-peg
install-build-deps: install-protoc-gen-gofast install-protoc install-statik install-stringer install-peg
install-statik:
go get -u github.com/rakyll/statik
install-stringer:
GO111MODULE=off go get -u golang.org/x/tools/cmd/stringer
GO111MODULE=off $(GO) get -u golang.org/x/tools/cmd/stringer
install-protoc-gen-gofast:
GO111MODULE=off go get -u github.com/gogo/protobuf/protoc-gen-gofast
GO111MODULE=off $(GO) get -u github.com/gogo/protobuf/protoc-gen-gofast
install-protoc-gen-go:
GO111MODULE=off $(GO) get -u github.com/golang/protobuf/protoc-gen-go
install-protoc:
@echo This tool cannot automatically install protoc. Please download and install protoc from https://google.github.io/proto-lens/installing-protoc.html
install-peg:
GO111MODULE=off go get github.com/pointlander/peg
GO111MODULE=off $(GO) get github.com/pointlander/peg
install-golangci-lint:
GO111MODULE=off go get github.com/golangci/golangci-lint/cmd/golangci-lint
GO111MODULE=off $(GO) get github.com/golangci/golangci-lint/cmd/golangci-lint
install-gometalinter:
GO111MODULE=off go get -u github.com/alecthomas/gometalinter
GO111MODULE=off $(GO) get -u github.com/alecthomas/gometalinter
GO111MODULE=off gometalinter --install
GO111MODULE=off go get github.com/remyoudompheng/go-misc/deadcode
GO111MODULE=off $(GO) get github.com/remyoudompheng/go-misc/deadcode
test-txstore-rbf:
PILOSA_TXSRC=rbf $(MAKE) testv-race
test-txstore-rbf_bolt:
PILOSA_TXSRC=rbf_bolt $(MAKE) testv-race

45
NOTICE
View file

@ -14,27 +14,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Enterprise Edition software license
===================================
Files contained under the directory `enterprise` are subject to the following
license notice (Full license included in the file `COPYING`):
Copyright (C) 2018 Pilosa Corp. All rights reserved.
Pilosa Enterprise Edition is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Pilosa Enterprise Edition is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Pilosa Enterprise Edition. If not, see <http://www.gnu.org/licenses/>.
Third-party software licenses
=============================
@ -115,3 +94,27 @@ The file /server/tlsconfig.go contains a modified redistribution of bridge
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The files /logger/filewriter.go and /logger/filewriter_test.go contain a modified redistribution of reopen (github.com/client9/reopen); the license follows:
The MIT License (MIT)
Copyright (c) 2015 Nick Galbreath
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -31,6 +31,8 @@ See our [Documentation](https://www.pilosa.com/docs/) for information about inst
1. [Install Pilosa](https://www.pilosa.com/docs/installation/).
Optionally, to include Lattice, the in-browser UI, follow the "Build from source" instructions, and run `make generate-statik` before `make install`. When you run a local Pilosa server on the default host, for example, you can access Lattice at [localhost:10101](http://localhost:10101).
2. [Start Pilosa](https://www.pilosa.com/docs/getting-started/#starting-pilosa) with the default configuration:
```shell
@ -40,7 +42,7 @@ See our [Documentation](https://www.pilosa.com/docs/) for information about inst
and verify that it's running:
```shell
curl localhost:10101/nodes
curl localhost:10101/status
```
3. Follow along with the [Sample Project](https://www.pilosa.com/docs/getting-started/#sample-project) to get a better understanding of Pilosa's capabilities.
@ -56,6 +58,15 @@ Check out how the Pilosa [Data Model](https://www.pilosa.com/docs/data-model/) w
You can interact with Pilosa directly in the console using the [Pilosa Query Language](https://www.pilosa.com/docs/query-language/) (PQL).
## Upgrading UI
Lattice is now a submodule of Pilosa, for the purpose of keeping the frontend and backend components of the UI system synchronized. When making a change to the UI that requires changing both the backend and frontend, follow steps in this order:
1. merge frontend (Lattice) PR
2. run `make upgrade-lattice`
3. run `git add lattice`, `git commit -m"Upgrade Lattice"`, `git push`
4. merge backend (Pilosa) PR
## Client Libraries
There are supported libraries for the following languages:
@ -63,13 +74,12 @@ There are supported libraries for the following languages:
- [Java](https://www.pilosa.com/docs/client-libraries/#java)
- [Python](https://www.pilosa.com/docs/client-libraries/#python)
## Licenses
## License
The core Pilosa code base and all default builds (referred to as Pilosa Community Edition) are licensed completely under the Apache License, Version 2.0.
If you build Pilosa with the `enterprise` build tag (Pilosa Enterprise Edition), then that build will include features licensed under the GNU Affero General
Public License (AGPL). Enterprise code is located entirely in the [github.com/pilosa/pilosa/enterprise](https://github.com/pilosa/pilosa/tree/master/enterprise)
directory. See [github.com/pilosa/pilosa/NOTICE](https://github.com/pilosa/pilosa/blob/master/NOTICE) and
[github.com/pilosa/pilosa/LICENSE](https://github.com/pilosa/pilosa/blob/master/LICENSE) for more information about Pilosa licenses.
Pilosa is licensed under the Apache License, Version 2.0.
A copy of the license is located in [github.com/pilosa/pilosa/LICENSE](https://github.com/pilosa/pilosa/blob/master/LICENSE).
More details about licensing are found in [github.com/pilosa/pilosa/NOTICE](https://github.com/pilosa/pilosa/blob/master/NOTICE).
## Get Support

1242
api.go

File diff suppressed because it is too large Load diff

214
api/client/grpc.go Normal file
View file

@ -0,0 +1,214 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package client
import (
"context"
"crypto/tls"
"log"
"sync"
pb "github.com/pilosa/pilosa/v2/proto"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials"
)
const maxMsgSize = 1024 * 1024 * 100 // 100 megs ought to be enough for anybody!
// GRPCClient is a client for working with the gRPC server.
type GRPCClient struct {
dialTargets []string
tlsConfig *tls.Config
mu sync.RWMutex
conn *grpc.ClientConn
targetIndex int
}
// NewGRPCClient returns a new instance of GRPCClient.
func NewGRPCClient(dialTargets []string, tlsConfig *tls.Config) (*GRPCClient, error) {
c := &GRPCClient{
dialTargets: dialTargets,
tlsConfig: tlsConfig,
}
// resetConn sets GRPCClient.conn when it doesn't
// exist yet.
if err := c.resetConn(); err != nil {
return nil, errors.Wrap(err, "setting connection")
}
return c, nil
}
// resetConn resets the gRPC client connection. This method
// can also be used to initially set the client connection
// because it only tries to first close the connection if
// the connection already exists.
func (c *GRPCClient) resetConn() error {
c.mu.Lock()
defer c.mu.Unlock()
// If an existing connection exists, close it first.
if c.conn != nil {
if err := c.conn.Close(); err != nil {
return errors.Wrap(err, "closing existing connection")
}
}
var opts []grpc.DialOption
if c.tlsConfig != nil {
creds := credentials.NewTLS(c.tlsConfig)
opts = append(opts, grpc.WithTransportCredentials(creds))
} else {
opts = append(opts, grpc.WithInsecure())
}
opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)))
var err error
if c.conn, err = grpc.Dial(c.dialTargets[c.getTargetIndex()], opts...); err != nil {
return errors.Wrap(err, "creating new grpc client")
}
return nil
}
// getTargetIndex gets the current target index, then increments it for
// next time. Unprotected.
func (c *GRPCClient) getTargetIndex() int {
if len(c.dialTargets) == 0 {
return 0
}
ret := c.targetIndex
c.targetIndex = (c.targetIndex + 1) % len(c.dialTargets) // cycle through dialTargets
return ret
}
// Close closes any connections the client has opened.
func (c *GRPCClient) Close() error {
c.mu.RLock()
defer c.mu.RUnlock()
if c.conn != nil {
return c.conn.Close()
}
return nil
}
// Conn returns the gRPC client connection. If the connection
// has gone into state `TransientFailure`, this method tries
// to reset the connection and return that new connection.
func (c *GRPCClient) Conn() *grpc.ClientConn {
c.mu.RLock()
if c.conn == nil {
c.mu.RUnlock()
return nil
} else if c.conn.GetState() != connectivity.TransientFailure {
defer c.mu.RUnlock()
return c.conn
}
c.mu.RUnlock()
if err := c.resetConn(); err != nil {
// TODO: log this error with logger
log.Printf("error resetting connection: %s", err)
}
c.mu.RLock()
defer c.mu.RUnlock()
return c.conn
}
// Query returns a stream of RowResponse for the given index and PQL string.
func (c *GRPCClient) Query(ctx context.Context, index string, pql string) (pb.StreamClient, error) {
conn := c.Conn()
if conn == nil {
return nil, errors.New("client has not established a grpc connection")
}
grpcClient := pb.NewPilosaClient(conn)
stream, err := grpcClient.QueryPQL(ctx, &pb.QueryPQLRequest{
Index: index,
Pql: pql,
})
if err != nil {
return nil, errors.Wrap(err, "getting stream")
} else if stream == nil {
return nil, errors.New("could not create stream")
}
return stream, err
}
// QueryUnary returns a TableResponse for the given index and PQL string.
func (c *GRPCClient) QueryUnary(ctx context.Context, index string, pql string) (*pb.TableResponse, error) {
conn := c.Conn()
if conn == nil {
return nil, errors.New("client has not established a grpc connection")
}
grpcClient := pb.NewPilosaClient(conn)
return grpcClient.QueryPQLUnary(ctx, &pb.QueryPQLRequest{
Index: index,
Pql: pql,
})
}
// Inspect returns a stream of RowResponse for the given index, columns, and filters.
// It is intended to mimic something like "select [fields] from table where recordID IN (...)".
func (c *GRPCClient) Inspect(ctx context.Context, index string, columnIDs []uint64, columnKeys []string, query string, fieldFilters []string, limit, offset uint64) (pb.StreamClient, error) {
conn := c.Conn()
if conn == nil {
return nil, errors.New("client has not established a grpc connection")
}
if len(columnIDs) > 0 && len(columnKeys) > 0 {
return nil, errors.New("only provide column ids or keys, not both")
}
// Convert columns to proto type IdsOrKeys.
idsOrKeys := &pb.IdsOrKeys{}
if len(columnKeys) > 0 {
idsOrKeys.Type = &pb.IdsOrKeys_Keys{Keys: &pb.StringArray{Vals: columnKeys}}
} else {
idsOrKeys.Type = &pb.IdsOrKeys_Ids{Ids: &pb.Uint64Array{Vals: columnIDs}}
}
grpcClient := pb.NewPilosaClient(conn)
stream, err := grpcClient.Inspect(ctx, &pb.InspectRequest{
Index: index,
Columns: idsOrKeys,
FilterFields: fieldFilters,
Limit: limit,
Offset: offset,
Query: query,
})
if err != nil {
return nil, errors.Wrap(err, "getting stream")
} else if stream == nil {
return nil, errors.New("could not create stream")
}
return stream, err
}

View file

@ -19,6 +19,7 @@ import (
"fmt"
"math"
"reflect"
"strconv"
"strings"
"testing"
"time"
@ -30,6 +31,140 @@ import (
"github.com/pilosa/pilosa/v2/test"
)
// attrFun defines a mapping from columnID -> attr value
func attrFun(id uint64) string {
//return fmt.Sprintf("%x", md5.Sum([]byte(strconv.FormatInt(int64(id), 10))))
return strconv.FormatInt(int64(id), 10)
}
func TestAPI_ImportColumnAttrs(t *testing.T) {
/*
columns seconds
100 1.150
1000 1.568
10000 5.156
100000 38.179
*/
c := test.MustRunCluster(t, 2,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node1"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
)},
)
defer c.Close()
m0 := c.GetNode(0)
m1 := c.GetNode(1)
t.Run("ImportColumnAttrs", func(t *testing.T) {
ctx := context.Background()
indexName := "i"
fieldName := "f"
attrKey := "k"
index, err := m0.API.CreateIndex(ctx, indexName, pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = m0.API.CreateField(ctx, indexName, fieldName)
if err != nil {
t.Fatalf("creating field: %v", err)
}
// Generate some attrs for two shards
numAttrs := 100
columnIDs0 := make([]uint64, 0, numAttrs)
attrVals0 := make([]string, 0, numAttrs)
columnIDs1 := make([]uint64, 0, numAttrs)
attrVals1 := make([]string, 0, numAttrs)
for n := 0; n < 1000000; n += 1000000 / numAttrs {
columnIDs0 = append(columnIDs0, uint64(n))
val0 := attrFun(uint64(n))
attrVals0 = append(attrVals0, val0)
setPql0 := fmt.Sprintf("Set(%d, %s=0) ", n, fieldName)
if _, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: indexName, Query: setPql0}); err != nil {
t.Fatal(err)
}
columnIDs1 = append(columnIDs1, uint64(n+ShardWidth))
val1 := attrFun(uint64(n + ShardWidth))
attrVals1 = append(attrVals1, val1)
setPql1 := fmt.Sprintf("Set(%d, %s=0) ", n+ShardWidth, fieldName)
if _, err := m1.API.Query(ctx, &pilosa.QueryRequest{Index: indexName, Query: setPql1}); err != nil {
t.Fatal(err)
}
}
// send shard0 to node1
req := &pilosa.ImportColumnAttrsRequest{
AttrKey: attrKey,
ColumnIDs: columnIDs0,
AttrVals: attrVals0,
Shard: 0,
Index: indexName,
IndexCreatedAt: index.CreatedAt(),
}
if err := m1.API.ImportColumnAttrs(ctx, req); err != nil {
t.Fatal(err)
}
// send shard1 to node0
req = &pilosa.ImportColumnAttrsRequest{
AttrKey: attrKey,
ColumnIDs: columnIDs1,
AttrVals: attrVals1,
Shard: 1,
Index: indexName,
IndexCreatedAt: index.CreatedAt(),
}
if err := m0.API.ImportColumnAttrs(ctx, req); err != nil {
t.Fatal(err)
}
// Query node0.
pql := fmt.Sprintf("Options(Row(%s=0), columnAttrs=true)", fieldName)
res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: indexName, Query: pql})
if err != nil {
t.Fatal(err)
}
m := len(res.ColumnAttrSets)
if m != 100 {
t.Fatalf("incorrect number of column attrs set; m = %v", m)
}
for _, v := range res.ColumnAttrSets {
attrVal := attrFun(v.ID)
if attrVal != v.Attrs[attrKey] {
t.Fatal(err)
}
}
// Query node1.
pql = fmt.Sprintf("Options(Row(%s=0), columnAttrs=true)", fieldName)
res, err = m1.API.Query(ctx, &pilosa.QueryRequest{Index: indexName, Query: pql})
if err != nil {
t.Fatal(err)
}
if len(res.ColumnAttrSets) != 100 {
t.Fatal("incorrect number of column attrs set")
}
for _, v := range res.ColumnAttrSets {
attrVal := attrFun(v.ID)
if attrVal != v.Attrs[attrKey] {
t.Fatal(err)
}
}
})
}
func TestAPI_Import(t *testing.T) {
c := test.MustRunCluster(t, 2,
[]server.CommandOption{
@ -49,62 +184,79 @@ func TestAPI_Import(t *testing.T) {
)
defer c.Close()
m0 := c[0]
m1 := c[1]
m0 := c.GetNode(0)
m1 := c.GetNode(1)
t.Run("RowIDColumnKey", func(t *testing.T) {
ctx := context.Background()
index := "rick"
field := "f"
indexName := "rick"
fieldName := "f"
_, err := m0.API.CreateIndex(ctx, index, pilosa.IndexOptions{Keys: true, TrackExistence: true})
index, err := m0.API.CreateIndex(ctx, indexName, pilosa.IndexOptions{Keys: true, TrackExistence: true})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = m0.API.CreateField(ctx, index, field, pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, 100))
if index.CreatedAt() == 0 {
t.Fatal("index createdAt is empty")
}
field, err := m0.API.CreateField(ctx, indexName, fieldName, pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, 100))
if err != nil {
t.Fatalf("creating field: %v", err)
}
if field.CreatedAt() == 0 {
t.Fatal("field createdAt is empty")
}
rowID := uint64(1)
timestamp := int64(0)
// Generate some keyed records.
rowIDs := []uint64{}
colKeys := []string{}
timestamps := []int64{}
for i := 1; i <= 10; i++ {
N := 10
for i := 1; i <= N; i++ {
rowIDs = append(rowIDs, rowID)
timestamps = append(timestamps, timestamp)
colKeys = append(colKeys, fmt.Sprintf("col%d", i))
}
// Keys are sharded so ordering is not guaranteed.
colKeys := []string{"col10", "col8", "col9", "col6", "col7", "col4", "col5", "col2", "col3", "col1"}
colKeys = colKeys[:N]
// Import data with keys to the coordinator (node0) and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
req := &pilosa.ImportRequest{
Index: index,
Field: field,
Shard: 0,
RowIDs: rowIDs,
ColumnKeys: colKeys,
Timestamps: timestamps,
}
if err := m0.API.Import(ctx, req); err != nil {
t.Fatal(err)
Index: indexName,
IndexCreatedAt: index.CreatedAt(),
Field: fieldName,
FieldCreatedAt: field.CreatedAt(),
Shard: 0, // import is all on shard 0, why are we making lots of other shards? b/c this is not a restriction.
RowIDs: rowIDs,
ColumnKeys: colKeys,
Timestamps: timestamps,
}
pql := fmt.Sprintf("Row(%s=%d)", field, rowID)
qcx := m0.API.Txf().NewQcx()
if err := m0.API.Import(ctx, qcx, req); err != nil {
t.Fatal(err)
}
panicOn(qcx.Finish())
pql := fmt.Sprintf("Row(%s=%d)", fieldName, rowID)
// Query node0.
if res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql}); err != nil {
if res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: indexName, Query: pql}); err != nil {
t.Fatal(err)
} else if keys := res.Results[0].(*pilosa.Row).Keys; !reflect.DeepEqual(keys, colKeys) {
t.Fatalf("unexpected column keys: %#v", keys)
t.Fatalf("expected colKeys='%#v'; observed column keys: %#v", colKeys, keys)
}
// Query node1.
if err := test.RetryUntil(5*time.Second, func() error {
if res, err := m1.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql}); err != nil {
if res, err := m1.API.Query(ctx, &pilosa.QueryRequest{Index: indexName, Query: pql}); err != nil {
return err
} else if keys := res.Results[0].(*pilosa.Row).Keys; !reflect.DeepEqual(keys, colKeys) {
return fmt.Errorf("unexpected column keys: %#v", keys)
@ -129,58 +281,6 @@ func TestAPI_Import(t *testing.T) {
}
})
t.Run("RowKeyColumnID", func(t *testing.T) {
ctx := context.Background()
index := "rkci"
field := "f"
_, err := m0.API.CreateIndex(ctx, index, pilosa.IndexOptions{Keys: false})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = m0.API.CreateField(ctx, index, field, pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, 100), pilosa.OptFieldKeys())
if err != nil {
t.Fatalf("creating field: %v", err)
}
rowKey := "rowkey"
// Generate some keyed records.
rowKeys := []string{rowKey, rowKey, rowKey}
colIDs := []uint64{1, 2, pilosa.ShardWidth + 1}
timestamps := []int64{0, 0, 0}
// Import data with keys to the coordinator (node0) and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
req := &pilosa.ImportRequest{
Index: index,
Field: field,
Shard: 0,
RowKeys: rowKeys,
ColumnIDs: colIDs,
Timestamps: timestamps,
}
if err := m0.API.Import(ctx, req); err != nil {
t.Fatal(err)
}
pql := fmt.Sprintf("Row(%s=%s)", field, rowKey)
// Query node0.
if res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql}); err != nil {
t.Fatal(err)
} else if columns := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(columns, colIDs) {
t.Fatalf("unexpected column ids: %+v", columns)
}
// Query node1.
if res, err := m1.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql}); err != nil {
t.Fatal(err)
} else if columns := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(columns, colIDs) {
t.Fatalf("unexpected column ids: %+v", columns)
}
})
}
func TestAPI_ImportValue(t *testing.T) {
@ -200,8 +300,8 @@ func TestAPI_ImportValue(t *testing.T) {
)
defer c.Close()
m0 := c[0]
m1 := c[1]
m0 := c.GetNode(0)
m1 := c.GetNode(1)
t.Run("ValColumnKey", func(t *testing.T) {
ctx := context.Background()
@ -219,12 +319,13 @@ func TestAPI_ImportValue(t *testing.T) {
// Generate some keyed records.
values := []int64{}
colKeys := []string{}
for i := 1; i <= 10; i++ {
values = append(values, int64(i))
colKeys = append(colKeys, fmt.Sprintf("col%d", i))
}
// Column keys are sharded so their order is not guaranteed.
colKeys := []string{"col10", "col8", "col9", "col6", "col7", "col4", "col5", "col2", "col3", "col1"}
// Import data with keys to the coordinator (node0) and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
req := &pilosa.ImportValueRequest{
@ -233,9 +334,12 @@ func TestAPI_ImportValue(t *testing.T) {
ColumnKeys: colKeys,
Values: values,
}
if err := m0.API.ImportValue(ctx, req); err != nil {
qcx := m0.API.Txf().NewQcx()
if err := m0.API.ImportValue(ctx, qcx, req); err != nil {
t.Fatal(err)
}
panicOn(qcx.Finish())
pql := fmt.Sprintf("Row(%s>0)", field)
@ -258,6 +362,125 @@ func TestAPI_ImportValue(t *testing.T) {
t.Fatal(err)
}
})
t.Run("ValDecimalField", func(t *testing.T) {
ctx := context.Background()
index := "valdec"
field := "fdec"
_, err := m1.API.CreateIndex(ctx, index, pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = m1.API.CreateField(ctx, index, field, pilosa.OptFieldTypeDecimal(1))
if err != nil {
t.Fatalf("creating field: %v", err)
}
// Generate some keyed records.
values := []float64{}
colIDs := []uint64{}
for i := 0; i < 10; i++ {
values = append(values, float64(i)+0.1)
colIDs = append(colIDs, uint64(i))
}
// Import data with keys to the coordinator (node0) and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
req := &pilosa.ImportValueRequest{
Index: index,
Field: field,
ColumnIDs: colIDs,
FloatValues: values,
}
qcx := m1.API.Txf().NewQcx()
if err := m1.API.ImportValue(ctx, qcx, req); err != nil {
t.Fatal(err)
}
panicOn(qcx.Finish())
query := fmt.Sprintf("Row(%s>6)", field)
// Query node0.
if res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: query}); err != nil {
t.Fatal(err)
} else if ids := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(ids, colIDs[6:]) {
t.Fatalf("unexpected column keys: observerd %+v; expected '%+v'", ids, colIDs[6:])
}
})
t.Run("ValDecimalFieldNegativeScale", func(t *testing.T) {
ctx := context.Background()
index := "valdecneg"
field := "fdecneg"
_, err := m0.API.CreateIndex(ctx, index, pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = m0.API.CreateField(ctx, index, field, pilosa.OptFieldTypeDecimal(-1))
if err == nil {
t.Fatal("expected error creating field")
}
})
t.Run("ValStringField", func(t *testing.T) {
ctx := context.Background()
index := "valstr"
field := "fstr"
fgnIndex := "fgnvalstr"
_, err := m0.API.CreateIndex(ctx, index, pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = m0.API.CreateIndex(ctx, fgnIndex, pilosa.IndexOptions{Keys: true})
if err != nil {
t.Fatalf("creating foreign index: %v", err)
}
_, err = m0.API.CreateField(ctx, index, field,
pilosa.OptFieldTypeInt(0, math.MaxInt64),
pilosa.OptFieldForeignIndex(fgnIndex),
)
if err != nil {
t.Fatalf("creating field: %v", err)
}
// Generate some keyed records.
values := []string{}
colIDs := []uint64{}
for i := 0; i < 10; i++ {
value := fmt.Sprintf("strval-%d", (i)*100+10)
values = append(values, value)
colIDs = append(colIDs, uint64(i))
}
// Import data with keys to the coordinator (node0) and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
req := &pilosa.ImportValueRequest{
Index: index,
Field: field,
ColumnIDs: colIDs,
StringValues: values,
}
qcx := m0.API.Txf().NewQcx()
if err := m0.API.ImportValue(ctx, qcx, req); err != nil {
t.Fatal(err)
}
panicOn(qcx.Finish())
pql := fmt.Sprintf(`Row(%s=="strval-110")`, field)
// Query node0.
if res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql}); err != nil {
t.Fatal(err)
} else if ids := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(ids, []uint64{1}) {
t.Fatalf("unexpected columns: observerd %+v; expected '%+v'", ids, []uint64{1})
}
})
}
// offsetModHasher represents a simple, mod-based hashing offset by 1.
@ -266,3 +489,136 @@ type offsetModHasher struct{}
func (*offsetModHasher) Hash(key uint64, n int) int {
return int(key+1) % n
}
func (*offsetModHasher) Name() string { return "mod" }
func TestAPI_ClearFlagForImportAndImportValues(t *testing.T) {
c := test.MustRunCluster(t, 1,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateReader(http.GetOpenTranslateReaderFunc(nil)),
)},
)
defer c.Close()
// plan:
// 1. set a bit
// 2. clear with Import() using the ImportRequest.Clear flag
// 3. verifiy the clear is done.
// repeat for ImportValueRequest and ImportValues()
m0 := c.GetNode(0)
m0api := m0.API
ctx := context.Background()
index := "i"
fieldAcct0 := "acct0"
opts := pilosa.OptFieldTypeInt(-1000, 1000)
_, err := m0api.CreateIndex(ctx, index, pilosa.IndexOptions{})
if err != nil {
t.Fatalf("creating index: %v", err)
}
_, err = m0api.CreateField(ctx, index, fieldAcct0, opts)
if err != nil {
t.Fatalf("creating fieldAcct0: %v", err)
}
iraField := "ira" // set field.
iraRowID := uint64(3)
_, err = m0api.CreateField(ctx, index, iraField)
if err != nil {
t.Fatalf("creating fieldIRA: %v", err)
}
acctOwnerID := uint64(78) // ColumnID
shard := acctOwnerID / ShardWidth
acct0bal := int64(500)
ivr0 := &pilosa.ImportValueRequest{
Index: index,
Field: fieldAcct0,
Shard: shard,
ColumnIDs: []uint64{acctOwnerID},
Values: []int64{acct0bal},
}
ir0 := &pilosa.ImportRequest{
Index: index,
Field: iraField,
Shard: shard,
ColumnIDs: []uint64{acctOwnerID},
RowIDs: []uint64{iraRowID},
}
qcx := m0api.Txf().NewQcx()
if err := m0api.Import(ctx, qcx, ir0); err != nil {
t.Fatal(err)
}
if err := m0api.ImportValue(ctx, qcx, ivr0); err != nil {
t.Fatal(err)
}
panicOn(qcx.Finish())
bitIsSet := func() bool {
query := fmt.Sprintf("Row(%v=%v)", iraField, iraRowID)
res, err := m0api.Query(context.Background(), &pilosa.QueryRequest{Index: index, Query: query})
panicOn(err)
cols := res.Results[0].(*pilosa.Row).Columns()
for i := range cols {
if cols[i] == acctOwnerID {
return true
}
}
return false
}
if !bitIsSet() {
panic("IRA bit should have been set")
}
queryAcct := func(m0api *pilosa.API, acctOwnerID uint64, fieldAcct0, index string) (acctBal int64) {
query := fmt.Sprintf("FieldValue(field=%v, column=%v)", fieldAcct0, acctOwnerID)
res, err := m0api.Query(context.Background(), &pilosa.QueryRequest{Index: index, Query: query})
panicOn(err)
if len(res.Results) == 0 {
return 0
}
valCount := res.Results[0].(pilosa.ValCount)
return valCount.Val
}
bal := queryAcct(m0api, acctOwnerID, fieldAcct0, index)
if bal != acct0bal {
panic(fmt.Sprintf("expected %v, observed %v starting acct0 balance", acct0bal, bal))
}
// clear the bit
qcx = m0api.Txf().NewQcx()
ir0.Clear = true
if err := m0api.Import(ctx, qcx, ir0); err != nil {
t.Fatal(err)
}
panicOn(qcx.Finish())
if bitIsSet() {
panic("IRA bit should have been cleared")
}
// clear the BSI
qcx = m0api.Txf().NewQcx()
ivr0.Clear = true
if err := m0api.ImportValue(ctx, qcx, ivr0); err != nil {
t.Fatal(err)
}
panicOn(qcx.Finish())
bal = queryAcct(m0api, acctOwnerID, fieldAcct0, index)
if bal != 0 {
panic(fmt.Sprintf("expected %v, observed %v starting acct0 balance", acct0bal, 0))
}
}

View file

@ -19,25 +19,30 @@ func _() {
_ = x[apiFragmentBlockData-8]
_ = x[apiFragmentBlocks-9]
_ = x[apiFragmentData-10]
_ = x[apiField-11]
_ = x[apiFieldAttrDiff-12]
_ = x[apiImport-13]
_ = x[apiImportValue-14]
_ = x[apiIndex-15]
_ = x[apiIndexAttrDiff-16]
_ = x[apiQuery-17]
_ = x[apiRecalculateCaches-18]
_ = x[apiRemoveNode-19]
_ = x[apiResizeAbort-20]
_ = x[apiSetCoordinator-21]
_ = x[apiShardNodes-22]
_ = x[apiViews-23]
_ = x[apiApplySchema-24]
_ = x[apiTranslateData-11]
_ = x[apiField-12]
_ = x[apiFieldAttrDiff-13]
_ = x[apiImport-14]
_ = x[apiImportValue-15]
_ = x[apiIndex-16]
_ = x[apiIndexAttrDiff-17]
_ = x[apiQuery-18]
_ = x[apiRecalculateCaches-19]
_ = x[apiRemoveNode-20]
_ = x[apiResizeAbort-21]
_ = x[apiSetCoordinator-22]
_ = x[apiShardNodes-23]
_ = x[apiViews-24]
_ = x[apiApplySchema-25]
_ = x[apiStartTransaction-26]
_ = x[apiFinishTransaction-27]
_ = x[apiTransactions-28]
_ = x[apiGetTransaction-29]
}
const _apiMethod_name = "apiClusterMessageapiCreateFieldapiCreateIndexapiDeleteFieldapiDeleteAvailableShardapiDeleteIndexapiDeleteViewapiExportCSVapiFragmentBlockDataapiFragmentBlocksapiFragmentDataapiFieldapiFieldAttrDiffapiImportapiImportValueapiIndexapiIndexAttrDiffapiQueryapiRecalculateCachesapiRemoveNodeapiResizeAbortapiSetCoordinatorapiShardNodesapiViewsapiApplySchema"
const _apiMethod_name = "apiClusterMessageapiCreateFieldapiCreateIndexapiDeleteFieldapiDeleteAvailableShardapiDeleteIndexapiDeleteViewapiExportCSVapiFragmentBlockDataapiFragmentBlocksapiFragmentDataapiTranslateDataapiFieldapiFieldAttrDiffapiImportapiImportValueapiIndexapiIndexAttrDiffapiQueryapiRecalculateCachesapiRemoveNodeapiResizeAbortapiSetCoordinatorapiShardNodesapiViewsapiApplySchemaapiStartTransactionapiFinishTransactionapiTransactionsapiGetTransaction"
var _apiMethod_index = [...]uint16{0, 17, 31, 45, 59, 82, 96, 109, 121, 141, 158, 173, 181, 197, 206, 220, 228, 244, 252, 272, 285, 299, 316, 329, 337, 351}
var _apiMethod_index = [...]uint16{0, 17, 31, 45, 59, 82, 96, 109, 121, 141, 158, 173, 189, 197, 213, 222, 236, 244, 260, 268, 288, 301, 315, 332, 345, 353, 367, 386, 406, 421, 438}
func (i apiMethod) String() string {
if i < 0 || i >= apiMethod(len(_apiMethod_index)-1) {

25
audit.go Normal file
View file

@ -0,0 +1,25 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"github.com/pilosa/pilosa/v2/testhook"
)
var NewAuditor func() testhook.Auditor = NewNopAuditor
func NewNopAuditor() testhook.Auditor {
return testhook.NewNopAuditor()
}

52
audit_internal_test.go Normal file
View file

@ -0,0 +1,52 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"fmt"
"reflect"
"github.com/pilosa/pilosa/v2/testhook"
)
// These audit hooks are desireable during testing, but not in
// production.
type auditorViewHooks struct{}
type auditorFragmentHooks struct{}
// static type checks
var _ testhook.RegistryHookLive = &auditorViewHooks{}
var _ testhook.RegistryHookLive = &auditorFragmentHooks{}
func (*auditorViewHooks) Live(o interface{}, entry *testhook.RegistryEntry) error {
if entry != nil && entry.OpenCount != 0 {
return fmt.Errorf("view %s still open", o.(*view).name)
}
return nil
}
func (*auditorFragmentHooks) Live(o interface{}, entry *testhook.RegistryEntry) error {
if entry != nil && entry.OpenCount != 0 {
return fmt.Errorf("fragment %s still open", o.(*fragment).path())
}
return nil
}
func GetInternalTestHooks() testhook.RegistryHooks {
return map[reflect.Type]testhook.RegistryHook{
reflect.TypeOf((*view)(nil)): &auditorViewHooks{},
reflect.TypeOf((*fragment)(nil)): &auditorFragmentHooks{},
}
}

107
audit_test.go Normal file
View file

@ -0,0 +1,107 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa_test
import (
"fmt"
"os"
"reflect"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/testhook"
)
// AuditLeaksOn is a global switch to turn on resource
// leak checking at the end of a test run.
var AuditLeaksOn = true
// for tests, we use a single shared auditor used by all of the holders.
var globalTestAuditor = testhook.NewVerifyCloseAuditor(testHooks)
// These audit hooks are desireable during testing, but not in
// production.
type auditorIndexHooks struct{}
type auditorFieldHooks struct{}
type auditorHolderHooks struct{}
// static type checking
var _ testhook.RegistryHookLive = &auditorIndexHooks{}
var _ testhook.RegistryHookLive = &auditorFieldHooks{}
var _ testhook.RegistryHookPostDestroy = &auditorHolderHooks{}
var _ testhook.RegistryHookLive = &auditorHolderHooks{}
var testHooks = map[reflect.Type]testhook.RegistryHook{
reflect.TypeOf((*pilosa.Index)(nil)): &auditorIndexHooks{},
reflect.TypeOf((*pilosa.Field)(nil)): &auditorFieldHooks{},
reflect.TypeOf((*pilosa.Holder)(nil)): &auditorHolderHooks{},
}
func init() {
if !AuditLeaksOn {
return
}
for k, v := range pilosa.GetInternalTestHooks() {
testHooks[k] = v
}
testhook.RegisterPreTestHook(func() error {
pilosa.NewAuditor = NewTestAuditor
return nil
})
testhook.RegisterPostTestHook(func() error {
err, errs := globalTestAuditor.FinalCheck()
if err != nil {
for i, e := range errs {
fmt.Fprintf(os.Stderr, "[%d]: %v\n", i, e)
}
}
return err
})
}
func NewTestAuditor() testhook.Auditor {
return globalTestAuditor
}
func (*auditorIndexHooks) Live(o interface{}, entry *testhook.RegistryEntry) error {
if entry != nil && entry.OpenCount != 0 {
return fmt.Errorf("index %s still open", o.(*pilosa.Index).Name())
}
return nil
}
func (*auditorFieldHooks) Live(o interface{}, entry *testhook.RegistryEntry) error {
if entry != nil && entry.OpenCount != 0 {
return fmt.Errorf("field %s still open", o.(*pilosa.Field).Name())
}
return nil
}
func (*auditorHolderHooks) WasDestroyed(o interface{}, kv testhook.KV, ent *testhook.RegistryEntry, err error) error {
path := o.(*pilosa.Holder).Path()
if path == "" {
fmt.Fprintf(os.Stderr, "OOPS: trying to destroy a holder with no path! created: %s\n",
ent.Stack)
} else {
os.RemoveAll(o.(*pilosa.Holder).Path())
}
return err
}
func (*auditorHolderHooks) Live(o interface{}, entry *testhook.RegistryEntry) error {
if entry != nil && entry.OpenCount != 0 {
return fmt.Errorf("holder %s still open", o.(*pilosa.Holder).Path())
}
return nil
}

219
barrier.go Normal file
View file

@ -0,0 +1,219 @@
// home https://github.com/glycerine/lmdb-go
// Copyright (c) 2020, the lmdb-go authors
// Copyright (c) 2015, Bryan Matsuo
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// Neither the name of the author nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package pilosa
import (
"github.com/glycerine/idem"
)
// Barrier allows us to temporarily halt all readers, so that
// a writer can commit alone and thus compact the db.
// The Barrier starts unblocked, alllowing passage to any
// caller of WaitAtGate().
type Barrier struct {
wait chan *appointment // send upon entering the waiting room.
halt *idem.Halter
blockReqCh chan *blockReq
unblockCh chan *unblock
}
type blockReq struct {
count int
done chan struct{}
}
func newBlockReq(count int) *blockReq {
return &blockReq{
count: count,
done: make(chan struct{}),
}
}
type appointment struct {
id int
done chan struct{}
}
func newAppointment(id int) *appointment {
return &appointment{
id: id,
done: make(chan struct{}),
}
}
// NewBarrier is either open, allowing immediate passage,
// or blocked, halting all callers at WaitAtGate()
// until the barrier is opened. By default it is open.
//
// Barrier.Close() must be called when the barrier
// is no longer needed to avoid a goroutine leak.
func NewBarrier() (b *Barrier) {
b = &Barrier{
wait: make(chan *appointment), // waiters indicate they are waiting for the gate by sending here.
halt: idem.NewHalter(),
blockReqCh: make(chan *blockReq),
unblockCh: make(chan *unblock),
}
go func() {
defer b.halt.Done.Close()
var waitlist []*appointment
var curBlockReq *blockReq
for {
select {
case br := <-b.blockReqCh:
if br.count == 0 {
close(br.done)
continue
}
if curBlockReq == nil {
// good, changing state from open to closed barrier.
} else {
panic("got 2nd block request atop of first")
}
curBlockReq = br
//vv("barrier: request to block for %v waiters", br.count)
if len(waitlist) != 0 {
panic("had waiters when we were open, internal/client bug")
}
case appt := <-b.wait:
//vv("barrier.wait sees appt = '%#v' and curBlockReq = '%#v'", appt, curBlockReq)
if curBlockReq == nil {
close(appt.done)
continue
}
waitlist = append(waitlist, appt)
n := len(waitlist)
th := curBlockReq.count
if th < 0 {
// infinite waiters. we block everybody until we
// see an unblock request.
continue
}
if n >= th {
close(curBlockReq.done)
curBlockReq = nil
}
case ub := <-b.unblockCh:
for _, appt := range waitlist {
close(appt.done)
}
waitlist = nil
curBlockReq = nil
close(ub.done)
case <-b.halt.ReqStop.Chan:
return
}
}
}()
return
}
// WaitAtGate will return immediately
// if the barrier is unblocked. Otherwise
// it will not return until another
// goroutine unblocks the barrier.
func (b *Barrier) WaitAtGate(id int) {
appt := newAppointment(id)
select {
case b.wait <- appt:
select {
case <-appt.done:
case <-b.halt.ReqStop.Chan:
}
case <-b.halt.ReqStop.Chan:
}
}
// Close should be called to stop the
// barrier's background goroutine when
// you are done using the barrier.
func (b *Barrier) Close() {
b.halt.ReqStop.Close()
<-b.halt.Done.Chan
}
type unblock struct {
done chan struct{}
}
func newUnblock() *unblock {
return &unblock{
done: make(chan struct{}),
}
}
// Unblock lets all waiting goroutines resume execution.
func (b *Barrier) UnblockReaders() {
ub := newUnblock()
select {
case b.unblockCh <- ub:
select {
case <-ub.done:
case <-b.halt.ReqStop.Chan:
}
case <-b.halt.ReqStop.Chan:
}
}
// BlockUntil is called with a count, the
// number of waiters required to be present and waiting
// at the gate before call returns.
// A count of < 0 will return immediately and raise
// the barrier to any number of arriving readers.
// A count of 0 is a no-op.
//
// Otherwise we raise the barrier
// and wait until we have seen count other goroutines waiting
// on it.
//
// We return without releasing the waiters. Call
// Open when you want them to resume.
func (b *Barrier) BlockUntil(count int) {
if count == 0 {
return
}
req := newBlockReq(count)
b.blockReqCh <- req
if count > 0 {
<-req.done
}
}
// BlockAllReadersNoWait raises the barrier to
// an infinite number of waiters and returns immediately
// to the caller.
func (b *Barrier) BlockAllReadersNoWait() {
req := newBlockReq(-1) // -1 means block any number of readers.
b.blockReqCh <- req
// don't wait. <-req.done
}

90
barrier_test.go Normal file
View file

@ -0,0 +1,90 @@
// home https://github.com/glycerine/lmdb-go
// Copyright (c) 2020, the lmdb-go authors
// Copyright (c) 2015, Bryan Matsuo
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// Neither the name of the author nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package pilosa
import (
"fmt"
"sync/atomic"
"testing"
"time"
)
func TestBarrierHolds(t *testing.T) {
b := NewBarrier()
defer b.Close()
released := int64(0)
waiter := func(i int) {
b.WaitAtGate(i)
//vv("goro %v is released", i)
atomic.AddInt64(&released, 1)
}
for i := 0; i < 3; i++ {
go waiter(i)
}
time.Sleep(time.Second)
r := atomic.SwapInt64(&released, 0)
if r != 3 {
panic("open barrier held back goro")
}
//vv("good: barrier started open")
//seenAll := make(chan bool)
b.BlockAllReadersNoWait()
for i := 0; i < 3; i++ {
go waiter(i)
}
time.Sleep(time.Second)
r = atomic.SwapInt64(&released, 0)
if r != 0 {
panic("bad: barrier did not hold back goro")
}
//vv("good: barrier of 4 did not release on 3")
go waiter(4)
time.Sleep(time.Second)
r = atomic.SwapInt64(&released, 0)
if r != 0 {
panic(fmt.Sprintf("bad: barrier did not hold back goro, should wait for unblock. r = %v", r))
}
b.UnblockReaders()
time.Sleep(time.Second)
r = atomic.SwapInt64(&released, 0)
if r != 4 {
panic("bad: unblock should have released 4 goro")
}
}

1028
bluegreentx.go Normal file

File diff suppressed because it is too large Load diff

98
bluegreentx_test.go Normal file
View file

@ -0,0 +1,98 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"bytes"
"context"
"io"
"io/ioutil"
"os"
"strings"
"testing"
cryrand "crypto/rand"
)
var _ = context.Background
var _ = os.Open
var _ = strings.Split
func TestMultiReaderB(t *testing.T) {
// MultiReaderB should read identical chunks of bytes from both its "a" and "b"
// member io.Readers, else it should panic. This should hold for
// varying sizes of inputs.
for n := 1 << 5; n < (1 << 18); n = n*2 - 13 {
src := io.LimitReader(cryrand.Reader, int64(n))
a := make([]byte, n)
nr := 0
for nr < n {
na, err := src.Read(a)
panicOn(err)
nr += na
}
if nr != n {
panic("short read")
}
b := make([]byte, n)
copy(b, a)
if !bytes.Equal(a, b) {
panic("test prep failed")
}
m := &MultiReaderB{
a: ioutil.NopCloser(bytes.NewBuffer(a)),
b: ioutil.NopCloser(bytes.NewBuffer(b)),
}
// should not trigger the internal panic of MultiReadB
ncp, err := io.Copy(ioutil.Discard, m)
panicOn(err)
if ncp != int64(n) {
panic("short copy")
}
for victim := 0; victim < n; victim += 7 {
copy(b, a)
if victim%2 == 0 {
// corrupt b
b[victim] = (b[victim] + 1) % 255
} else {
// corrupt a
a[victim] = (a[victim] + 1) % 255
}
m = &MultiReaderB{
a: ioutil.NopCloser(bytes.NewBuffer(a)),
b: ioutil.NopCloser(bytes.NewBuffer(b)),
}
helperShouldPanicOnCopy(m)
}
}
}
func helperShouldPanicOnCopy(m *MultiReaderB) {
// differences in bytes read should be noticed
defer func() {
r := recover()
if r == nil {
panic("expected panic on byte difference but didn't see it")
}
}()
_, _ = io.Copy(ioutil.Discard, m)
}

1612
bolt.go Normal file

File diff suppressed because it is too large Load diff

1268
bolt_test.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -25,9 +25,9 @@ import (
"github.com/cespare/xxhash"
"github.com/boltdb/bolt"
"github.com/pilosa/pilosa/v2"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
// attrBlockSize is the size of attribute blocks for anti-entropy.

File diff suppressed because it is too large Load diff

View file

@ -14,9 +14,13 @@
package boltdb_test
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"reflect"
"strconv"
"testing"
"time"
@ -24,28 +28,29 @@ import (
"github.com/pilosa/pilosa/v2/boltdb"
)
//var vv = pilosa.VV
func TestTranslateStore_TranslateKey(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
// Ensure initial key translates to ID 1.
if id, err := s.TranslateKey("foo"); err != nil {
// Ensure initial key translates to first ID for shard
id1, err := s.TranslateKey("foo", true)
if err != nil {
t.Fatal(err)
} else if got, want := id, uint64(1); got != want {
t.Fatalf("TranslateKey()=%d, want %d", got, want)
}
// Ensure next key autoincrements.
if id, err := s.TranslateKey("bar"); err != nil {
if id, err := s.TranslateKey("bar", true); err != nil {
t.Fatal(err)
} else if got, want := id, uint64(2); got != want {
} else if got, want := id, id1+1; got != want {
t.Fatalf("TranslateKey()=%d, want %d", got, want)
}
// Ensure retranslating existing key returns original ID.
if id, err := s.TranslateKey("foo"); err != nil {
if id, err := s.TranslateKey("foo", true); err != nil {
t.Fatal(err)
} else if got, want := id, uint64(1); got != want {
} else if got, want := id, id1; got != want {
t.Fatalf("TranslateKey()=%d, want %d", got, want)
}
}
@ -54,59 +59,182 @@ func TestTranslateStore_TranslateKeys(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
// Ensure initial keys translate to incrementing IDs.
if ids, err := s.TranslateKeys([]string{"foo", "bar"}); err != nil {
ids, err := s.TranslateKeys([]string{"abc", "abc"}, true)
if err != nil {
t.Fatal(err)
} else if got, want := ids[0], uint64(1); got != want {
t.Fatalf("TranslateKeys()[0]=%d, want %d", got, want)
} else if got, want := ids[1], uint64(2); got != want {
} else if got, want := ids[1], ids[0]; got != want {
t.Fatalf("TranslateKeys()[1]=%d, want %d", got, want)
}
// Ensure initial keys translate to incrementing IDs.
ids1, err := s.TranslateKeys([]string{"foo", "bar"}, true)
if err != nil {
t.Fatal(err)
} else if got, want := ids1[1], ids1[0]+1; got != want {
t.Fatalf("TranslateKeys()[1]=%d, want %d", got, want)
}
// Ensure retranslation returns original IDs.
if ids, err := s.TranslateKeys([]string{"foo", "bar"}); err != nil {
if ids, err := s.TranslateKeys([]string{"foo", "bar"}, true); err != nil {
t.Fatal(err)
} else if got, want := ids[0], uint64(1); got != want {
} else if got, want := ids[0], ids1[0]; got != want {
t.Fatalf("TranslateKeys()[0]=%d, want %d", got, want)
} else if got, want := ids[1], uint64(2); got != want {
} else if got, want := ids[1], ids1[1]; got != want {
t.Fatalf("TranslateKeys()[1]=%d, want %d", got, want)
}
// Ensure retranslating with existing and non-existing keys returns correctly.
if ids, err := s.TranslateKeys([]string{"foo", "baz", "bar"}); err != nil {
if ids, err := s.TranslateKeys([]string{"foo", "baz", "bar"}, true); err != nil {
t.Fatal(err)
} else if got, want := ids[0], uint64(1); got != want {
} else if got, want := ids[0], ids1[0]; got != want {
t.Fatalf("TranslateKeys()[0]=%d, want %d", got, want)
} else if got, want := ids[1], uint64(3); got != want {
} else if got, want := ids[1], ids1[0]+2; got != want {
t.Fatalf("TranslateKeys()[1]=%d, want %d", got, want)
} else if got, want := ids[2], uint64(2); got != want {
} else if got, want := ids[2], ids1[1]; got != want {
t.Fatalf("TranslateKeys()[2]=%d, want %d", got, want)
}
}
func TestTranslateStore_CreateKeys(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
ids, err := s.CreateKeys("abc", "abc")
if err != nil {
t.Fatal(err)
} else if _, ok := ids["abc"]; !ok {
t.Fatalf(`missing "abc"; got %v`, ids)
} else if len(ids) > 1 {
t.Fatalf("expected one key, got %d in %v", len(ids), ids)
}
// Ensure different keys translate to different IDs.
ids1, err := s.CreateKeys("foo", "bar")
if err != nil {
t.Fatal(err)
} else if foo, bar := ids1["foo"], ids1["bar"]; foo == bar {
t.Fatalf(`"foo" and "bar" map back to the same ID %d`, foo)
}
// Ensure retranslation returns original IDs.
if ids, err := s.CreateKeys("bar", "foo"); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(ids, ids1) {
t.Fatalf("retranslation produced result %v which is different from original translation %v", ids, ids1)
}
// Ensure retranslating with existing and non-existing keys returns correctly.
if ids, err := s.CreateKeys("foo", "baz", "bar"); err != nil {
t.Fatal(err)
} else if got, want := ids["foo"], ids1["foo"]; got != want {
t.Fatalf(`mismatched ID %d for "foo" (previously %d)`, got, want)
} else if _, ok := ids["baz"]; !ok {
t.Fatalf(`missing translation for "baz"; got %v`, ids)
} else if got, want := ids["bar"], ids1["bar"]; got != want {
t.Fatalf(`mismatched ID %d for "bar" (previously %d)`, got, want)
}
}
func TestTranslateStore_ReadKey(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
id, err := s.TranslateKey("foo", false)
if err != pilosa.ErrTranslatingKeyNotFound {
t.Fatal(err)
}
if id != 0 {
t.Fatalf("TranslateKey()=%d, want %d", id, 0)
}
s.SetReadOnly(true)
id, err = s.TranslateKey("foo", true)
if err == nil {
t.Fatalf("got error: %+v, want: 'translate store read only'", err)
}
if id != 0 {
t.Fatalf("TranslateKey()=%d, want %d", id, 0)
}
s.SetReadOnly(false)
// Ensure next key autoincrements.
if id, err = s.TranslateKey("foo", true); err != nil {
t.Fatal(err)
}
id1, err := s.TranslateKey("foo", false)
if err != nil {
t.Fatal(err)
}
if id1 != id {
t.Fatalf("TranslateKey()=%d, want %d", id1, id)
}
}
func TestTranslateStore_ReadKeys(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
ids, err := s.TranslateKeys([]string{"foo", "bar", "baz", "baz", "bar", "foo"}, false)
if err != pilosa.ErrTranslatingKeyNotFound {
t.Fatal(err)
}
for _, id := range ids {
if id != 0 {
t.Fatalf("TranslateKeys()=%d, want %d", id, 0)
}
}
// Ensure next key autoincrements.
if ids, err = s.TranslateKeys([]string{"foo", "bar", "baz", "baz", "bar", "foo"}, true); err != nil {
t.Fatal(err)
}
ids1, err := s.TranslateKeys([]string{"foo", "bar", "baz", "baz", "bar", "foo"}, false)
if err != nil {
t.Fatal(err)
}
for i := range ids1 {
if ids1[i] != ids[i] {
t.Fatalf("TranslateKeys()=%d, want %d", ids1[i], ids[i])
}
}
}
func TestTranslateStore_TranslateID(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
// Setup initial keys.
if _, err := s.TranslateKey("foo"); err != nil {
id1, err := s.TranslateKey("foo", true)
if err != nil {
t.Fatal(err)
} else if _, err := s.TranslateKey("bar"); err != nil {
}
id2, err := s.TranslateKey("bar", true)
if err != nil {
t.Fatal(err)
}
id3, err := s.TranslateKey("", true)
if err != nil {
t.Fatal(err)
}
// Ensure IDs can be translated back to keys.
if key, err := s.TranslateID(1); err != nil {
if key, err := s.TranslateID(id1); err != nil {
t.Fatal(err)
} else if got, want := key, "foo"; got != want {
t.Fatalf("TranslateID()=%s, want %s", got, want)
}
if key, err := s.TranslateID(2); err != nil {
if key, err := s.TranslateID(id2); err != nil {
t.Fatal(err)
} else if got, want := key, "bar"; got != want {
t.Fatalf("TranslateID()=%s, want %s", got, want)
}
if key, err := s.TranslateID(id3); err != nil {
t.Fatal(err)
} else if got, want := key, ""; got != want {
t.Fatalf("TranslateID()=%s, want %s", got, want)
}
}
func TestTranslateStore_TranslateIDs(t *testing.T) {
@ -114,12 +242,13 @@ func TestTranslateStore_TranslateIDs(t *testing.T) {
defer MustCloseTranslateStore(s)
// Setup initial keys.
if _, err := s.TranslateKeys([]string{"foo", "bar"}); err != nil {
ids, err := s.TranslateKeys([]string{"foo", "bar"}, true)
if err != nil {
t.Fatal(err)
}
// Ensure IDs can be translated back to keys.
if keys, err := s.TranslateIDs([]uint64{1, 2, 3}); err != nil {
if keys, err := s.TranslateIDs([]uint64{ids[0], ids[1], 1}); err != nil {
t.Fatal(err)
} else if got, want := keys[0], "foo"; got != want {
t.Fatalf("TranslateIDs()[0]=%s, want %s", got, want)
@ -130,13 +259,116 @@ func TestTranslateStore_TranslateIDs(t *testing.T) {
}
}
func TestTranslateStore_FindKeys(t *testing.T) {
cases := []struct {
name string
data []string
lookup []string
}{
{
name: "All",
data: []string{"plugh", "xyzzy", "h"},
lookup: []string{"plugh", "xyzzy", "h"},
},
{
name: "Extra",
data: []string{"plugh", "xyzzy", "h"},
lookup: []string{"plugh", "xyzzy", "h", "65"},
},
{
name: "None",
data: []string{"a", "b", "c"},
lookup: []string{"d", "e"},
},
{
name: "Empty",
lookup: []string{"h"},
},
{
name: "LookupNothing",
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
var naiveMap map[string]uint64
if c.data != nil {
// Load in key data.
keys := c.data
ids, err := s.TranslateKeys(keys, true)
if err != nil {
t.Errorf("failed to import keys: %v", err)
return
}
if len(ids) != len(keys) {
t.Errorf("mapped %d keys to %d ids", len(keys), len(ids))
return
}
naiveMap = make(map[string]uint64, len(keys))
for i, key := range keys {
naiveMap[key] = ids[i]
}
}
// Compute expected lookup result.
result := map[string]uint64{}
for _, key := range c.lookup {
id, ok := naiveMap[key]
if !ok {
// The key is expected to be missing.
continue
}
result[key] = id
}
// Find the keys.
found, err := s.FindKeys(c.lookup...)
if err != nil {
t.Errorf("failed to find keys: %v", err)
} else if !reflect.DeepEqual(result, found) {
t.Errorf("expected %v but found %v", result, found)
}
})
}
}
func TestTranslateStore_MaxID(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
// Generate a bunch of keys.
var lastk uint64
for i := 0; i < 1026; i++ {
k, err := s.TranslateKey(strconv.Itoa(i), true)
if err != nil {
t.Fatalf("translating %d: %v", i, err)
}
lastk = k
}
// Verify the max ID.
max, err := s.MaxID()
if err != nil {
t.Fatalf("checking max ID: %v", err)
}
if max != lastk {
t.Fatalf("last key is %d but max is %d", lastk, max)
}
}
func TestTranslateStore_EntryReader(t *testing.T) {
t.Run("OK", func(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
// Create multiple new keys.
if _, err := s.TranslateKeys([]string{"foo", "bar"}); err != nil {
ids1, err := s.TranslateKeys([]string{"foo", "bar"}, true)
if err != nil {
t.Fatal(err)
}
@ -151,7 +383,7 @@ func TestTranslateStore_EntryReader(t *testing.T) {
// Read first entry.
if err := r.ReadEntry(&entry); err != nil {
t.Fatal(err)
} else if got, want := entry.ID, uint64(1); got != want {
} else if got, want := entry.ID, ids1[0]; got != want {
t.Fatalf("ReadEntry() ID=%d, want %d", got, want)
} else if got, want := entry.Key, "foo"; got != want {
t.Fatalf("ReadEntry() Key=%s, want %s", got, want)
@ -160,21 +392,22 @@ func TestTranslateStore_EntryReader(t *testing.T) {
// Read next entry.
if err := r.ReadEntry(&entry); err != nil {
t.Fatal(err)
} else if got, want := entry.ID, uint64(2); got != want {
} else if got, want := entry.ID, ids1[1]; got != want {
t.Fatalf("ReadEntry() ID=%d, want %d", got, want)
} else if got, want := entry.Key, "bar"; got != want {
t.Fatalf("ReadEntry() Key=%s, want %s", got, want)
}
// Insert next key while reader is open.
if _, err := s.TranslateKey("baz"); err != nil {
id2, err := s.TranslateKey("baz", true)
if err != nil {
t.Fatal(err)
}
// Read newly created entry.
if err := r.ReadEntry(&entry); err != nil {
t.Fatal(err)
} else if got, want := entry.ID, uint64(3); got != want {
} else if got, want := entry.ID, id2; got != want {
t.Fatalf("ReadEntry() ID=%d, want %d", got, want)
} else if got, want := entry.Key, "baz"; got != want {
t.Fatalf("ReadEntry() Key=%s, want %s", got, want)
@ -198,20 +431,25 @@ func TestTranslateStore_EntryReader(t *testing.T) {
}
defer r.Close()
// cache holds the translated key id so we can check it later
cache := make(chan uint64)
// Insert key in separate goroutine.
// Sleep momentarily to reader hangs.
translateErr := make(chan error)
go func() {
time.Sleep(100 * time.Millisecond)
if _, err := s.TranslateKey("foo"); err != nil {
id, err := s.TranslateKey("foo", true)
if err != nil {
translateErr <- err
}
cache <- id
}()
var entry pilosa.TranslateEntry
if err := r.ReadEntry(&entry); err != nil {
t.Fatal(err)
} else if got, want := entry.ID, uint64(1); got != want {
} else if got, want := entry.ID, <-cache; got != want {
t.Fatalf("ReadEntry() ID=%d, want %d", got, want)
} else if got, want := entry.Key, "foo"; got != want {
t.Fatalf("ReadEntry() Key=%s, want %s", got, want)
@ -302,11 +540,76 @@ func MustNewTranslateStore() *boltdb.TranslateStore {
panic(err)
}
s := boltdb.NewTranslateStore("I", "F")
s := boltdb.NewTranslateStore("I", "F", 0, pilosa.DefaultPartitionN)
s.Path = f.Name()
return s
}
func TestTranslateStore_ReadWrite(t *testing.T) {
t.Run("WriteTo_ReadFrom", func(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
batch0 := []string{}
for i := 0; i < 100; i++ {
batch0 = append(batch0, fmt.Sprintf("key%d", i))
}
batch1 := []string{}
for i := 100; i < 200; i++ {
batch1 = append(batch1, fmt.Sprintf("key%d", i))
}
// Populate the store with the keys in batch0.
batch0IDs, err := s.TranslateKeys(batch0, true)
if err != nil {
t.Fatal(err)
}
// Put the contents of the store into a buffer.
buf := bytes.NewBuffer(nil)
expN := int64(32768)
// After this, the buffer should contain batch0.
if n, err := s.WriteTo(buf); err != nil {
t.Fatalf("writing to buffer: %s", err)
} else if n != expN {
t.Fatalf("expected buffer size: %d, but got: %d", expN, n)
}
// Populate the store with the keys in batch1.
batch1IDs, err := s.TranslateKeys(batch1, true)
if err != nil {
t.Fatal(err)
}
expIDs := []uint64{batch0IDs[50], batch1IDs[50]}
// Check the IDs for a key from each batch.
if ids, err := s.TranslateKeys([]string{"key50", "key150"}, false); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(expIDs, ids) {
t.Fatalf("first expected ids: %v, but got: %v", expIDs, ids)
}
// Reset the contents of the store with the data in the buffer.
if n, err := s.ReadFrom(buf); err != nil {
t.Fatalf("reading from buffer: %s", err)
} else if n != expN {
t.Fatalf("expected buffer size: %d, but got: %d", expN, n)
}
// This time, we expect the second key to be different because
// we overwrote the store, and then just set that key.
if ids, err := s.TranslateKeys([]string{"key50", "key150"}, true); err != nil {
t.Fatal(err)
} else if ids[0] != expIDs[0] {
t.Fatalf("last expected ids[0]: %d, but got: %d", expIDs[0], ids[0])
} else if ids[1] == expIDs[1] {
t.Fatalf("last expected different ids[1]: %d, but got: %d", expIDs[1], ids[1])
}
})
}
// MustOpenNewTranslateStore returns a new, opened TranslateStore.
func MustOpenNewTranslateStore() *boltdb.TranslateStore {
s := MustNewTranslateStore()
@ -324,3 +627,173 @@ func MustCloseTranslateStore(s *boltdb.TranslateStore) {
panic(err)
}
}
func TestCryptoHashPerKey(t *testing.T) {
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
// hash one translation
expect := map[int]string{
1: string([]byte{0x76, 0x48, 0x8b, 0x70, 0xe8, 0x54, 0x35, 0xc6, 0x8e, 0xa6, 0x4, 0x6c, 0xfa, 0xd2, 0x1a, 0x12}),
2: string([]byte{0x81, 0x46, 0x84, 0x37, 0x26, 0x96, 0x41, 0xf3, 0x54, 0x4e, 0x98, 0xbc, 0x48, 0xab, 0x1b, 0xf0}),
3: string([]byte{0x7f, 0xe9, 0xf, 0x6d, 0x7b, 0x14, 0x1, 0x44, 0xb2, 0x4e, 0xd0, 0x86, 0x2f, 0x62, 0x8c, 0xa9}),
}
for n := 1; n < 4; n++ {
var batch0 []string
for i := 0; i < n; i++ {
batch0 = append(batch0, fmt.Sprintf("key%d", i))
}
// Populate the store with the keys in batch0.
batch0IDs, err := s.TranslateKeys(batch0, true)
_ = batch0IDs
if err != nil {
t.Fatal(err)
}
// done with setup
sum, err := s.ComputeTranslatorSummaryCols(0, pilosa.NewTopology(&pilosa.Jmphasher{}, pilosa.DefaultPartitionN, 1, nil))
if err != nil {
panic(err)
}
nkey := sum.KeyCount
nid := sum.IDCount
observedChecksum := sum.Checksum
if nkey != n {
panic("wrong key count")
}
if nkey != nid {
panic("key count should match id count")
}
// shardwidth 22 has different hashes, of course.
if pilosa.ShardWidth == 20 {
expectedChecksum := expect[n]
if observedChecksum != expectedChecksum {
panic(fmt.Sprintf("got wrong checksum obs '%#v' vs expected '%#v'", observedChecksum, expectedChecksum))
}
}
}
}
func TestTranslateStore_RepairNonInvertibleStringKeyTranslation(t *testing.T) {
const N = 6
// before repair
var fwd [N]map[string]uint64
var rev [N]map[uint64]string
// after repair
var fwd2 [N]map[string]uint64
var rev2 [N]map[uint64]string
// case 0: forward is messed up (unlikely but check for it anyway, be sure we can repair)
// "key0" -> id 0 // correct.
// "key1" -> id 0 // wrong. after Repair, should see key1 -> 1 (0xec0002)
//
// id 0 -> "key0" // correct
// id 1 -> "key1" // correct
//
fwd[0] = map[string]uint64{"key0": 0xec00001, "key1": 0xec00001}
rev[0] = map[uint64]string{0xec00001: "key0", 0xec00002: "key1"}
fwd2[0] = map[string]uint64{"key0": 0xec00001, "key1": 0xec00002}
rev2[0] = map[uint64]string{0xec00001: "key0", 0xec00002: "key1"}
// case 1: reverse is messed up (we have seen this in the past)
// "key0" -> id 0 // correct
// "key1" -> id 1 // correct
//
// id 0 -> "key0" // correct.
// id 1 -> "key0" // wrong. after Repair, should see id 1 -> "key1"
//
fwd[1] = map[string]uint64{"key0": 0xec00001, "key1": 0xec00002}
rev[1] = map[uint64]string{0xec00001: "key0", 0xec00002: "key0"}
fwd2[1] = map[string]uint64{"key0": 0xec00001, "key1": 0xec00002}
rev2[1] = map[uint64]string{0xec00001: "key0", 0xec00002: "key1"}
// case 2: only present in reverse.
fwd[2] = map[string]uint64{}
rev[2] = map[uint64]string{0xec00001: "key0"}
fwd2[2] = map[string]uint64{"key0": 0xec00001}
rev2[2] = map[uint64]string{0xec00001: "key0"}
// case 3: same thing. with camoflage.
fwd[3] = map[string]uint64{"key1": 0xec00002}
rev[3] = map[uint64]string{0xec00001: "key0", 0xec00002: "key1"}
fwd2[3] = map[string]uint64{"key0": 0xec00001, "key1": 0xec00002}
rev2[3] = map[uint64]string{0xec00001: "key0", 0xec00002: "key1"}
// case 4: only present in forward.
fwd[4] = map[string]uint64{"key0": 0xec00001}
rev[4] = map[uint64]string{}
fwd2[4] = map[string]uint64{"key0": 0xec00001}
rev2[4] = map[uint64]string{0xec00001: "key0"}
// case 5: same thing. with camoflage.
fwd[5] = map[string]uint64{"key0": 0xec00001}
rev[5] = map[uint64]string{0xec00002: "key1"}
fwd2[5] = map[string]uint64{"key0": 0xec00001, "key1": 0xec00002}
rev2[5] = map[uint64]string{0xec00001: "key0", 0xec00002: "key1"}
// case 6: we had an id, but b/c of the fix, that id is no longer used.
// now that id might still be used in the fragment for a column,
// and so we will need to remove that id/column from the fragment.
// encapsulated: "did it affect the state of the fields?"
for i := 0; i < 5; i++ {
//println("i = ", i)
s := MustOpenNewTranslateStore()
defer MustCloseTranslateStore(s)
if err := s.SetFwdRevMaps(nil, fwd[i], rev[i]); err != nil {
t.Fatal(err)
}
if err := verifyState("setup", i, s, fwd[i], rev[i]); err != nil {
t.Fatal(err)
}
var topo *pilosa.Topology
verbose := false
applyKeyRepairs := true
changed, err := s.RepairKeys(topo, verbose, applyKeyRepairs)
if err != nil {
t.Fatal(err)
}
if !changed {
t.Fatalf("expected changes!")
}
if err := verifyState("afterRepair", i, s, fwd2[i], rev2[i]); err != nil {
t.Fatal(err)
}
}
}
func verifyState(label string, i int, s *boltdb.TranslateStore, fwd map[string]uint64, rev map[uint64]string) error {
// verify the setup
const writable = true
for key, expectID := range fwd {
id, err := s.TranslateKey(key, !writable)
if err != nil {
return err
}
if id != expectID {
return fmt.Errorf("fwd %v problem. i=%v, for key '%v', expected %x, observed %x", label, i, key, expectID, id)
}
}
for id, expectKey := range rev {
key, err := s.TranslateID(id)
if err != nil {
return err
}
if key != expectKey {
return fmt.Errorf("rev %v problem. i=%v, for id '%x', expected %v, observed %v", label, i, id, expectKey, key)
}
}
return nil
}

View file

@ -69,6 +69,7 @@ const (
messageTypeRecalculateCaches
messageTypeNodeEvent
messageTypeNodeStatus
messageTypeTransaction
)
// MarshalInternalMessage serializes the pilosa message and adds pilosa internal
@ -116,6 +117,8 @@ func getMessage(typ byte) Message {
return &NodeEvent{}
case messageTypeNodeStatus:
return &NodeStatus{}
case messageTypeTransaction:
return &TransactionMessage{}
default:
panic(fmt.Sprintf("unknown message type %d", typ))
}
@ -155,6 +158,8 @@ func getMessageType(m Message) byte {
return messageTypeNodeEvent
case *NodeStatus:
return messageTypeNodeStatus
case *TransactionMessage:
return messageTypeTransaction
default:
panic(fmt.Sprintf("don't have type for message %#v", m))
}

296
bsi.go Normal file
View file

@ -0,0 +1,296 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"math/bits"
"github.com/pilosa/pilosa/v2/roaring"
)
// bsiData contains BSI-structured data.
type bsiData []*Row
// pivotDescending loops over nonzero BSI values in descending order.
// For each value, the provided function is called with the value and a slice of the associated columns.
// If limit or offset are not-nil, they will be applied.
// Applying a limit or offset may modify the pointed-to value.
func (bsi bsiData) pivotDescending(filter *Row, branch uint64, limit, offset *uint64, fn func(uint64, ...uint64)) {
// This "pivot" algorithm works by treating the BSI data as a tree.
// Each branch of this tree corresponds to a power-of-2-sized range of BSI values.
// Each range is subdivided into 2 ranges of half size, which form lower branches.
// Eventually, a range of width 1 cannot be subdivided and forms a leaf.
// At each branch and leaf, there is a bitmap of all columns within the corresponding range.
// The lower branches are formed as a difference or intersect of the upper branch's bitmap with the BSI bit that subdivides the range.
// This function uses a depth-first search over this virtual tree.
switch {
case !filter.Any():
// There are no remaining data.
case offset != nil && *offset >= filter.Count():
// Skip this entire branch.
*offset -= filter.Count()
case limit != nil && *limit == 0:
// The limit has been reached.
// No more data is necessary.
case len(bsi) == 0:
// This is a leaf node.
cols := filter.Columns()
if offset != nil {
cols = cols[*offset:]
*offset = 0
}
if limit != nil {
if *limit < uint64(len(cols)) {
cols = cols[:*limit]
}
*limit -= uint64(len(cols))
}
fn(branch, cols...)
default:
// Pivot over the highest bit.
upperBranch, lowerBranch := branch|(1<<uint(len(bsi)-1)), branch
splitBit := bsi[len(bsi)-1]
lowerBits := bsi[:len(bsi)-1]
lowerBits.pivotDescending(filter.Intersect(splitBit), upperBranch, limit, offset, fn)
lowerBits.pivotDescending(filter.Difference(splitBit), lowerBranch, limit, offset, fn)
}
}
/*
// distribution generates a BSI histogram for the input.
// TODO: I forgot what I was going to use this for.
// Could probbably use this for:
// - quartile queries
// - TopN on int
func (bsi bsiData) distribution(filter *Row) bsiData {
var dist bsiData
bsi.pivotDescending(filter, 0, nil, nil, func(count uint64, values ...uint64) {
dist.insert(count, uint64(len(values)))
})
return dist
}
*/
var placeholderBitmap = roaring.NewBitmap()
// addBSI adds two BSI bitmaps together.
// It does not handle sign and has no concept of overflow.
func addBSI(x, y bsiData) bsiData {
// Accumulate row segments.
segments := make([][]rowSegment, len(x)+len(y))
xsegs, ysegs := segments[:len(x)], segments[len(x):]
for i, r := range x {
xsegs[i] = r.segments
}
for i, r := range y {
ysegs[i] = r.segments
}
var dst bsiData
var xbitmaps, ybitmaps []*roaring.Bitmap
for {
// Find the next shard.
next := ^uint64(0)
for _, s := range segments {
if len(s) == 0 {
continue
}
shard := s[0].shard
if shard < next {
next = shard
}
}
if next == ^uint64(0) {
// There are no remaining shards.
break
}
// Accumulate bitmaps for this shard.
xbitmaps, ybitmaps = xbitmaps[:0], ybitmaps[:0]
for i, segs := range xsegs {
if len(segs) == 0 || segs[0].shard != next {
continue
}
xsegs[i] = segs[1:]
bm := segs[0].data
if !bm.Any() {
continue
}
for len(xbitmaps) < i {
xbitmaps = append(xbitmaps, placeholderBitmap)
}
xbitmaps = append(xbitmaps, bm)
}
for i, segs := range ysegs {
if len(segs) == 0 || segs[0].shard != next {
continue
}
ysegs[i] = segs[1:]
bm := segs[0].data
if !bm.Any() {
continue
}
for len(ybitmaps) < i {
ybitmaps = append(ybitmaps, placeholderBitmap)
}
ybitmaps = append(ybitmaps, bm)
}
// Add the shard values together.
var out []*roaring.Bitmap
switch {
case len(xbitmaps) == 0:
// There are no values in x.
out = ybitmaps
case len(ybitmaps) == 0:
// There are no values in y.
out = xbitmaps
default:
out = roaring.Add(xbitmaps, ybitmaps)
}
// Convert the bitmaps to output segments.
for i, b := range out {
if !b.Any() {
continue
}
for len(dst) <= i {
dst = append(dst, NewRow())
}
dst[i].segments = append(dst[i].segments, rowSegment{
shard: next,
writable: true,
data: b,
n: b.Count(),
})
}
}
return dst
}
// rowBuilder builds a row quickly from individual values.
// It is optimized for the case in which values are generated sequentially.
type rowBuilder struct {
bm *roaring.Bitmap
mask *[1024]uint64
array []uint16
key uint64
n int32
}
// flushKey flushes the data at the current key to the bitmap.
func (b *rowBuilder) flushKey() {
var c *roaring.Container
switch {
case b.mask != nil:
c = roaring.NewContainerBitmapN(b.mask[:], b.n)
b.mask = nil
case len(b.array) > 0:
c = roaring.NewContainerArrayCopy(b.array)
b.array = b.array[:0]
default:
return
}
if b.bm == nil {
b.bm = roaring.NewBitmap()
}
if old := b.bm.Containers.Get(b.key); old != nil {
c = roaring.Union(c, old)
}
b.bm.Containers.Put(b.key, c)
}
// Add a value to the bitmap.
// Values must be added sequentially.
func (b *rowBuilder) Add(v uint64) {
vkey := v / (1 << 16)
if b.key != vkey {
// This is a new key, so flush the old one.
b.flushKey()
b.key = vkey
}
if b.mask != nil {
// Add to the mask.
b.n += int32(1 &^ (b.mask[uint16(v)/64] >> (v % 64)))
b.mask[uint16(v)/64] |= 1 << (v % 64)
return
}
// Add to an array.
b.array = append(b.array, uint16(v))
if len(b.array) >= roaring.ArrayMaxSize {
// The array is too big.
// Convert it to a bitmask.
m := [1024]uint64{}
for _, v := range b.array {
m[v/64] |= 1 << (v % 64)
}
b.n = int32(len(b.array))
b.array = b.array[:0]
b.mask = &m
}
}
// Build a Row from stored data.
// This resets the builder.
func (b *rowBuilder) Build() *Row {
// Flush the active key to the bitmap.
b.flushKey()
// Remove the bitmap and convert it to a Row.
bm := b.bm
b.bm = nil
if bm == nil {
return NewRow()
}
return NewRowFromBitmap(bm)
}
// bsiBuilder assembles BSI data.
// It is optimized for the case in which values are generated sequentially.
type bsiBuilder []rowBuilder
// Insert a value into the BSI data.
// Columns must be inserted sequentially, and duplicates are not allowed.
func (b *bsiBuilder) Insert(col, val uint64) {
for val != 0 {
i := bits.TrailingZeros64(val)
val &^= 1 << i
for len(*b) <= i {
*b = append(*b, rowBuilder{})
}
(*b)[i].Add(col)
}
}
// Build BSI data.
// This resets the builder.
func (b *bsiBuilder) Build() bsiData {
builders := *b
*b = builders[:0]
rows := make(bsiData, len(builders))
for i := range builders {
rows[i] = builders[i].Build()
}
return rows
}

175
bsi_test.go Normal file
View file

@ -0,0 +1,175 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"fmt"
"math/rand"
"sort"
"testing"
)
// TestBSIAdd does a number of iterations. For each iteration, it
// generates a random number of ids, and two random values for each id
// to add together.
func TestBSIAdd(t *testing.T) {
// TODO wouldn't it be cool if our test suite had a randomized
// burn-in mode where you could run any test which supported it
// with a random seed and way more iterations?
rnd := rand.New(rand.NewSource(99))
//numZipf := rand.NewZipf(rnd, 1.5, 2, ShardWidth-1)
idZipf := rand.NewZipf(rnd, 1.8, 4, ShardWidth)
var builderA, builderB bsiBuilder
// a and b are generated slices of numbers to add together
var a, b []uint64
// idToIndex maps record ids to indexes in a and b
idToIndex := make(map[int]int)
// indexToID has the record id for each value in a and b
indexToID := []uint64{}
min := 999999999
max := 0
for iteration := 0; iteration < 1; iteration++ {
t.Run(fmt.Sprintf("%d", iteration), func(t *testing.T) {
// reset generated data
a, b = a[:0], b[:0]
indexToID = indexToID[:0]
for k := range idToIndex {
delete(idToIndex, k)
}
// z generates the values, they can be fairly large, but are usually small
z := rand.NewZipf(rnd, 1.3, 7, 1<<44)
id := -1
for i := 0; true; i++ {
// get the next id, skipping a random amount
id = id + int(idZipf.Uint64()+1)
if id >= ShardWidth {
if i < min {
min = i
}
if max < i {
max = i
}
t.Log("num: ", i)
break
}
idToIndex[id] = int(i)
indexToID = append(indexToID, uint64(id))
// append a random value to each data slice
a = append(a, z.Uint64())
b = append(b, z.Uint64())
}
// build the BSIs based on the data slices and generated IDs
for index, id := range indexToID {
va, vb := a[index], b[index]
builderA.Insert(uint64(id), va)
builderB.Insert(uint64(id), vb)
}
dataA, dataB := builderA.Build(), builderB.Build()
dataC := addBSI(dataA, dataB)
// build results from added bsiData; results[i] should hold a[i]+b[i]
results := make([]uint64, len(a))
dataC.pivotDescending(NewRow().Union(dataC...), 0, nil, nil, func(count uint64, ids ...uint64) {
for _, id := range ids {
results[idToIndex[int(id)]] = count
}
})
for i, res := range results {
if res != a[i]+b[i] {
t.Errorf("Mismatch at %d\na: %v\nb: %v\nr: %v", i, a, b, results)
}
}
})
}
t.Log("min", min)
t.Log("max", max)
}
type bsiAddCase struct {
positions []uint64
a []uint64
b []uint64
}
func (b bsiAddCase) Len() int {
return len(b.positions)
}
// Less reports whether the element with
// index i should sort before the element with index j.
func (b bsiAddCase) Less(i, j int) bool {
return b.positions[i] < b.positions[j]
}
// Swap swaps the elements with indexes i and j.
func (b bsiAddCase) Swap(i, j int) {
b.positions[i], b.positions[j] = b.positions[j], b.positions[i]
b.a[i], b.a[j] = b.a[j], b.a[i]
b.b[i], b.b[j] = b.b[j], b.b[i]
}
// TestBSIAddCases tests specific cases of bsiAdd (would generally be
// pulled from randomly generated ones from TestBSIAdd upon failure).
func TestBSIAddCases(t *testing.T) {
tests := []bsiAddCase{
{
positions: []uint64{161311, 611110, 82544, 996022, 836077, 64964, 480737, 156534, 240525, 580896, 239236, 54607, 1019438, 894260, 17570, 884645, 936658, 682651, 987695, 390274},
a: []uint64{17, 1, 2846, 45437619, 23781, 36, 88, 168691, 13417, 1301, 10, 71, 0, 176, 1010, 21, 1, 509, 17, 4},
b: []uint64{24, 288, 12737, 14, 150, 21, 24, 354, 0, 19, 5, 150, 3940, 121, 25, 621, 7, 9023592401, 6033, 7},
},
{
positions: []uint64{17570, 54607},
a: []uint64{1010, 71},
b: []uint64{25, 150},
},
}
var builderA, builderB bsiBuilder
for i, tst := range tests {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
if len(tst.a) != len(tst.b) || len(tst.a) != len(tst.positions) {
t.Fatalf("Malformed test, a is %d, but b is %d", len(tst.a), len(tst.b))
}
sort.Sort(tst)
for i := 0; i < len(tst.a); i++ {
builderA.Insert(tst.positions[i], tst.a[i])
builderB.Insert(tst.positions[i], tst.b[i])
}
dataA, dataB := builderA.Build(), builderB.Build()
dataC := addBSI(dataA, dataB)
// maps id to count
results := make(map[uint64]uint64)
dataC.pivotDescending(NewRow().Union(dataC...), 0, nil, nil, func(count uint64, ids ...uint64) {
for _, id := range ids {
results[id] = count
}
})
for i, id := range tst.positions {
if results[id] != tst.a[i]+tst.b[i] {
t.Fatalf("value %d mismatch, id: %d. got %d, want %d", i, id, results[id], tst.a[i]+tst.b[i])
}
}
})
}
}

232
cache.go
View file

@ -16,6 +16,7 @@ package pilosa
import (
"bytes"
"encoding/json"
"fmt"
"io"
"sort"
@ -23,7 +24,9 @@ import (
"time"
"github.com/pilosa/pilosa/v2/lru"
pb "github.com/pilosa/pilosa/v2/proto"
"github.com/pilosa/pilosa/v2/stats"
"github.com/pkg/errors"
)
const (
@ -118,7 +121,8 @@ func (c *lruCache) Top() []bitmapPair {
Count: n,
})
}
sort.Sort(bitmapPairs(a))
pairs := bitmapPairs(a)
sort.Sort(&pairs)
return a
}
@ -134,9 +138,11 @@ var _ cache = &lruCache{}
// rankCache represents a cache with sorted entries.
type rankCache struct {
mu sync.Mutex
entries map[uint64]uint64
rankings []bitmapPair // cached, ordered list
mu sync.Mutex
entries map[uint64]uint64
rankings bitmapPairs // cached, ordered list
rankingsRead bool
dirty bool
updateN int
updateTime time.Time
@ -168,10 +174,16 @@ func NewRankCache(maxEntries uint32) *rankCache {
func (c *rankCache) Add(id uint64, n uint64) {
c.mu.Lock()
defer c.mu.Unlock()
// Flag the cache as dirty.
// This forces recalculation if top is called before the cache is recalculated.
c.dirty = true
// Ignore if the column count is below the threshold,
// unless the count is 0, which is effectively used
// to clear the cache value.
if n < c.thresholdValue && n > 0 {
delete(c.entries, id)
return
}
@ -184,7 +196,13 @@ func (c *rankCache) Add(id uint64, n uint64) {
func (c *rankCache) BulkAdd(id uint64, n uint64) {
c.mu.Lock()
defer c.mu.Unlock()
// Flag the cache as dirty.
// This forces recalculation if top is called before the cache is recalculated.
c.dirty = true
if n < c.thresholdValue {
delete(c.entries, id)
return
}
@ -209,12 +227,15 @@ func (c *rankCache) Len() int {
func (c *rankCache) IDs() []uint64 {
c.mu.Lock()
defer c.mu.Unlock()
a := make([]uint64, 0, len(c.entries))
for id := range c.entries {
a = append(a, id)
if len(c.entries) == 0 {
return nil
}
sort.Sort(uint64Slice(a))
return a
ids := make([]uint64, 0, len(c.entries))
for id := range c.entries {
ids = append(ids, id)
}
sort.Sort(uint64Slice(ids))
return ids
}
// Invalidate recalculates the entries by rank.
@ -228,7 +249,7 @@ func (c *rankCache) Invalidate() {
func (c *rankCache) Recalculate() {
c.mu.Lock()
defer c.mu.Unlock()
c.stats.Count("cache.recalculate", 1, 1.0)
c.stats.Count(MetricRecalculateCache, 1, 1.0)
c.recalculate()
}
@ -236,27 +257,40 @@ func (c *rankCache) invalidate() {
// Don't invalidate more than once every X seconds.
// TODO: consider making this configurable.
if time.Since(c.updateTime).Seconds() < 10 {
// Skipping recalculation means that the ranked cache's growth is unbounded.
// This is somewhat necessary for now since recalculation is not cheap.
// The cache will remain flagged as dirty and will be recalculated if Top is called.
// This may cause unexpected memory growth, so record it in metrics for debugging purposes.
c.stats.Count(MetricInvalidateCacheSkipped, 1, 1.0)
return
}
c.stats.Count("cache.invalidate", 1, 1.0)
c.stats.Count(MetricInvalidateCache, 1, 1.0)
c.recalculate()
}
func (c *rankCache) recalculate() {
if c.rankingsRead {
c.rankings = nil
c.rankingsRead = false
}
// Convert cache to a sorted list.
rankings := make([]bitmapPair, 0, len(c.entries))
rankings := c.rankings[:0]
if cap(rankings) < len(c.entries) {
rankings = make([]bitmapPair, 0, len(c.entries))
}
for id, cnt := range c.entries {
rankings = append(rankings, bitmapPair{
ID: id,
Count: cnt,
})
}
sort.Sort(bitmapPairs(rankings))
c.rankings = rankings
sort.Sort(&c.rankings)
// Store the count of the item at the threshold index.
c.rankings = rankings
length := len(c.rankings)
c.stats.Gauge("RankCache", float64(length), 1.0)
c.stats.Gauge(MetricRankCacheLength, float64(length), 1.0)
var removeItems []bitmapPair // cached, ordered list
if length > int(c.maxEntries) {
@ -272,11 +306,14 @@ func (c *rankCache) recalculate() {
// If size is larger than the threshold then trim it.
if len(c.entries) > c.thresholdBuffer {
c.stats.Count("cache.threshold", 1, 1.0)
c.stats.Count(MetricCacheThresholdReached, 1, 1.0)
for _, pair := range removeItems {
delete(c.entries, pair.ID)
}
}
// The cache is no longer dirty.
c.dirty = false
}
// SetStats defines the stats client used in the cache.
@ -285,7 +322,19 @@ func (c *rankCache) SetStats(s stats.StatsClient) {
}
// Top returns an ordered list of pairs.
func (c *rankCache) Top() []bitmapPair { return c.rankings }
func (c *rankCache) Top() []bitmapPair {
c.mu.Lock()
defer c.mu.Unlock()
if c.dirty {
// The cache is dirty, so we need to recalculate it to get a consistent view.
c.stats.Count(MetricReadDirtyCache, 1, 1.0)
c.recalculate()
}
c.rankingsRead = true
return c.rankings
}
// WriteTo writes the cache to w.
func (c *rankCache) WriteTo(w io.Writer) (n int64, err error) {
@ -309,17 +358,68 @@ type bitmapPair struct {
// bitmapPairs is a sortable list of BitmapPair objects.
type bitmapPairs []bitmapPair
func (p bitmapPairs) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p bitmapPairs) Len() int { return len(p) }
func (p bitmapPairs) Less(i, j int) bool { return p[i].Count > p[j].Count }
func (p *bitmapPairs) Swap(i, j int) { (*p)[i], (*p)[j] = (*p)[j], (*p)[i] }
func (p *bitmapPairs) Len() int { return len(*p) }
func (p *bitmapPairs) Less(i, j int) bool { return (*p)[i].Count > (*p)[j].Count }
// Pair holds an id/count pair.
type Pair struct {
ID uint64 `json:"id"`
Key string `json:"key,omitempty"`
Key string `json:"key"`
Count uint64 `json:"count"`
}
// PairField is a Pair with its associated field.
type PairField struct {
Pair Pair
Field string
}
func (p PairField) Clone() (r PairField) {
return PairField{
Pair: p.Pair,
Field: p.Field,
}
}
// ToTable implements the ToTabler interface.
func (p PairField) ToTable() (*pb.TableResponse, error) {
return pb.RowsToTable(p, 1)
}
// ToRows implements the ToRowser interface.
func (p PairField) ToRows(callback func(*pb.RowResponse) error) error {
if p.Pair.Key != "" {
return callback(&pb.RowResponse{
Headers: []*pb.ColumnInfo{
{Name: p.Field, Datatype: "string"},
{Name: "count", Datatype: "uint64"},
},
Columns: []*pb.ColumnResponse{
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_StringVal{StringVal: p.Pair.Key}},
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Uint64Val{Uint64Val: p.Pair.Count}},
},
})
} else {
return callback(&pb.RowResponse{
Headers: []*pb.ColumnInfo{
{Name: p.Field, Datatype: "uint64"},
{Name: "count", Datatype: "uint64"},
},
Columns: []*pb.ColumnResponse{
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Uint64Val{Uint64Val: p.Pair.ID}},
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Uint64Val{Uint64Val: p.Pair.Count}},
},
})
}
}
// MarshalJSON marshals PairField into a JSON-encoded byte slice,
// excluding `Field`.
func (p PairField) MarshalJSON() ([]byte, error) {
return json.Marshal(p.Pair)
}
// Pairs is a sortable slice of Pair objects.
type Pairs []Pair
@ -395,6 +495,82 @@ func (p Pairs) String() string {
return buf.String()
}
// PairsField is a Pairs object with its associated field.
type PairsField struct {
Pairs []Pair
Field string
}
func (p *PairsField) Clone() (r *PairsField) {
r = &PairsField{
Pairs: make([]Pair, len(p.Pairs)),
Field: p.Field,
}
copy(r.Pairs, p.Pairs)
return
}
// ToTable implements the ToTabler interface.
func (p *PairsField) ToTable() (*pb.TableResponse, error) {
return pb.RowsToTable(p, len(p.Pairs))
}
// ToRows implements the ToRowser interface.
func (p *PairsField) ToRows(callback func(*pb.RowResponse) error) error {
// Determine if the ID has string keys.
var stringKeys bool
if len(p.Pairs) > 0 {
if p.Pairs[0].Key != "" {
stringKeys = true
}
}
dtype := "uint64"
if stringKeys {
dtype = "string"
}
ci := []*pb.ColumnInfo{
{Name: p.Field, Datatype: dtype},
{Name: "count", Datatype: "uint64"},
}
for _, pair := range p.Pairs {
if stringKeys {
if err := callback(&pb.RowResponse{
Headers: ci,
Columns: []*pb.ColumnResponse{
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_StringVal{StringVal: pair.Key}},
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Uint64Val{Uint64Val: uint64(pair.Count)}},
}}); err != nil {
return errors.Wrap(err, "calling callback")
}
} else {
if err := callback(&pb.RowResponse{
Headers: ci,
Columns: []*pb.ColumnResponse{
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Uint64Val{Uint64Val: uint64(pair.ID)}},
&pb.ColumnResponse{ColumnVal: &pb.ColumnResponse_Uint64Val{Uint64Val: uint64(pair.Count)}},
}}); err != nil {
return errors.Wrap(err, "calling callback")
}
}
ci = nil //only send on the first
}
return nil
}
// MarshalJSON marshals PairsField into a JSON-encoded byte slice,
// excluding `Field`.
func (p PairsField) MarshalJSON() ([]byte, error) {
return json.Marshal(p.Pairs)
}
// int64Slice represents a sortable slice of int64 numbers.
type int64Slice []int64
func (p int64Slice) Len() int { return len(p) }
func (p int64Slice) Less(i, j int) bool { return p[i] < p[j] }
func (p int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// uint64Slice represents a sortable slice of uint64 numbers.
type uint64Slice []uint64
@ -431,13 +607,7 @@ func (p uint64Slice) merge(other []uint64) []uint64 {
return ret
}
// bitmapCache provides an interface for caching full bitmaps.
type bitmapCache interface {
Fetch(id uint64) (*Row, bool)
Add(id uint64, b *Row)
}
// simpleCache implements BitmapCache
// simpleCache implements a bitmap Rowcache.
// it is meant to be a short-lived cache for cases where writes are continuing to access
// the same row within a short time frame (i.e. good for write-heavy loads)
// A read-heavy use case would cause the cache to get bigger, potentially causing the
@ -452,6 +622,12 @@ func (s *simpleCache) Fetch(id uint64) (*Row, bool) {
return m, ok
}
func newSimpleCache() *simpleCache {
return &simpleCache{
cache: make(map[uint64]*Row),
}
}
// Add adds the bitmap to the cache, keyed on the id. A nil row means
// deleting the row from the cache.
func (s *simpleCache) Add(id uint64, b *Row) {

View file

@ -15,13 +15,14 @@
package pilosa_test
import (
"reflect"
"testing"
"github.com/pilosa/pilosa/v2"
)
// Ensure a bitmap query can be executed.
func TestCache_Rank(t *testing.T) {
// Ensure cache stays constrained to its configured size.
func TestCache_Rank_Size(t *testing.T) {
cacheSize := uint32(3)
cache := pilosa.NewRankCache(cacheSize)
for i := 1; i < int(2*cacheSize); i++ {
@ -31,5 +32,54 @@ func TestCache_Rank(t *testing.T) {
if cache.Len() != int(cacheSize) {
t.Fatalf("unexpected cache Size: %d!=%d expected\n", cache.Len(), cacheSize)
}
}
// Ensure cache entries set below threshold are handled appropriately.
func TestCache_Rank_Threshold(t *testing.T) {
cacheSize := uint32(5)
cache := pilosa.NewRankCache(cacheSize)
for i := 1; i < int(2*cacheSize); i++ {
cache.Add(uint64(i), 3)
}
// Set the cache value for rows 4 and 5 to a number below the threshold
// value (which is 3), and ensure that they gets zeroed out.
cache.Add(4, 1)
cache.BulkAdd(5, 1)
cache.Recalculate()
if cache.Get(4) != 0 {
t.Fatalf("unexpected cache value after Add: %d!=%d expected\n", cache.Get(4), 0)
}
if cache.Get(5) != 0 {
t.Fatalf("unexpected cache value after BulkAdd: %d!=%d expected\n", cache.Get(5), 0)
}
}
// Test that consecutive writes show up in Top.
// On later writes, the cache skips recalculation to save CPU time.
// This used to mean that the later writes would not show up in Top.
// Now, the cache is flagged as dirty and recalculated during the call to Top.
func TestCache_Rank_Dirty(t *testing.T) {
cacheSize := uint32(5)
cache := pilosa.NewRankCache(cacheSize)
type pair struct{ ID, Count uint64 }
expect := []pair{
{5, 2},
{4, 1},
}
for _, v := range expect {
cache.Add(v.ID, v.Count)
}
var got []pair
for _, p := range cache.Top() {
got = append(got, pair(p))
}
if !reflect.DeepEqual(expect, got) {
t.Fatalf("wrote %v but got %v", expect, got)
}
}

323
catcher.go Normal file
View file

@ -0,0 +1,323 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"fmt"
"io"
"github.com/pilosa/pilosa/v2/roaring"
txkey "github.com/pilosa/pilosa/v2/short_txkey"
//txkey "github.com/pilosa/pilosa/v2/txkey"
)
// catcher is useful to report error locations with a
// stack dump before the complexity
// of the executor_test swallows up
// the location of a panic.
type catcherTx struct {
b Tx
}
func newCatcherTx(b Tx) *catcherTx {
return &catcherTx{b: b}
}
func init() {
// keep golangci-lint happy
_ = newCatcherTx
}
var _ Tx = (*catcherTx)(nil)
func (c *catcherTx) IncrementOpN(index, field, view string, shard uint64, changedN int) {
c.b.IncrementOpN(index, field, view, shard, changedN)
}
func (c *catcherTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator {
return c.b.NewTxIterator(index, field, view, shard)
}
func (c *catcherTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see ImportRoaringBits() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.ImportRoaringBits(index, field, view, shard, rit, clear, log, rowSize, data)
}
func (c *catcherTx) Dump(short bool, shard uint64) {
c.b.Dump(short, shard)
}
func (c *catcherTx) Readonly() bool {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see Readonly() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.Readonly()
}
func (tx *catcherTx) Pointer() string {
return fmt.Sprintf("%p", tx)
}
func (c *catcherTx) Rollback() {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see Rollback() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
c.b.Rollback()
}
func (c *catcherTx) Commit() error {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see Commit() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.Commit()
}
func (c *catcherTx) RoaringBitmap(index, field, view string, shard uint64) (*roaring.Bitmap, error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see RoaringBitmap() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.RoaringBitmap(index, field, view, shard)
}
func (c *catcherTx) Container(index, field, view string, shard uint64, key uint64) (ct *roaring.Container, err error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see Container() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.Container(index, field, view, shard, key)
}
func (c *catcherTx) PutContainer(index, field, view string, shard uint64, key uint64, rc *roaring.Container) error {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see PutContainer() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.PutContainer(index, field, view, shard, key, rc)
}
func (c *catcherTx) RemoveContainer(index, field, view string, shard uint64, key uint64) error {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see RemoveContainer() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.RemoveContainer(index, field, view, shard, key)
}
func (c *catcherTx) UseRowCache() bool {
return c.b.UseRowCache()
}
func (c *catcherTx) IsDone() bool {
return c.b.IsDone()
}
func (c *catcherTx) Add(index, field, view string, shard uint64, batched bool, a ...uint64) (changeCount int, err error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see Add() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.Add(index, field, view, shard, batched, a...)
}
func (c *catcherTx) Remove(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see Remove() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.Remove(index, field, view, shard, a...)
}
func (c *catcherTx) Contains(index, field, view string, shard uint64, key uint64) (exists bool, err error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see Contains() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.Contains(index, field, view, shard, key)
}
func (c *catcherTx) ContainerIterator(index, field, view string, shard uint64, firstRoaringContainerKey uint64) (citer roaring.ContainerIterator, found bool, err error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see ContainerIterator() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.ContainerIterator(index, field, view, shard, firstRoaringContainerKey)
}
func (c *catcherTx) ForEach(index, field, view string, shard uint64, fn func(i uint64) error) error {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see ForEach() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.ForEach(index, field, view, shard, fn)
}
func (c *catcherTx) ForEachRange(index, field, view string, shard uint64, start, end uint64, fn func(uint64) error) error {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see ForEachRange() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.ForEachRange(index, field, view, shard, start, end, fn)
}
func (c *catcherTx) Count(index, field, view string, shard uint64) (uint64, error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see Count() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.Count(index, field, view, shard)
}
func (c *catcherTx) Max(index, field, view string, shard uint64) (uint64, error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see Max() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.Max(index, field, view, shard)
}
func (c *catcherTx) Min(index, field, view string, shard uint64) (uint64, bool, error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see Min() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.Min(index, field, view, shard)
}
func (c *catcherTx) UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see UnionInPlace() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.UnionInPlace(index, field, view, shard, others...)
}
func (c *catcherTx) CountRange(index, field, view string, shard uint64, start, end uint64) (n uint64, err error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see CountRange() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.CountRange(index, field, view, shard, start, end)
}
func (c *catcherTx) OffsetRange(index, field, view string, shard, offset, start, end uint64) (other *roaring.Bitmap, err error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see OffsetRange() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.OffsetRange(index, field, view, shard, offset, start, end)
}
func (c *catcherTx) RoaringBitmapReader(index, field, view string, shard uint64, fragmentPathForRoaring string) (r io.ReadCloser, sz int64, err error) {
defer func() {
if r := recover(); r != nil {
AlwaysPrintf("see RoaringBitmapReader() panic '%v' at '%v'", r, stack())
panic(r)
}
}()
return c.b.RoaringBitmapReader(index, field, view, shard, fragmentPathForRoaring)
}
func (c *catcherTx) Type() string {
return c.b.Type()
}
func (c *catcherTx) Group() *TxGroup {
return c.b.Group()
}
func (c *catcherTx) Options() Txo {
return c.b.Options()
}
// Sn retreives the serial number of the Tx.
func (c *catcherTx) Sn() int64 {
return c.b.Sn()
}
func (c *catcherTx) ApplyFilter(index, field, view string, shard uint64, ckey uint64, filter roaring.BitmapFilter) (err error) {
return GenericApplyFilter(c, index, field, view, shard, ckey, filter)
}
func (c *catcherTx) GetSortedFieldViewList(idx *Index, shard uint64) (fvs []txkey.FieldView, err error) {
return c.b.GetSortedFieldViewList(idx, shard)
}
func (tx *catcherTx) GetFieldSizeBytes(index, field string) (uint64, error) {
return 0, nil
}

View file

@ -17,6 +17,7 @@ package pilosa
import (
"context"
"io"
"time"
)
// Bit represents the intersection of a row and a column. It can be specified by
@ -43,7 +44,11 @@ type FieldValue struct {
// something hasn't been architected correctly.
// While I understand that putting the entire Client behind an interface might require this many methods,
// I don't want to let it go unquestioned.
// Another note from Travis: I think we eventually want to unify `InternalClient` with the `go-pilosa` client.
// Doing that may obviate the need to refactor this.
type InternalClient interface {
InternalQueryClient
MaxShardByIndex(ctx context.Context) (map[string]uint64, error)
Schema(ctx context.Context) ([]*IndexInfo, error)
PostSchema(ctx context.Context, uri *URI, s *Schema, remote bool) error
@ -51,7 +56,6 @@ type InternalClient interface {
FragmentNodes(ctx context.Context, index string, shard uint64) ([]*Node, error)
Nodes(ctx context.Context) ([]*Node, error)
Query(ctx context.Context, index string, queryRequest *QueryRequest) (*QueryResponse, error)
QueryNode(ctx context.Context, uri *URI, index string, queryRequest *QueryRequest) (*QueryResponse, error)
Import(ctx context.Context, index, field string, shard uint64, bits []Bit, opts ...ImportOption) error
ImportK(ctx context.Context, index, field string, bits []Bit, opts ...ImportOption) error
EnsureIndex(ctx context.Context, name string, options IndexOptions) error
@ -59,6 +63,7 @@ type InternalClient interface {
EnsureFieldWithOptions(ctx context.Context, index, field string, opt FieldOptions) error
ImportValue(ctx context.Context, index, field string, shard uint64, vals []FieldValue, opts ...ImportOption) error
ImportValueK(ctx context.Context, index, field string, vals []FieldValue, opts ...ImportOption) error
ImportValue2(ctx context.Context, req *ImportValueRequest, options *ImportOptions) error
ExportCSV(ctx context.Context, index, field string, shard uint64, w io.Writer) error
CreateField(ctx context.Context, index, field string) error
CreateFieldWithOptions(ctx context.Context, index, field string, opt FieldOptions) error
@ -68,7 +73,17 @@ type InternalClient interface {
RowAttrDiff(ctx context.Context, uri *URI, index, field string, blks []AttrBlock) (map[uint64]map[string]interface{}, error)
SendMessage(ctx context.Context, uri *URI, msg []byte) error
RetrieveShardFromURI(ctx context.Context, index, field, view string, shard uint64, uri URI) (io.ReadCloser, error)
RetrieveTranslatePartitionFromURI(ctx context.Context, index string, partition int, uri URI) (io.ReadCloser, error)
ImportRoaring(ctx context.Context, uri *URI, index, field string, shard uint64, remote bool, req *ImportRoaringRequest) error
ImportColumnAttrs(ctx context.Context, uri *URI, index string, req *ImportColumnAttrsRequest) error
StartTransaction(ctx context.Context, id string, timeout time.Duration, exclusive bool) (*Transaction, error)
FinishTransaction(ctx context.Context, id string) (*Transaction, error)
Transactions(ctx context.Context) (map[string]*Transaction, error)
GetTransaction(ctx context.Context, id string) (*Transaction, error)
GetNodeUsage(ctx context.Context, uri *URI) (map[string]NodeUsage, error)
GetPastQueries(ctx context.Context, uri *URI) ([]PastQueryStatus, error)
}
//===============
@ -76,23 +91,57 @@ type InternalClient interface {
// InternalQueryClient is the internal interface for querying a node.
type InternalQueryClient interface {
QueryNode(ctx context.Context, uri *URI, index string, queryRequest *QueryRequest) (*QueryResponse, error)
// Trasnlate keys on the particular node. The parameter writable informs TranslateStore if we can generate a new ID if any of keys does not exist.
TranslateKeysNode(ctx context.Context, uri *URI, index, field string, keys []string, writable bool) ([]uint64, error)
TranslateIDsNode(ctx context.Context, uri *URI, index, field string, id []uint64) ([]string, error)
FindIndexKeysNode(ctx context.Context, uri *URI, index string, keys ...string) (map[string]uint64, error)
FindFieldKeysNode(ctx context.Context, uri *URI, index string, field string, keys ...string) (map[string]uint64, error)
CreateIndexKeysNode(ctx context.Context, uri *URI, index string, keys ...string) (map[string]uint64, error)
CreateFieldKeysNode(ctx context.Context, uri *URI, index string, field string, keys ...string) (map[string]uint64, error)
}
type nopInternalQueryClient struct{}
func (n *nopInternalQueryClient) QueryNode(ctx context.Context, uri *URI, index string, queryRequest *QueryRequest) (*QueryResponse, error) {
func (n nopInternalQueryClient) QueryNode(ctx context.Context, uri *URI, index string, queryRequest *QueryRequest) (*QueryResponse, error) {
return nil, nil
}
func newNopInternalQueryClient() *nopInternalQueryClient {
return &nopInternalQueryClient{}
func (n nopInternalQueryClient) TranslateKeysNode(ctx context.Context, uri *URI, index, field string, keys []string, writable bool) ([]uint64, error) {
return nil, nil
}
func (n nopInternalQueryClient) TranslateIDsNode(ctx context.Context, uri *URI, index, field string, ids []uint64) ([]string, error) {
return nil, nil
}
func (n nopInternalQueryClient) FindIndexKeysNode(ctx context.Context, uri *URI, index string, keys ...string) (map[string]uint64, error) {
return nil, nil
}
func (n nopInternalQueryClient) FindFieldKeysNode(ctx context.Context, uri *URI, index string, field string, keys ...string) (map[string]uint64, error) {
return nil, nil
}
func (n nopInternalQueryClient) CreateIndexKeysNode(ctx context.Context, uri *URI, index string, keys ...string) (map[string]uint64, error) {
return nil, nil
}
func (n nopInternalQueryClient) CreateFieldKeysNode(ctx context.Context, uri *URI, index string, field string, keys ...string) (map[string]uint64, error) {
return nil, nil
}
func newNopInternalQueryClient() nopInternalQueryClient {
return nopInternalQueryClient{}
}
var _ InternalQueryClient = newNopInternalQueryClient()
//===============
type nopInternalClient struct{}
type nopInternalClient struct{ nopInternalQueryClient }
func newNopInternalClient() nopInternalClient {
return nopInternalClient{}
@ -120,18 +169,24 @@ func (n nopInternalClient) Nodes(ctx context.Context) ([]*Node, error) {
func (n nopInternalClient) Query(ctx context.Context, index string, queryRequest *QueryRequest) (*QueryResponse, error) {
return nil, nil
}
func (n nopInternalClient) QueryNode(ctx context.Context, uri *URI, index string, queryRequest *QueryRequest) (*QueryResponse, error) {
return nil, nil
}
func (n nopInternalClient) Import(ctx context.Context, index, field string, shard uint64, bits []Bit, opts ...ImportOption) error {
return nil
}
func (n nopInternalClient) ImportK(ctx context.Context, index, field string, bits []Bit, opts ...ImportOption) error {
return nil
}
func (n nopInternalClient) ImportValue2(ctx context.Context, req *ImportValueRequest, options *ImportOptions) error {
return nil
}
func (n nopInternalClient) ImportRoaring(ctx context.Context, uri *URI, index, field string, shard uint64, remote bool, req *ImportRoaringRequest) error {
return nil
}
func (n nopInternalClient) ImportColumnAttrs(ctx context.Context, uri *URI, index string, req *ImportColumnAttrsRequest) error {
return nil
}
func (n nopInternalClient) EnsureIndex(ctx context.Context, name string, options IndexOptions) error {
return nil
}
@ -172,3 +227,27 @@ func (n nopInternalClient) SendMessage(ctx context.Context, uri *URI, msg []byte
func (n nopInternalClient) RetrieveShardFromURI(ctx context.Context, index, field, view string, shard uint64, uri URI) (io.ReadCloser, error) {
return nil, nil
}
func (n nopInternalClient) RetrieveTranslatePartitionFromURI(ctx context.Context, index string, partition int, uri URI) (io.ReadCloser, error) {
return nil, nil
}
func (n nopInternalClient) StartTransaction(ctx context.Context, id string, timeout time.Duration, exclusive bool) (*Transaction, error) {
return nil, nil
}
func (n nopInternalClient) FinishTransaction(ctx context.Context, id string) (*Transaction, error) {
return nil, nil
}
func (n nopInternalClient) Transactions(ctx context.Context) (map[string]*Transaction, error) {
return nil, nil
}
func (n nopInternalClient) GetTransaction(ctx context.Context, id string) (*Transaction, error) {
return nil, nil
}
func (n nopInternalClient) GetNodeUsage(ctx context.Context, uri *URI) (map[string]NodeUsage, error) {
return nil, nil
}
func (n nopInternalClient) GetPastQueries(ctx context.Context, uri *URI) ([]PastQueryStatus, error) {
return nil, nil
}

1306
cluster.go

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,6 @@ package pilosa
import (
"bytes"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
@ -35,6 +34,7 @@ import (
"github.com/gorilla/mux"
"github.com/pilosa/pilosa/v2/logger"
"github.com/pilosa/pilosa/v2/roaring"
"github.com/pilosa/pilosa/v2/testhook"
"github.com/pkg/errors"
)
@ -91,12 +91,17 @@ func TestFragCombos(t *testing.T) {
}
// newIndexWithTempPath returns a new instance of Index.
func newIndexWithTempPath(name string) *Index {
path, err := ioutil.TempDir(*TempDir, "pilosa-index-")
func newIndexWithTempPath(tb testing.TB, name string) *Index {
path, err := testhook.TempDirInDir(tb, *TempDir, "pilosa-index-")
if err != nil {
panic(err)
}
index, err := NewIndex(path, name)
h := NewHolder(path, nil)
panicOn(h.Open())
index, err := h.CreateIndex(name, IndexOptions{})
testhook.Cleanup(tb, func() {
h.Close()
})
if err != nil {
panic(err)
}
@ -105,7 +110,6 @@ func newIndexWithTempPath(name string) *Index {
// Ensure that fragSources creates the correct fragment mapping.
func TestFragSources(t *testing.T) {
uri0, err := NewURIFromAddress("host0")
if err != nil {
t.Fatal(err)
@ -157,27 +161,53 @@ func TestFragSources(t *testing.T) {
c5.addNodeBasicSorted(node2)
c5.addNodeBasicSorted(node3)
idx := newIndexWithTempPath("i")
idx := newIndexWithTempPath(t, "i")
defer idx.Close()
field, err := idx.CreateFieldIfNotExists("f", OptFieldTypeDefault())
if err != nil {
t.Fatal(err)
}
_, err = field.SetBit(1, 101, nil)
// Obtain transaction.
var shard uint64
tx := idx.holder.txf.NewTx(Txo{Write: writable, Index: idx, Shard: shard})
defer tx.Rollback()
_, err = field.SetBit(tx, 1, 101, nil)
if err != nil {
t.Fatal(err)
}
_, err = field.SetBit(1, ShardWidth+1, nil)
panicOn(tx.Commit())
shard = 1
tx = idx.holder.txf.NewTx(Txo{Write: writable, Index: idx, Shard: shard})
defer tx.Rollback()
_, err = field.SetBit(tx, 1, ShardWidth*shard+1, nil)
if err != nil {
t.Fatal(err)
}
_, err = field.SetBit(1, ShardWidth*2+1, nil)
panicOn(tx.Commit())
shard = 2
tx = idx.holder.txf.NewTx(Txo{Write: writable, Index: idx, Shard: shard})
defer tx.Rollback()
_, err = field.SetBit(tx, 1, ShardWidth*shard+1, nil)
if err != nil {
t.Fatal(err)
}
_, err = field.SetBit(1, ShardWidth*3+1, nil)
panicOn(tx.Commit())
shard = 3
tx = idx.holder.txf.NewTx(Txo{Write: writable, Index: idx, Shard: shard})
defer tx.Rollback()
_, err = field.SetBit(tx, 1, ShardWidth*shard+1, nil)
if err != nil {
t.Fatal(err)
}
panicOn(tx.Commit())
tests := []struct {
from *cluster
@ -351,7 +381,7 @@ func TestCluster_Partition(t *testing.T) {
c := newCluster()
c.partitionN = partitionN
partitionID := c.partition(index, shard)
partitionID := c.shardToShardPartition(index, shard)
if partitionID < 0 || partitionID >= partitionN {
t.Errorf("partition out of range: shard=%d, p=%d, n=%d", shard, partitionID, partitionN)
}
@ -381,7 +411,7 @@ func TestHasher(t *testing.T) {
{0x0ddc0ffeebadf00d, []int{0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 15, 15, 15, 15}},
} {
for i, v := range tt.bucket {
hasher := &jmphasher{}
hasher := &Jmphasher{}
if got := hasher.Hash(tt.key, i+1); got != v {
t.Errorf("hash(%v,%v)=%v, want %v", tt.key, i+1, got, v)
}
@ -391,7 +421,7 @@ func TestHasher(t *testing.T) {
// Ensure ContainsShards can find the actual shard list for node and index.
func TestCluster_ContainsShards(t *testing.T) {
c := NewTestCluster(5)
c := NewTestCluster(t, 5)
c.ReplicaN = 3
shards := c.containsShards("test", roaring.NewBitmap(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), c.nodes[2])
@ -537,7 +567,7 @@ func TestCluster_Coordinator(t *testing.T) {
}
func TestCluster_Topology(t *testing.T) {
c1 := NewTestCluster(1) // automatically creates Node{ID: "node0"}
c1 := NewTestCluster(t, 1) // automatically creates Node{ID: "node0"}
uri0 := NewTestURIFromHostPort("host0", 0)
uri1 := NewTestURIFromHostPort("host1", 0)
@ -585,7 +615,7 @@ func TestCluster_Topology(t *testing.T) {
func TestCluster_ResizeStates(t *testing.T) {
t.Run("Single node, no data", func(t *testing.T) {
tc := NewClusterCluster(1)
tc := NewClusterCluster(t, 1)
// Open TestCluster.
if err := tc.Open(); err != nil {
@ -615,7 +645,7 @@ func TestCluster_ResizeStates(t *testing.T) {
})
t.Run("Single node, in topology", func(t *testing.T) {
tc := NewClusterCluster(0)
tc := NewClusterCluster(t, 0)
if err := tc.addNode(); err != nil {
t.Fatalf("adding node: %v", err)
}
@ -647,7 +677,7 @@ func TestCluster_ResizeStates(t *testing.T) {
})
t.Run("Single node, not in topology", func(t *testing.T) {
tc := NewClusterCluster(0)
tc := NewClusterCluster(t, 0)
if err := tc.addNode(); err != nil {
t.Fatalf("adding node: %v", err)
}
@ -676,7 +706,7 @@ func TestCluster_ResizeStates(t *testing.T) {
})
t.Run("Multiple nodes, no data", func(t *testing.T) {
tc := NewClusterCluster(0)
tc := NewClusterCluster(t, 0)
if err := tc.addNode(); err != nil {
t.Fatalf("adding node: %v", err)
}
@ -718,7 +748,7 @@ func TestCluster_ResizeStates(t *testing.T) {
})
t.Run("Multiple nodes, in/not in topology", func(t *testing.T) {
tc := NewClusterCluster(0)
tc := NewClusterCluster(t, 0)
if err := tc.addNode(); err != nil {
t.Fatalf("adding node: %v", err)
}
@ -767,7 +797,7 @@ func TestCluster_ResizeStates(t *testing.T) {
})
t.Run("Multiple nodes, with data", func(t *testing.T) {
tc := NewClusterCluster(0)
tc := NewClusterCluster(t, 0)
if err := tc.addNode(); err != nil {
t.Fatalf("adding node: %v", err)
}
@ -782,6 +812,7 @@ func TestCluster_ResizeStates(t *testing.T) {
if err := tc.CreateField("i", "f", OptFieldTypeDefault()); err != nil {
t.Fatalf("creating field: %v", err)
}
// Each tc.SetBit starts and commits its own Tx.
if err := tc.SetBit("i", "f", 1, 101, nil); err != nil {
t.Fatalf("setting bit: %v", err)
}
@ -794,12 +825,21 @@ func TestCluster_ResizeStates(t *testing.T) {
node0Field := node0.holder.Field("i", "f")
node0View := node0Field.view("standard")
node0Fragment := node0View.Fragment(1)
node0Checksum := node0Fragment.Checksum()
node0Checksum, err := node0Fragment.Checksum()
if err != nil {
t.Fatal(err)
}
idx0 := node0.holder.Index("i")
if idx0 == nil {
t.Fatal(`idx0 was nil, could not retrieve Index("i")`)
}
// addNode needs to block until the resize process has completed.
if err := tc.addNode(); err != nil {
t.Fatalf("adding node: %v", err)
}
node1 := tc.Clusters[1]
// Ensure that nodes come up in state NORMAL.
@ -808,6 +848,7 @@ func TestCluster_ResizeStates(t *testing.T) {
} else if node1.State() != ClusterStateNormal {
t.Errorf("expected node1 state: %v, but got: %v", ClusterStateNormal, node1.State())
}
// INVAR: after node1.State() is normal, the rebalancing should have been done.
expectedTop := &Topology{
nodeIDs: []string{node0.Node.ID, node1.Node.ID},
@ -826,8 +867,15 @@ func TestCluster_ResizeStates(t *testing.T) {
node1View := node1Field.view("standard")
node1Fragment := node1View.Fragment(1)
idx1 := node1.holder.Index("i")
if idx1 == nil {
t.Fatal(`idx1 was nil, could not retrieve Index("i")`)
}
// Ensure checksums are the same.
if chksum := node1Fragment.Checksum(); !bytes.Equal(chksum, node0Checksum) {
if chksum, err := node1Fragment.Checksum(); err != nil {
t.Fatal(err)
} else if !bytes.Equal(chksum, node0Checksum) {
t.Fatalf("expected standard view checksum to match: %x - %x", chksum, node0Checksum)
}
@ -846,6 +894,7 @@ func TestAE(t *testing.T) {
c.abortAntiEntropy()
close(ch)
}()
defer c.abortAntiEntropyQ() // avoid leaking a goroutine.
select {
case <-ch:
return
@ -857,11 +906,13 @@ func TestAE(t *testing.T) {
t.Run("AbortBlocksInitialized", func(t *testing.T) {
c := newCluster()
c.initializeAntiEntropy()
ch := make(chan struct{})
go func() {
c.abortAntiEntropy()
close(ch)
}()
defer c.abortAntiEntropyQ() // avoid leak of goroutine.
select {
case <-ch:
t.Fatalf("aborting anti entropy on an initialized cluster didn't block")
@ -899,7 +950,7 @@ func TestAE(t *testing.T) {
// Ensures that coordinator can be changed.
func TestCluster_UpdateCoordinator(t *testing.T) {
t.Run("UpdateCoordinator", func(t *testing.T) {
c := NewTestCluster(2)
c := NewTestCluster(t, 2)
oldNode := c.nodes[0]
newNode := c.nodes[1]
@ -942,15 +993,22 @@ func TestCluster_confirmNodeDownUp(t *testing.T) {
t.Error(err)
}
uri.Port = uint16(iport)
if confirmNodeDown(uri, logger.NewVerboseLogger(os.Stdout)) {
c := newCluster()
c.logger = logger.NewVerboseLogger(os.Stdout)
if c.confirmNodeDown(uri) {
t.Errorf("expected node to be up")
}
}
func TestCluster_confirmNodeDownTimeout(t *testing.T) {
sleep := 50 * time.Millisecond
retries := 5
if testing.Short() {
t.Skip()
}
r := mux.NewRouter()
r.HandleFunc("/version", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(confirmDownSleep * time.Second * confirmDownRetries)
time.Sleep(sleep * time.Duration(retries))
fmt.Fprintln(w, "ignored")
}))
server := httptest.NewServer(r)
@ -969,21 +1027,53 @@ func TestCluster_confirmNodeDownTimeout(t *testing.T) {
t.Error(err)
}
uri.Port = uint16(iport)
if !confirmNodeDown(uri, logger.NewVerboseLogger(os.Stdout)) {
c := newCluster()
c.confirmDownSleep = sleep
c.confirmDownRetries = retries
c.logger = logger.NewVerboseLogger(os.Stdout)
if !c.confirmNodeDown(uri) {
t.Errorf("expected node to be down")
}
}
func TestCluster_confirmNodeDownDown(t *testing.T) {
if testing.Short() {
t.Skip()
}
uri := URI{}
uri.Scheme = "http"
uri.Host = "DoesntMatter"
uri.Port = 6666
c := newCluster()
c.confirmDownSleep = 50 * time.Millisecond
c.confirmDownRetries = 5
c.logger = logger.NewVerboseLogger(os.Stdout)
if !confirmNodeDown(uri, logger.NewVerboseLogger(os.Stdout)) {
if !c.confirmNodeDown(uri) {
t.Errorf("expected node to be down")
}
}
func TestCluster_GetNonPrimaryReplicas(t *testing.T) {
c := newCluster()
c.ReplicaN = 3
topo := NewTopology(c.Hasher, c.partitionN, c.ReplicaN, c)
c.Topology = topo
nNodes := 4
for i := 0; i < nNodes; i++ {
nodeID := fmt.Sprintf("node%d", i)
c.nodes = append(c.nodes, &Node{
ID: nodeID,
URI: NewTestURI("http", fmt.Sprintf("host%d", i), uint16(0)),
})
c.Topology.addID(nodeID)
}
partitionID := 256
nonPrimes := topo.GetNonPrimaryReplicas(partitionID)
m := len(nonPrimes)
if m != c.ReplicaN-1 {
t.Fatalf("expected 2 non primes, got %v", m)
}
}

154
cmd/badloader/badloader.go Normal file
View file

@ -0,0 +1,154 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"archive/tar"
"compress/gzip"
"context"
"time"
//"fmt"
"fmt"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/http"
"io"
"io/ioutil"
gohttp "net/http"
//"log"
"os"
//"path/filepath"
//"sort"
"strconv"
"strings"
)
func UploadTar(srcFile string, client *http.InternalClient) error {
t0 := time.Now()
f, err := os.Open(srcFile)
if err != nil {
return (err)
}
defer f.Close()
var tarReader *tar.Reader
if strings.HasSuffix(srcFile, "gz") {
gzf, err := gzip.NewReader(f)
if err != nil {
return err
}
tarReader = tar.NewReader(gzf)
} else {
tarReader = tar.NewReader(f)
}
viewData := make(map[string][]byte)
//given ordered by index/field/view
//trait_store/product_count__commercial_cd_or_share_certificate/views/bsig_product_count__commercial_cd_or_share_certificate/fragments/255
lastIndex := ""
lastField := ""
lastShard := uint64(0)
//vv("top of tar loop")
n := 0
for {
header, err := tarReader.Next()
if err == io.EOF {
if header != nil {
panic("header should not be nil on err io.EOF")
}
//submit any stuff we have left
if len(viewData) > 0 {
request := &pilosa.ImportRoaringRequest{
Views: viewData,
}
// Submit(lastIndex, lastField, lastShard, request)
//vv("about to submit lastIndex='%v' lastShard='%v'", lastIndex, lastShard)
uri := GetImportRoaringURI(lastIndex, lastShard)
err := client.ImportRoaring(context.Background(), uri, lastIndex, lastField, lastShard, false, request)
panicOn(err)
//vv("done with submit lastIndex='%v' lastShard='%v'", lastIndex, lastShard)
}
return nil
}
//vv("got header '%v'", header.Name)
n++
if n%500 == 0 {
vv("n = %v, progress, elapsed '%v'", n, time.Since(t0))
}
parts := strings.Split(header.Name, "/")
//vv("parts = '%#v'", parts)
index := parts[1]
field := parts[2]
view := parts[4]
shard, err := strconv.ParseUint(parts[6], 10, 64)
if err != nil {
return err
}
// TODO: shards can be loaded in parallel, so maybe farm out to a worker set of goro.
if index != lastIndex || field != lastField || shard != lastShard {
if len(viewData) > 0 {
request := &pilosa.ImportRoaringRequest{
Views: viewData,
}
//vv("about to submit lastIndex='%v' lastShard='%v'", lastIndex, lastShard)
uri := GetImportRoaringURI(lastIndex, lastShard)
panicOn(client.ImportRoaring(context.Background(), uri, lastIndex, lastField, lastShard, false, request))
viewData = make(map[string][]byte)
//vv("done with submit lastIndex='%v' lastShard='%v'; took='%v'", lastIndex, lastShard, time.Since(t0))
}
}
roaringData, err := ioutil.ReadAll(tarReader)
if err != nil {
return err
}
if _, already := viewData[view]; already {
panic(fmt.Sprintf("view '%v' already present!", view))
}
viewData[view] = roaringData
lastIndex = index
lastField = field
//lastShard = shard
//vv("bottom of loop")
}
}
// badloader reproduce a union in place issue for us. slurp is
// the new "good" loader, and should always be preferred now
// when not trying to repro that bug. pulled from 85fa67e8
func main() {
host := "127.0.0.1:10101"
h := &gohttp.Client{}
c, err := http.NewInternalClient(host, h)
panicOn(err)
tarSrcPath := "q2.tar.gz"
t0 := time.Now()
panicOn(UploadTar(tarSrcPath, c))
vv("total elapsed '%v'", time.Since(t0))
}
var globURI *pilosa.URI
func init() {
var err error
globURI, err = pilosa.NewURIFromHostPort("127.0.0.1", 10101)
panicOn(err)
}
// get correct node to go to.
func GetImportRoaringURI(index string, shard uint64) *pilosa.URI {
return globURI
}

177
cmd/badloader/vprint.go Normal file
View file

@ -0,0 +1,177 @@
// home: https://github.com/glycerine/vprint
// Copyright 2019 Jason E. Aten, Ph.D. All rights reserved.
// License: MIT
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main
import (
"fmt"
"io"
"os"
"path"
"runtime"
"runtime/debug"
"sync"
"time"
)
const RFC3339MsecTz0 = "2006-01-02T15:04:05.000Z07:00"
const RFC3339UsecTz0 = "2006-01-02T15:04:05.000000Z07:00"
// for tons of debug output
var VerboseVerbose bool = false
// convience functions for . import
var pp = PP
var vv = VV
var panicOn = PanicOn
func init() {
// keeper linter happy
_ = pp
_ = vv
}
func PanicOn(err error) {
if err != nil {
panic(err)
}
}
func PP(format string, a ...interface{}) {
if VerboseVerbose {
TSPrintf(format, a...)
}
}
func VV(format string, a ...interface{}) {
TSPrintf(format, a...)
}
func AlwaysPrintf(format string, a ...interface{}) {
TSPrintf(format, a...)
}
var tsPrintfMut sync.Mutex
// time-stamped printf
func TSPrintf(format string, a ...interface{}) {
tsPrintfMut.Lock()
Printf("\n%s %s ", FileLine(3), ts())
Printf(format+"\n", a...)
tsPrintfMut.Unlock()
}
// get timestamp for logging purposes
func ts() string {
return time.Now().Format(RFC3339UsecTz0)
}
// so we can multi write easily, use our own printf
var OurStdout io.Writer = os.Stdout
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(OurStdout, format, a...)
}
func FileLine(depth int) string {
_, fileName, fileLine, ok := runtime.Caller(depth)
var s string
if ok {
s = fmt.Sprintf("%s:%d", path.Base(fileName), fileLine)
} else {
s = ""
}
return s
}
func stack() string {
return string(debug.Stack())
}
func FileExists(name string) bool {
fi, err := os.Stat(name)
if err != nil {
return false
}
if fi.IsDir() {
return false
}
return true
}
func DirExists(name string) bool {
fi, err := os.Stat(name)
if err != nil {
return false
}
if fi.IsDir() {
return true
}
return false
}
func FileSize(name string) (int64, error) {
fi, err := os.Stat(name)
if err != nil {
return -1, err
}
return fi.Size(), nil
}
// Caller returns the name of the calling function.
func Caller(upStack int) string {
// elide ourself and runtime.Callers
target := upStack + 2
pc := make([]uintptr, target+2)
n := runtime.Callers(0, pc)
f := runtime.Frame{Function: "unknown"}
if n > 0 {
frames := runtime.CallersFrames(pc[:n])
for i := 0; i <= target; i++ {
contender, more := frames.Next()
if i == target {
f = contender
}
if !more {
break
}
}
}
return f.Function
}
// happy linter:
var _ = DirExists
var _ = FileExists
var _ = Caller
var _ = stack
var _ = RFC3339MsecTz0
var _ = RFC3339UsecTz0
var _ = AlwaysPrintf
var _ = FileSize

View file

@ -45,5 +45,12 @@ Inspects a data file and provides stats.
return inspector.Run(context.Background())
},
}
flags := inspectCmd.Flags()
flags.BoolVarP(&inspector.Quiet, "quiet", "q", false, "don't list details of containers")
flags.IntVarP(&inspector.Max, "max", "n", 0, "list at most max items (0 = unlimited)")
flags.StringVarP(&inspector.InspectOpts.Indexes, "index", "i", "", "filter indexes")
flags.StringVarP(&inspector.InspectOpts.Views, "view", "v", "", "filter views")
flags.StringVarP(&inspector.InspectOpts.Fields, "field", "f", "", "filter fields")
flags.StringVarP(&inspector.InspectOpts.Shards, "shard", "s", "", "filter shards")
return inspectCmd
}

View file

@ -50,7 +50,7 @@ The file does not contain any headers.
flags.StringVarP(&Exporter.Index, "index", "i", "", "Pilosa index to export")
flags.StringVarP(&Exporter.Field, "field", "f", "", "Field to export")
flags.StringVarP(&Exporter.Path, "output-file", "o", "", "File to write export to - default stdout")
ctl.SetTLSConfig(flags, &Exporter.TLS.CertificatePath, &Exporter.TLS.CertificateKeyPath, &Exporter.TLS.CACertPath, &Exporter.TLS.SkipVerify, &Exporter.TLS.EnableClientVerification)
ctl.SetTLSConfig(flags, "", &Exporter.TLS.CertificatePath, &Exporter.TLS.CertificateKeyPath, &Exporter.TLS.CACertPath, &Exporter.TLS.SkipVerify, &Exporter.TLS.EnableClientVerification)
return exportCmd
}

View file

@ -53,9 +53,9 @@ omitted. If it is present then its format should be YYYY-MM-DDTHH:MM.
flags.StringVarP(&Importer.Field, "field", "f", "", "Field to import into.")
flags.BoolVar(&Importer.IndexOptions.Keys, "index-keys", false, "Specify keys=true when creating an index")
flags.BoolVar(&Importer.FieldOptions.Keys, "field-keys", false, "Specify keys=true when creating a field")
flags.StringVar(&Importer.FieldOptions.Type, "field-type", "", "Specify the field type when creating a field. One of: set, int, time, bool, mutex")
flags.Int64Var(&Importer.FieldOptions.Min, "field-min", 0, "Specify the minimum for an int field on creation")
flags.Int64Var(&Importer.FieldOptions.Max, "field-max", 0, "Specify the maximum for an int field on creation")
flags.StringVar(&Importer.FieldOptions.Type, "field-type", "", "Specify the field type when creating a field. One of: set, int, decimal, time, bool, mutex")
flags.Int64Var(&Importer.FieldOptions.Min.Value, "field-min", 0, "Specify the minimum for an int field on creation") // TODO: noting that decimal field min/max are not supported here.
flags.Int64Var(&Importer.FieldOptions.Max.Value, "field-max", 0, "Specify the maximum for an int field on creation")
flags.StringVar(&Importer.FieldOptions.CacheType, "field-cache-type", pilosa.CacheTypeRanked, "Specify the cache type for a set field on creation. One of: none, lru, ranked")
flags.Uint32Var(&Importer.FieldOptions.CacheSize, "field-cache-size", 50000, "Specify the cache size for a set field on creation")
flags.Var(&Importer.FieldOptions.TimeQuantum, "field-time-quantum", "Specify the time quantum for a time field on creation. One of: D, DH, H, M, MD, MDH, Y, YM, YMD, YMDH")
@ -63,7 +63,7 @@ omitted. If it is present then its format should be YYYY-MM-DDTHH:MM.
flags.BoolVarP(&Importer.Sort, "sort", "", false, "Enables sorting before import.")
flags.BoolVarP(&Importer.CreateSchema, "create", "e", false, "Create the schema if it does not exist before import.")
flags.BoolVarP(&Importer.Clear, "clear", "", false, "Clear the data provided in the import.")
ctl.SetTLSConfig(flags, &Importer.TLS.CertificatePath, &Importer.TLS.CertificateKeyPath, &Importer.TLS.CACertPath, &Importer.TLS.SkipVerify, &Importer.TLS.EnableClientVerification)
ctl.SetTLSConfig(flags, "", &Importer.TLS.CertificatePath, &Importer.TLS.CertificateKeyPath, &Importer.TLS.CACertPath, &Importer.TLS.SkipVerify, &Importer.TLS.EnableClientVerification)
return importCmd
}

View file

@ -21,6 +21,7 @@ import (
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/cmd"
"github.com/pilosa/pilosa/v2/pql"
)
func TestImportHelp(t *testing.T) {
@ -58,8 +59,8 @@ field = "f1"
v.Check(cmd.Importer.Field, "f1")
v.Check(cmd.Importer.FieldOptions, pilosa.FieldOptions{
Keys: true,
Max: 100,
Min: -10,
Max: pql.NewDecimal(100, 0),
Min: pql.NewDecimal(-10, 0),
CacheType: pilosa.CacheTypeRanked,
CacheSize: 50000,
})

358
cmd/pilosa-bench/main.go Normal file
View file

@ -0,0 +1,358 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"expvar"
"flag"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
_ "net/http/pprof"
"os"
"sort"
"strings"
"time"
"github.com/pilosa/pilosa/v2"
phttp "github.com/pilosa/pilosa/v2/http"
"golang.org/x/sync/errgroup"
)
var (
requestCountVar = expvar.NewInt("request_count")
requestCurrentLatencyVar = expvar.NewFloat("request_current_latency") // seconds
requestAvgLatencyVar = expvar.NewFloat("request_avg_latency") // seconds
requestTotalLatencyVar = expvar.NewFloat("request_total_latency") // seconds
requestPerSecVar = expvar.NewFloat("request_per_sec")
)
func main() {
if err := run(context.Background(), os.Args[1:]); err == flag.ErrHelp {
os.Exit(1)
} else if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run(ctx context.Context, args []string) (err error) {
fs := flag.NewFlagSet("pilosa-bench", flag.ContinueOnError)
hostport := fs.String("hostport", "localhost:10101", "")
typ := fs.String("type", "row", "query type (row)")
n := fs.Int("n", 1000, "number of queries")
rate := fs.Int("rate", 1, "number of queries per second")
verbose := fs.Bool("v", false, "verbose logging")
from := fs.String("from", "", "from time for row-range queries (ISO 8601)")
to := fs.String("to", "", "to time for row-range queries (ISO 8601)")
if err := fs.Parse(args); err != nil {
return err
}
// Parse from/to time.
var opt queryOptions
if *from != "" {
if opt.from, err = time.Parse(time.RFC3339, *from); err != nil {
return fmt.Errorf("cannot parse -from time")
}
}
if *to != "" {
if opt.to, err = time.Parse(time.RFC3339, *to); err != nil {
return fmt.Errorf("cannot parse -to time")
}
}
if (*typ == "row-range" || *typ == "topk") && (opt.from.IsZero() || opt.to.IsZero()) {
return fmt.Errorf("-from and -to flags must be specified for topk & row-range queries")
}
// Clear time prefix on log.
log.SetFlags(0)
if !*verbose {
log.SetOutput(ioutil.Discard)
}
// Setup PRNG to have consistent values for the same set of data.
rand.Seed(0)
// Setup connection to pilosa.
client, err := phttp.NewInternalClient(*hostport, http.DefaultClient)
if err != nil {
return err
}
// Set up HTTP endpoint to provide /debug endpoints.
fmt.Println("Serving debug endpoint at http://localhost:7070/debug")
go func() { _ = http.ListenAndServe(":7070", nil) }()
// Run separate goroutine to calculate the current req/sec & latency.
go monitor()
// Load all id/keys for each field.
log.Printf("loading field identifiers")
fieldIDMap, err := loadFields(ctx, client)
if err != nil {
return fmt.Errorf("cannot load field identifiers: %w", err)
} else if len(fieldIDMap) == 0 {
return fmt.Errorf("no field identifiers available, please verify data exists")
}
// Generate list of sorted keys.
fieldKeys := make([]fieldKey, 0, len(fieldIDMap))
for k, f := range fieldIDMap {
switch *typ {
case "row-bsi":
if f.info.Options.Type != "int" {
continue
}
case "row-range", "topk":
if f.info.Options.Type != "time" {
continue
}
default:
if f.info.Options.Type == "int" || f.info.Options.Type == "time" {
continue
}
}
fieldKeys = append(fieldKeys, k)
}
sort.Slice(fieldKeys, func(i, j int) bool {
return compareFieldKeys(fieldKeys[i], fieldKeys[j]) == -1
})
// Ensure we have appropriate fields for our query type.
if len(fieldKeys) == 0 {
return fmt.Errorf("no available fields are appropriate for %q queries", *typ)
}
log.Printf("issuing %d queries at %d query/sec", *n, *rate)
// Repeatedly issue queries based on available row data.
ticker := time.NewTicker(time.Second / time.Duration(*rate))
var g errgroup.Group
for i := 0; i < *n; i++ {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
key := fieldKeys[rand.Intn(len(fieldKeys))]
q, err := generateQuery(*typ, key.index, key.field, fieldIDMap[key].info, fieldIDMap[key].identifiers, opt)
if err != nil {
return fmt.Errorf("cannot generate query: %w", err)
}
log.Printf("[query] %s", q)
g.Go(func() error {
t := time.Now()
_, err = client.Query(ctx, key.index, &pilosa.QueryRequest{Index: key.index, Query: q})
if err != nil {
return err
}
elapsed := time.Since(t).Seconds()
requestCountVar.Add(1)
requestTotalLatencyVar.Add(elapsed)
requestAvgLatencyVar.Set(requestTotalLatencyVar.Value() / float64(requestCountVar.Value()))
return nil
})
}
return g.Wait()
}
// monitor runs in a separate goroutine and updates metrics.
func monitor() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
var lastTime time.Time
var lastN int64
var lastLatency float64
for range ticker.C {
now, n := time.Now(), requestCountVar.Value()
latency := requestTotalLatencyVar.Value()
if !lastTime.IsZero() {
elapsed := lastTime.Sub(now).Seconds()
if n > 0 {
requestCurrentLatencyVar.Set((lastLatency - latency) / float64(n))
}
requestPerSecVar.Set(float64(lastN-n) / elapsed)
}
lastTime, lastN, lastLatency = now, n, latency
}
}
func generateQuery(typ, index, field string, info *pilosa.FieldInfo, identifiers *pilosa.RowIdentifiers, opt queryOptions) (string, error) {
switch typ {
case "row":
return generateRowQuery(index, field, identifiers), nil
case "row-bsi":
return generateRowBSIQuery(index, field), nil
case "row-range":
return generateRowRangeQuery(index, field, identifiers, opt.from, opt.to), nil
case "count":
return generateCountQuery(index, field, identifiers), nil
case "intersect":
return generateIntersectQuery(index, field, identifiers), nil
case "union":
return generateUnionQuery(index, field, identifiers), nil
case "difference":
return generateDifferenceQuery(index, field, identifiers), nil
case "xor":
return generateXorQuery(index, field, identifiers), nil
case "groupby":
return generateGroupByQuery(index, field), nil
case "topk":
return generateTopKQuery(index, field, opt.from, opt.to), nil
default:
return "", fmt.Errorf("invalid query type: %q", typ)
}
}
func generateRowQuery(index, field string, identifiers *pilosa.RowIdentifiers) string {
if len(identifiers.Rows) > 0 {
return fmt.Sprintf("Row(%s=%d)", field, chooseRowID(identifiers))
}
return fmt.Sprintf("Row(%s=%q)", field, chooseRowKey(identifiers))
}
func generateRowBSIQuery(index, field string) string {
return fmt.Sprintf("Row(%s > 0)", field)
}
func generateRowRangeQuery(index, field string, identifiers *pilosa.RowIdentifiers, from, to time.Time) string {
if len(identifiers.Rows) > 0 {
return fmt.Sprintf("Row(%s=%d, from='%s', to='%s')", field, chooseRowID(identifiers), from.Format("2006-01-02T15:04"), to.Format("2006-01-02T15:04"))
}
return fmt.Sprintf("Row(%s=%q, from='%s', to='%s')", field, chooseRowKey(identifiers), from.Format("2006-01-02T15:04"), to.Format("2006-01-02T15:04"))
}
func generateRowQueries(index, field string, identifiers *pilosa.RowIdentifiers) string {
a := make([]string, rand.Intn(9)+1)
for i := range a {
a[i] = generateRowQuery(index, field, identifiers)
}
return strings.Join(a, ", ")
}
func generateCountQuery(index, field string, identifiers *pilosa.RowIdentifiers) string {
return fmt.Sprintf("Count(%s)", generateRowQuery(index, field, identifiers))
}
func generateIntersectQuery(index, field string, identifiers *pilosa.RowIdentifiers) string {
return fmt.Sprintf("Intersect(%s)", generateRowQueries(index, field, identifiers))
}
func generateUnionQuery(index, field string, identifiers *pilosa.RowIdentifiers) string {
return fmt.Sprintf("Union(%s)", generateRowQueries(index, field, identifiers))
}
func generateDifferenceQuery(index, field string, identifiers *pilosa.RowIdentifiers) string {
return fmt.Sprintf("Difference(%s)", generateRowQueries(index, field, identifiers))
}
func generateXorQuery(index, field string, identifiers *pilosa.RowIdentifiers) string {
return fmt.Sprintf("Xor(%s)", generateRowQueries(index, field, identifiers))
}
func generateGroupByQuery(index, field string) string {
return fmt.Sprintf("GroupBy(Rows(%s))", field)
}
func generateTopKQuery(index, field string, from, to time.Time) string {
return fmt.Sprintf("TopK(%s, from='%s', to='%s')", field, from.Format("2006-01-02T15:04"), to.Format("2006-01-02T15:04"))
}
// loadFields returns a mapping of index/field names to field info & identifiers.
func loadFields(ctx context.Context, client *phttp.InternalClient) (map[fieldKey]*fieldInfo, error) {
indexes, err := client.Schema(ctx)
if err != nil {
return nil, err
}
m := make(map[fieldKey]*fieldInfo)
for _, ii := range indexes {
for _, f := range ii.Fields {
log.Printf("field: index=%s name=%s type=%s", ii.Name, f.Name, f.Options.Type)
switch f.Options.Type {
case "set", "mutex", "time":
identifiers, err := fetchFieldIDs(ctx, client, ii.Name, f.Name)
if err != nil {
return nil, fmt.Errorf("fetch fields: %w", err)
} else if len(identifiers.Rows) > 0 || len(identifiers.Keys) > 0 {
m[fieldKey{ii.Name, f.Name}] = &fieldInfo{f, identifiers}
}
case "int":
m[fieldKey{ii.Name, f.Name}] = &fieldInfo{info: f}
}
}
}
return m, nil
}
// fetchFieldIDs returns a list of field IDs or keys.
func fetchFieldIDs(ctx context.Context, client *phttp.InternalClient, indexName, fieldName string) (*pilosa.RowIdentifiers, error) {
resp, err := client.Query(ctx, indexName, &pilosa.QueryRequest{Index: indexName, Query: `Rows(` + fieldName + `)`})
if err != nil {
return nil, err
}
switch result := resp.Results[0].(type) {
case *pilosa.RowIdentifiers:
return result, nil
case pilosa.RowIdentifiers:
return &result, nil
default:
return nil, fmt.Errorf("unexpected result type: %T", result)
}
}
func chooseRowID(identifiers *pilosa.RowIdentifiers) uint64 {
return identifiers.Rows[rand.Intn(len(identifiers.Rows))]
}
func chooseRowKey(identifiers *pilosa.RowIdentifiers) string {
return identifiers.Keys[rand.Intn(len(identifiers.Keys))]
}
type fieldKey struct {
index string
field string
}
type fieldInfo struct {
info *pilosa.FieldInfo
identifiers *pilosa.RowIdentifiers
}
func compareFieldKeys(x, y fieldKey) int {
if cmp := strings.Compare(x.index, y.index); cmp != 0 {
return cmp
}
return strings.Compare(x.field, y.field)
}
type queryOptions struct {
from, to time.Time
}

111
cmd/pilosa-chk/chk.go Normal file
View file

@ -0,0 +1,111 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"fmt"
"log"
"os"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/boltdb"
"github.com/pilosa/pilosa/v2/hash"
"github.com/zeebo/blake3"
)
// pilosa-chk : read boltdb files and print checksums and counts on the keys. With
// -v and -ops and -bits you can display every last bit if you want.
//
// pilosa-chk is deliberately NOT a part of pilosa so that it can run without
// forcing a customer to upgrade or downgrade their installed version.
func main() {
var dir string
var showOpsLog bool
var showBits bool
var showFrags bool
var dirChecksum bool
home := os.Getenv("HOME")
flag.StringVar(&dir, "dir", fmt.Sprintf("%v/.pilosa", home), "pilosa data dir to read")
flag.BoolVar(&showFrags, "v", false, "show the checksum hash for each fragment in each index. Warning: long output")
flag.BoolVar(&showOpsLog, "ops", false, "show the ops log for each fragment. Warning: very long output. Implies -v")
flag.BoolVar(&showBits, "bits", false, "show the hot bits for each fragment. Warning: very, very long output. Implies -v")
flag.BoolVar(&dirChecksum, "dirsum", false, "compute a directory hash")
flag.Parse()
if showBits {
showFrags = true
}
if showOpsLog {
showFrags = true
}
fmt.Printf("opening dir '%v'... this may take a few seconds...\n", dir)
if dirChecksum {
fmt.Printf("path '%v' has dirhash %v\n", dir, hash.HashOfDir(dir))
return
}
fmt.Printf(" the blake-3 hash includes the value of each mapping and the field or partitionID.\n")
holder := pilosa.NewHolder(dir, nil)
holder.OpenTranslateStore = boltdb.OpenTranslateStore
err := holder.Open()
if err != nil {
log.Fatal(err)
}
fmt.Printf("\ncalculating hashes of row and column key translation maps on data from dir '%v'...\n", dir)
var indexes []*pilosa.Index
final := pilosa.NewAllTranslatorSummary()
const verbose = true
const checkKeys = false
const applyKeyRepairs = false
for _, idx := range holder.Indexes() {
asum, err := idx.ComputeTranslatorSummary(verbose, checkKeys, applyKeyRepairs, nil, "fake-nodeID", 10)
if err != nil {
log.Fatal(err)
}
final.Append(asum)
indexes = append(indexes, idx)
}
final.Sort()
hasher := blake3.New()
fmt.Printf("\nsummary of col/row translations%v:\n", dir)
for _, sum := range final.Sums {
//fmt.Printf("index: %v partitionID: %v blake3-%x keyCount: %v idCount: %v\n", sum.Index, sum.PartitionID, sum.Checksum, sum.KeyCount, sum.IDCount)
_, _ = hasher.Write([]byte(sum.Checksum))
}
var buf [16]byte
_, _ = hasher.Digest().Read(buf[0:])
fmt.Printf("all-checksum = blake3-%x\n", buf)
if showFrags {
for _, idx := range indexes {
fmt.Printf("==============================\n")
fmt.Printf("index: %v\n", idx.Name())
fmt.Printf("==============================\n")
idx.WriteFragmentChecksums(os.Stdout, showBits, showOpsLog, nil, verbose)
}
}
}

36
cmd/pilosa-fsck/Makefile Normal file
View file

@ -0,0 +1,36 @@
.PHONY: install build release
CLONE_URL=github.com/pilosa/pilosa
VERSION := $(shell git describe --tags 2> /dev/null || echo unknown)
LATTICE_COMMIT := $(shell git -C lattice rev-parse --short HEAD 2>/dev/null)
VARIANT = Molecula
VERSION_ID = $(VERSION)-$(GOOS)-$(GOARCH)
BRANCH := $(if $(TRAVIS_BRANCH),$(TRAVIS_BRANCH),$(if $(CIRCLE_BRANCH),$(CIRCLE_BRANCH),$(shell git rev-parse --abbrev-ref HEAD)))
BRANCH_ID := $(BRANCH)-$(GOOS)-$(GOARCH)
BUILD_TIME := $(shell date -u +%FT%T%z)
SHARD_WIDTH = 20
COMMIT := $(shell git describe --exact-match >/dev/null 2>&1 || git rev-parse --short HEAD)
LDFLAGS="-X github.com/pilosa/pilosa/v2.Version=$(VERSION) -X github.com/pilosa/pilosa/v2.BuildTime=$(BUILD_TIME) -X github.com/pilosa/pilosa/v2.Variant=$(VARIANT) -X github.com/pilosa/pilosa/v2.Commit=$(COMMIT) -X github.com/pilosa/pilosa/v2.LatticeCommit=$(LATTICE_COMMIT)"
GOOS = $(shell go env GOOS)
# Install pilosa-fsck
install:
go install -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS)
# Compile pilosa-fsck
build:
go build -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS)
REL = release-pilosa-fsck.$(COMMIT).$(GOOS)
release:
mkdir $(REL)
cd release-pilosa-fsck; tar cf - . |(cd ../$(REL); tar xf - )
go build -tags='$(BUILD_TAGS)' -ldflags $(LDFLAGS) $(FLAGS) -o $(REL)/pilosa-fsck
tar cf - $(REL) | gzip > $(REL).tar.gz
rm -rf $(REL)
mv $(REL).tar.gz ../..
clean:
find . -name pilosa-fsck | xargs rm -f
rm -f release-pilosa-fsck*.tar.gz

985
cmd/pilosa-fsck/fsck.go Normal file
View file

@ -0,0 +1,985 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"github.com/dustin/go-humanize"
"github.com/gogo/protobuf/proto"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/boltdb"
"github.com/pilosa/pilosa/v2/internal"
"github.com/pilosa/pilosa/v2/server"
"github.com/pkg/errors"
"github.com/zeebo/blake3"
)
// pilosa-fsck :
// an external customer tool (originally for Q2) to do 2 jobs:
// Given a set of cluster backups (and their .id and .topology files)
// mounted on the same file system, we can:
// 1) scan for fragment differences between the primary and its replicas (default); or
// 2) repair those differences by overwriting the replcas with the primary fragments (if -fix is given).
//
// pilosa-chk is deliberately NOT a part of pilosa so that it can run without
// forcing a customer to upgrade or downgrade their installed version.
// FsckConfig configures the dumpcols() and/or read() runs.
type FsckConfig struct {
Fix bool // -fix
FixCol bool // -fixcol
Colkeydump bool // -col
JustThisIndex string // -index
// -col column key dump only options:
// Dir string
// PartitionID int
// ShowHeader bool
// ShowKey bool
// ShowID bool
// not flags, just the Args() left after all other flags. Should be the list
// of pilosa (holder) directories for the cluster.
Dirs []string
Verbose bool // -v
Quiet bool // -q
// manual workaround for not having PilosaConfigPath, if really need be.
ReplicaN int // -replicas
PilosaConfigPath string // -config
ParallelReaders int // -readers
topo *pilosa.Topology
}
// call DefineFlags before myflags.Parse()
func (cfg *FsckConfig) DefineFlags(fs *flag.FlagSet) {
fs.BoolVar(&cfg.Fix, "fix", false, "(warning: alters the backed-up node images on disk) copy primary data to replicas to create a consistent cluster. Implies -fixcol")
fs.BoolVar(&cfg.FixCol, "fixcol", false, "(warning: alters the backed-up node images on disk) repair string key translation tables. Skip repair of index data.")
//fs.BoolVar(&cfg.Verbose, "v", false, "be very verbose during analysis")
fs.BoolVar(&cfg.Quiet, "q", false, "be very quiet")
fs.IntVar(&cfg.ReplicaN, "replicas", 0, "(required) manually entered replicaN; the number of replicas maintained in the cluster. Must be the same as the [cluster] 'replicas = R' entry in the pilosa.conf file for the cluster.")
fs.IntVar(&cfg.ParallelReaders, "readers", 10, "how many parallel readers to use to scan at once. 0 means do everything possible in parallel. 1 means serialize everything through a single reader. Can be adjusted to control memory consumption.")
fs.StringVar(&cfg.PilosaConfigPath, "config", "", "(required: -replicas or -config, with -config preferred) path to the pilosa.conf for the cluster (e.g. /etc/pilosa.conf)")
fs.StringVar(&cfg.JustThisIndex, "index", "", "(optional) restrict to just this index. Otherwise we default to all indexes.")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "pilosa-fsck version: %v\n\n", pilosa.VersionInfo())
fmt.Fprintf(os.Stderr, `Use: pilosa-fsck -replicas R {-fix} {-q} /backup/1/.pilosa /backup/2/.pilosa ... /backup/N/.pilosa
-fix
(warning: alters the backed-up node images on disk) copy primary data to replicas to create a consistent cluster.
-replicas R
(required) R is a positive integer, giving the replicaN or replicator factor for the cluster. This is
the number of replicas maintained in the cluster. Must be the same as the
[cluster] 'replicas = R' entry shared across all the pilosa.conf files on each node.
-index index_name
(optional) restrict to just this index. Otherwise we default to all indexes.
-readers PR
how many parallel readers to use to scan at once. PR==0 means do everything
possible in parallel. PR==1 means serialize everything through a single reader.
Adjust PR to control memory consumption if needed. As a practical limit, setting
PR > 10000 will have no effect. (default is 10).
-q
be very quiet during analysis and repair
`)
fmt.Fprintf(os.Stderr, `
Welcome to pilosa-fsck. This is a scan and repair
tool that is modeled after the classic unix file
system utility fsck.
WARNING: DO NOT RUN ON A LIVE SYSTEM.
The most important point to remember is that analysis
and repair must be done *offline*.
Just as fsck must be run on an unmounted disk,
pilosa-fsck must be run on a backup. It must
not be run on the directories where a live Pilosa system
is serving queries. Instead, take a backup first.
A backup is a set of N Pilosa data directories that have been
copied from your live system. They must all
be visible and mounted on one filesystem together.
pilosa-fsck can be run in scan-mode (without -fix),
or in repair-mode with -fix. The console output
supplies a log documenting the analysis
and showing what data changes would have been made.
REQUIRED COMMAND LINE ARGUMENTS
The paths to all the top-level Pilosa
data directories in a cluster must be given on the command
line. The -replicas R flag is also always required. It
must be correct for your cluser. Here R is the same as
the [cluster] stanza "replicas = R" line from your
pilosa.conf.
Example:
Suppose you are ready to run pilosa-fsck:
you have taken a backup of your four node Pilosa
cluster and stored it all on one filesystem with
all nodes visible and uncompressed. This
is a pre-requisite to running pilosa-fsck.
Let's suppose we have replication R = 3 set.
In this example, have stored our backed-up directories in
/backup/molecula
and the four node backups are in
subdirectories node1/ node2/ node3/ node4/ under this:
/backup/molecula/node1/
/backup/molecula/node1/.pilosa/.id
/backup/molecula/node1/.pilosa/.topology
/backup/molecula/node1/.pilosa/myindex
/backup/molecula/node2/
/backup/molecula/node2/.pilosa/.id
/backup/molecula/node2/.pilosa/.topology
/backup/molecula/node2/.pilosa/myindex
/backup/molecula/node3/
/backup/molecula/node3/.pilosa/.id
/backup/molecula/node3/.pilosa/.topology
/backup/molecula/node3/.pilosa/myindex
/backup/molecula/node4/
/backup/molecula/node4/.pilosa/.id
/backup/molecula/node4/.pilosa/.topology
/backup/molecula/node4/.pilosa/myindex
NOTE: your .pilosa directories need not be named .pilosa. They can
be something else, such as when the -d flag to pilosa server was used.
The .id file, the .topology file, and the index directories must be
found directly underneath.
Then a typical invocation to scan a cluster backup for issues:
$ cd /backup/molecula/
$ pilosa-fsck -replicas 3 node1/.pilosa node2/.pilosa node3/.pilosa node4/.pilosa &> log
A typical invocation to repair the replication in the same backup:
$ pilosa-fsck -replicas 3 -fix node1/.pilosa node2/.pilosa node3/.pilosa node4/.pilosa &> log
In both cases, the .id and .topology files must
be present in the backups.
Without -fix, no modifications will be made to the backups. Only
by running with -fix will repairs be made. The user can safely
always run with -fix to repair only if needed.
A zero error code will be returned to the shell if no repairs were needed.
A zero error code will be also be returned to the shell if
repairs were needed and they were accomplished under -fix.
A non-zero error code indicates that repairs were needed but
were not made.
`)
}
}
// call c.ValidateConfig() after myflags.Parse()
func (c *FsckConfig) ValidateConfig() error {
if c.Fix {
c.FixCol = true
}
if c.ReplicaN == 0 && c.PilosaConfigPath == "" {
return fmt.Errorf("must supply -replicas with the replica count from your pilosa.conf (positive integer count)")
}
if c.ReplicaN == 0 && c.PilosaConfigPath != "" {
if !FileExists(c.PilosaConfigPath) {
return fmt.Errorf(" -config path '%v' does not exist", c.PilosaConfigPath)
}
by, err := ioutil.ReadFile(c.PilosaConfigPath)
if err != nil {
return fmt.Errorf("error: could not read the -config path '%v': '%v'", c.PilosaConfigPath, err)
}
srvcfg, err := server.ParseConfig(string(by))
if err != nil {
//vv("warning: -config path '%v' problem, could not parse toml: '%v'", c.PilosaConfigPath, err)
// fall back to manual parsing of config
lines := strings.Split(string(by), "\n")
clusterStart := -1
for i, line := range lines {
if strings.Contains(line, `[cluster]`) {
clusterStart = i
}
if i > clusterStart {
if strings.Contains(line, "replicas") {
split := strings.Split(line, "=")
ns := strings.TrimSpace(split[1])
n, err := strconv.Atoi(ns)
if err != nil {
return fmt.Errorf("error: could not parse the replicaN from line %v in -config path '%v' (%v): '%v'", i+1, c.PilosaConfigPath, line, err)
}
c.ReplicaN = n
}
}
}
} else {
c.ReplicaN = srvcfg.Cluster.ReplicaN
}
if c.ReplicaN == 0 {
return fmt.Errorf("error: -config path '%v' did not list the Replica count: cannot be 0. See the [cluster] section, the 'replicas = R' line.", c.PilosaConfigPath)
}
//vv("c.ReplicaN = %v", c.ReplicaN)
}
return nil
}
var ProgramName = "pilosa-fsck"
func main() {
myflags := flag.NewFlagSet(ProgramName, flag.ContinueOnError)
cfg := &FsckConfig{}
cfg.DefineFlags(myflags)
cfg.Verbose = true
err := myflags.Parse(os.Args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "\n%v\n", err.Error())
os.Exit(1)
}
err = cfg.ValidateConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "%s error: %s\n", ProgramName, err)
os.Exit(1)
}
dirs := myflags.Args()
nDir := len(dirs)
if nDir <= 0 && !cfg.Colkeydump {
fmt.Fprintf(os.Stderr, "error: %v command line arguments missing error: provide all of the top-level pilosa directories for the cluster as command line arguments.\n", ProgramName)
os.Exit(1)
}
cmdline := strings.Join(os.Args, " ")
// make sure all the dir are distinct
dup := make(map[string]bool)
for _, dir := range dirs {
if dup[dir] {
fmt.Fprintf(os.Stderr, "%v error: duplicate data directory '%v' given in command line '%v'. Each backup directory must be distinct.\n", ProgramName, dir, cmdline)
os.Exit(1)
} else {
dup[dir] = true
}
}
fmt.Fprintf(os.Stdout, "#!/bin/bash\n\n# pilosa-fsck version: %v\n", pilosa.VersionInfo())
cwd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "error: could not read current dir: '%v'\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stdout, "# cwd: %v\n", cwd)
fmt.Fprintf(os.Stdout, "# command line: %v\n", cmdline)
t0 := time.Now()
fmt.Fprintf(os.Stdout, "# started at %v\n\n", t0.Format(RFC3339MsecTz0))
defer func() {
fmt.Fprintf(os.Stdout, "# finished at %v (elapsed %v)\n\n", time.Now().Format(RFC3339MsecTz0), time.Since(t0))
}()
cfg.Dirs = dirs
fixNeeded, err := cfg.Run()
if err != nil {
fmt.Fprintf(os.Stdout, "# finished at %v (elapsed %v)\n\n", time.Now().Format(RFC3339MsecTz0), time.Since(t0))
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if fixNeeded && !cfg.Fix {
fmt.Fprintf(os.Stdout, "# finished at %v (elapsed %v)\n\n", time.Now().Format(RFC3339MsecTz0), time.Since(t0))
fmt.Fprintf(os.Stderr, "# pilosa-fsck exiting with non-zero error code because a repair is needed, but -fix was not given.\n")
os.Exit(1)
}
}
func (cfg *FsckConfig) Run() (fixNeeded bool, err error) {
// if cfg.Colkeydump {
// cfg.dumpcols()
//}
perNodeIndexMaps, clusterNodes, ats, err := cfg.read()
if err != nil {
return false, err
}
if cfg.FixCol {
err := cfg.RepairTranslationStores(ats)
if err != nil {
return false, fmt.Errorf("error fixing key translation stores with cfg.RepairTranslationStores(): '%v'\n", err)
}
}
//vv("perNodeIndexMaps='%#v', clusterNodes='%#v'", perNodeIndexMaps, clusterNodes)
fixme, reports, err := cfg.analyze(clusterNodes, perNodeIndexMaps, ats)
if err != nil {
return false, fmt.Errorf("error in FsckConfig.analyze(): '%v'", err)
}
fixNeeded = ats.RepairNeeded || fixme
for _, report := range reports {
fmt.Printf("%v\n", report)
}
if len(reports) == 0 {
fmt.Fprintf(os.Stderr, "pilosa-fsck: no index found to analyze. cmdline was: %v\n", strings.Join(os.Args, " "))
}
return
}
var _ = (&FsckConfig{}).dumpAts
func (cfg *FsckConfig) dumpAts(ats *pilosa.AllTranslatorSummary) {
fmt.Printf("# dumpAts: RepairNeeded=%v\n", ats.RepairNeeded)
for _, sum := range ats.Sums {
fmt.Printf("# sum = '%#v'\n", sum)
}
}
type group struct {
elem []*pilosa.TranslatorSummary
partitionID int
}
func (g *group) String() (s string) {
for i, e := range g.elem {
s += fmt.Sprintf("partition %v, group elem [%v] out of %v: %v\n", g.partitionID, i, len(g.elem), e.String())
}
return
}
func indexesFromAts(ats *pilosa.AllTranslatorSummary) (indexes []string) {
indexMap := make(map[string]bool)
for _, sum := range ats.Sums {
if !indexMap[sum.Index] {
indexMap[sum.Index] = true
indexes = append(indexes, sum.Index)
}
}
sort.Strings(indexes)
return
}
func (cfg *FsckConfig) RepairTranslationStores(ats *pilosa.AllTranslatorSummary) (err error) {
verbose := cfg.Verbose
// group by index first. then repair.
indexes := indexesFromAts(ats)
for _, index := range indexes {
if !cfg.DoingIndex(index) {
continue
}
m := make(map[int]*group)
for _, sum := range ats.Sums {
if !sum.IsColKey || sum.Index != index {
continue
}
grp := m[sum.PartitionID]
if grp == nil {
grp = &group{
partitionID: sum.PartitionID,
}
m[sum.PartitionID] = grp
}
grp.elem = append(grp.elem, sum)
}
for partitionID, group := range m {
_ = partitionID
prim := -1
keyCount := 0
for k, e := range group.elem {
if e.IsPrimary {
prim = k
}
keyCount += e.KeyCount
}
if prim == -1 {
panic(fmt.Sprintf("no primary found for group '%v'", group.String()))
}
primary := group.elem[prim]
primaryChecksum := primary.Checksum
for _, e := range group.elem {
if e.IsPrimary {
continue
}
// is e a replica? not necessarily! have to check.
if !e.IsReplica {
//if verbose {
// since this will happen even on a fix point, where it is already empty,
// we don't report it again.
//fmt.Printf("# non-replica should have no data: creating an empty translation store here at '%v'\n", e.StorePath)
//}
err := os.RemoveAll(e.StorePath)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("RepairTranslationStores() os.RemoveAll(e.StorePath='%v')", e.StorePath))
}
store, err := boltdb.OpenTranslateStore(e.StorePath, e.Index, e.Field, e.PartitionID, pilosa.DefaultPartitionN)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("RepairTranslationStores() create empty boldtdb: boltdb.OpenTranslateStore e.StorePath='%v'", e.StorePath))
}
err = store.Close()
if err != nil {
return errors.Wrap(err, fmt.Sprintf("RepairTranslationStores() closing empty boltdb at path '%v'", e.StorePath))
}
continue
}
// INVAR: e is a replica for this paritionID.
// Copy from primary if checksums are different.
if e.Checksum != primaryChecksum {
from := group.elem[prim].StorePath
dest := e.StorePath
if verbose {
fmt.Printf("# e.Checksum '%v' != primaryChecksum '%v': copying from primary translation store '%v' -> '%v'\n", e.Checksum, primaryChecksum, from, dest)
}
err := cp(from, dest)
if err != nil {
return fmt.Errorf("error: could not copy from primary '%v' to replica translation store '%v': '%v' ... try to keep going...\n", from, dest, err)
}
}
}
}
}
return nil
}
/*
func (cfg *FsckConfig) dumpcols() {
verbose := cfg.Verbose
quiet := cfg.Quiet
_, _ = verbose, quiet
dir := cfg.Dir
index := cfg.Index
partitionID := cfg.PartitionID
showKey := cfg.ShowKey
showID := cfg.ShowID
if !quiet {
fmt.Printf("# dumpcols: opening dir '%v'... this may take a few minutes...\n", dir)
}
holder := pilosa.NewHolder(dir, nil)
holder.OpenTranslateStore = boltdb.OpenTranslateStore
err := holder.Open()
if err != nil {
log.Fatal(err)
}
if cfg.ShowHeader {
fmt.Println("# columnKey columId")
}
id_key := make(map[uint64]string)
key_id := make(map[string]uint64)
for _, idx := range holder.Indexes() {
fmt.Printf("# Looking '%v'\n", idx.Name())
if idx.Name() == index {
store := idx.TranslateStore(partitionID)
fmt.Printf("# Key By ID partitionID = %v\n", partitionID)
err := store.KeyWalker(func(key string, col uint64) {
key_id[key] = col
if showKey {
fmt.Printf("# '%v' %v shard: %v partition: %v\n", key, col, col/pilosa.ShardWidth, partitionID)
}
})
panicOn(err)
}
}
for _, idx := range holder.Indexes() {
if idx.Name() == index {
store := idx.TranslateStore(partitionID)
//fmt.Printf("# ID ByKey\n")
err := store.IDWalker(func(key string, col uint64) {
id_key[col] = key
if showID {
fmt.Printf("# '%v' %v\n", key, col)
}
})
panicOn(err)
}
}
fmt.Printf("# k: %d i: %d\n", len(key_id), len(id_key))
fmt.Println("id_key")
for k, v := range id_key {
l, ok := key_id[v]
if ok {
if k != l {
fmt.Printf("# X: %v %v %v\n", k, l, v)
}
} else {
fmt.Printf("# key not in id %v\n", v)
}
}
fmt.Println("key_id")
for k, v := range key_id {
l, ok := id_key[v]
if ok {
if k != l {
fmt.Printf("# T: %v %v %v\n", k, l, v)
}
} else {
fmt.Printf("# id not in key %v\n", v)
}
}
}
*/
func (cfg *FsckConfig) read() (perNodeIndexMaps []map[string]*pilosa.IndexFragmentSummary, clusterNodes []string, final *pilosa.AllTranslatorSummary, err error) {
final = pilosa.NewAllTranslatorSummary()
dirs := cfg.Dirs
for _, dir := range dirs {
idx2frag, nodeID, atsNode, err := cfg.readOneDir(dir)
if err != nil {
return nil, nil, nil, err
}
final.Append(atsNode)
clusterNodes = append(clusterNodes, nodeID)
perNodeIndexMaps = append(perNodeIndexMaps, idx2frag)
}
return
}
func (cfg *FsckConfig) readOneDir(dir string) (idx2frag map[string]*pilosa.IndexFragmentSummary, nodeID string, atsNode *pilosa.AllTranslatorSummary, err error) {
verbose := cfg.Verbose
quiet := cfg.Quiet
if !quiet {
fmt.Printf("# opening dir '%v'... this may take a few minutes...\n\n", dir)
}
jmphasher := &pilosa.Jmphasher{}
partitionN := pilosa.DefaultPartitionN
replicaN := cfg.ReplicaN
topo, err := loadTopology(dir, jmphasher, partitionN, replicaN)
if err != nil {
return nil, "", nil, err
}
cfg.topo = topo
//vv("topo = '%#v'", topo)
nodeIDs := topo.GetNodeIDs()
//vv("nodeIDs = '%#v'", nodeIDs)
nNodes := len(nodeIDs)
nDir := len(cfg.Dirs)
if nDir != nNodes {
return nil, "", nil, fmt.Errorf("command line had %v directories (%#v) but the .topology had %v nodes (%#v)", nDir, cfg.Dirs, nNodes, nodeIDs)
}
holder := pilosa.NewHolder(dir, nil)
holder.OpenTranslateStore = boltdb.OpenTranslateStore
nodeID, err = holder.LoadNodeID()
panicOn(err)
//vv("nodeID = '%v'", nodeID)
err = holder.Open()
if err != nil {
log.Fatal(err)
}
if !quiet {
fmt.Printf("\n# calculating hashes of row and column key translation maps on data from dir '%v'...\n", dir)
}
var indexes []*pilosa.Index
const checkKeys = true
atsNode = pilosa.NewAllTranslatorSummary()
for _, idx := range holder.Indexes() {
if !cfg.DoingIndex(idx.Name()) {
continue
}
//vv("calling idx.ComputeTranslatorSummary(verbose, checkKeys=%v, cfg.FixCol='%v')", checkKeys, cfg.FixCol)
asum, err := idx.ComputeTranslatorSummary(verbose, checkKeys, cfg.FixCol, topo, nodeID, cfg.ParallelReaders)
if err != nil {
log.Fatal(err)
}
atsNode.Append(asum)
indexes = append(indexes, idx)
}
atsNode.Sort()
hasher := blake3.New()
if !quiet {
fmt.Printf("\n# summary of col/row translations in dir: %v:\n", dir)
}
for _, sum := range atsNode.Sums {
if !quiet {
fmt.Printf("# index: %v partitionID: %v blake3-%v keyCount: %v idCount: %v\n", sum.Index, sum.PartitionID, sum.Checksum, sum.KeyCount, sum.IDCount)
}
_, _ = hasher.Write([]byte(sum.Checksum))
}
var buf [16]byte
_, _ = hasher.Digest().Read(buf[0:])
if !quiet {
fmt.Printf("# all-checksum = blake3-%x\n", buf)
}
// fragment analysis
showBits := false
showOpsLog := false
idx2frag = make(map[string]*pilosa.IndexFragmentSummary) // on this node.
for _, idx := range indexes {
if verbose {
fmt.Printf("# ==============================\n")
fmt.Printf("# index: %v\n", idx.Name())
fmt.Printf("# ==============================\n")
}
frgsum := idx.WriteFragmentChecksums(os.Stdout, showBits, showOpsLog, topo, verbose)
frgsum.Dir = dir
frgsum.NodeID = nodeID
idx2frag[idx.Name()] = frgsum
}
_ = holder.Close()
//vv("idx2frag = '%v'", idx2frag) // tons of output. see 1234.out.full for examaple.
return
}
func (cfg *FsckConfig) DoingIndex(index string) bool {
if cfg.JustThisIndex == "" {
// scan all indexes
return true
}
if index == cfg.JustThisIndex {
// scan just this one
return true
}
return false
}
// from cluster.go:1924
func loadTopology(holderDir string, hasher pilosa.Hasher, partitionN, replicaN int) (*pilosa.Topology, error) {
buf, err := ioutil.ReadFile(filepath.Join(holderDir, ".topology"))
if err != nil {
return nil, err
}
var pb internal.Topology
err = proto.Unmarshal(buf, &pb)
if err != nil {
return nil, err
}
return pilosa.DecodeTopology(&pb, hasher, partitionN, replicaN, nil)
}
func (cfg *FsckConfig) analyze(clusterNodes []string, perNodeIndexMaps []map[string]*pilosa.IndexFragmentSummary, ats *pilosa.AllTranslatorSummary) (fixNeeded bool, reports []string, err error) {
verbose := cfg.Verbose
quiet := cfg.Quiet
_, _ = verbose, quiet
allIndex := make(map[string]bool)
for _, mp := range perNodeIndexMaps {
for index := range mp {
allIndex[index] = true
}
}
if !quiet {
vv("allIndex = '%#v'", allIndex)
}
for index := range allIndex {
if !quiet {
vv("on index '%v'", index)
}
nodes2fragsum := make(map[string]*pilosa.IndexFragmentSummary)
for _, mp := range perNodeIndexMaps {
sum := mp[index]
if sum == nil {
continue
}
nodes2fragsum[sum.NodeID] = sum
}
fixme, report, err := cfg.analyzeThisIndex(index, nodes2fragsum, ats)
if err != nil {
return false, reports, fmt.Errorf("error in analyze of index '%v': '%v'", index, err)
}
fixNeeded = fixNeeded || fixme
reports = append(reports, report)
}
return fixNeeded, reports, nil
}
func (cfg *FsckConfig) analyzeThisIndex(
index string,
nodes2fragsum map[string]*pilosa.IndexFragmentSummary,
ats *pilosa.AllTranslatorSummary,
) (fixNeeded bool, report string, err error) {
verbose := cfg.Verbose
quiet := cfg.Quiet
_, _ = verbose, quiet
var removedBytes int64
var copiedBytes int64
var changedFiles int64
var totalFiles int64
var overwrittenBytes int64
var totalBytes int64
if !quiet {
vv("top of analyzeThisIndex(index='%v'); len of nodes2fragsum = %v; nodes2fragsum='%#v'",
index, len(nodes2fragsum), nodes2fragsum)
}
for node, sum := range nodes2fragsum {
if !quiet {
fmt.Printf("# on node '%v'\n", node)
}
// do they disagree on who is the primary?
// for each fragment, do they disagree on the checksum?
// Q: which nodes are supposed to have data, and which
// nodes are not supposed to have data?
// loopFragSum:
for relpath, fragsum := range sum.RelPath2fsum {
fragsum.NodeID = node
totalFiles++
//vv("checking %v on node %v", relpath, node)
replicas, nonReplicas := cfg.topo.GetReplicasForPrimary(fragsum.Primary)
_, _ = replicas, nonReplicas
//vv("replicas = '%#v'", replicas)
//vv("nonReplicas = '%#v'", nonReplicas)
err := cfg.verifyReplicasAvailable(replicas, nonReplicas, nodes2fragsum, fragsum)
if err != nil {
return fixNeeded, "", err
}
// find the primary's checksum
primaryChecksum := ""
var primaryFragSum *pilosa.FragSum
for node, isPrimary := range replicas {
if isPrimary {
primarySum := nodes2fragsum[node]
primaryFragSum = primarySum.RelPath2fsum[relpath]
if primaryFragSum == nil {
// This seems clear indication that we have the topology wrong.
// When the topology is right, there are NO errors of this kind.
//
msg := fmt.Sprintf("# ugh. BAD. Stopping because any fix will be wrong. We see wrong -replica %v param, OR the .id files are mis-assigned with respect to the topology file. Could not find primary FragSum for relpath = '%v'. replicas = '%#v', nonReplicas = '%#v'\n", cfg.ReplicaN, relpath, replicas, nonReplicas)
vv(msg)
fmt.Fprintf(os.Stderr, "%v\n", msg)
panic(msg) // stop. the fixes are going to be wrong.
} else {
primaryChecksum = primaryFragSum.Checksum
primaryFragSum.NodeID = node
primaryFragSum.ScanDone = true
}
break
}
}
if primaryChecksum == "" {
return fixNeeded, "", fmt.Errorf("could not find primary replica??? replicas='%#v', nodes2fragsum='%v'; for fragsum='%#v'", replicas, nodes2fragsum, fragsum)
}
// is this a non-replica?
_, isNon := nonReplicas[fragsum.NodeID]
if isNon {
removedBytes += FileSize(fragsum.AbsPath)
changedFiles++
//vv("yes, is nonReplica: fragsum.NodeID='%v'", fragsum.NodeID)
if !quiet {
fmt.Printf("rm %v #### REPAIR REMOVE data from non-replica at node '%v' (fragsum='%#v') vs. primary (%#v)\n\n", fragsum.AbsPath, node, fragsum, primaryFragSum)
}
if cfg.Fix {
err := os.Remove(fragsum.AbsPath)
if err != nil {
return fixNeeded, "", fmt.Errorf("error removing non-replica extra fragment '%v': '%v'", fragsum.AbsPath, err)
}
}
} else {
presz := FileSize(fragsum.AbsPath)
totalBytes += presz
checksum := fragsum.Checksum
if checksum != primaryChecksum {
copiedBytes += FileSize(primaryFragSum.AbsPath)
changedFiles++
overwrittenBytes += presz
if !quiet {
fmt.Printf("cp %v %v #### REPAIR OVERWRITE replica at node '%v' (%#v) from primary '%v' (%#v)\n", primaryFragSum.AbsPath, fragsum.AbsPath, node, fragsum, primaryFragSum.NodeID, primaryFragSum)
}
if cfg.Fix {
err := cp(primaryFragSum.AbsPath, fragsum.AbsPath)
if err != nil {
return fixNeeded, "", fmt.Errorf("error copying from '%v' to '%v': '%v'",
primaryFragSum.AbsPath, fragsum.AbsPath, err)
}
}
}
}
fragsum.ScanDone = true
}
}
nDir := len(nodes2fragsum)
keyCount, idCount := cfg.getKeyIDCounts(index, ats)
fixNeeded = changedFiles > 0 || ats.RepairNeeded
var actionTaken string
var wouldBe string
if cfg.Fix || cfg.FixCol {
if fixNeeded {
actionTaken = "*REPAIRS WERE MADE TO THE BACKUPS*"
wouldBe = "sync repairs made:"
} else {
wouldBe = ""
actionTaken = "NO REPAIR NEEDED."
}
} else {
if fixNeeded {
wouldBe = "sync actions that would be taken under -fix:"
actionTaken = "*REPAIRS NEEDED BUT WERE NOT APPLIED* ; pilosa-fsck -fix was omitted."
} else {
wouldBe = ""
actionTaken = "NO REPAIR NEEDED."
}
}
var fragUpdate string
if changedFiles > 0 {
fragUpdate = fmt.Sprintf(`
# %v
# copied bytes: %v
# file bytes overwritten: %v
# new bytes added: %v
# new bytes is %0.01f%% of %v total bytes
# removed %v bytes from non-replicas
# changed file count %v (%0.01f%%; total files=%v)
#
`, wouldBe, humanize.Comma(copiedBytes), humanize.Comma(overwrittenBytes), humanize.Comma(copiedBytes-overwrittenBytes), 100*float64(copiedBytes-overwrittenBytes)/float64(totalBytes), humanize.Comma(totalBytes), humanize.Comma(removedBytes), changedFiles, 100*float64(changedFiles)/float64(totalFiles), humanize.Comma(totalFiles))
}
report = fmt.Sprintf(`
# ========================================================
# pilosa-fsck final report
#
# run with -fix: %v
#
# index examined: '%v'
#
# nodes examined: %v
# -replicas %v replication factor used
#
# feature data examined: %v bytes
# feature files examined: %v files
#
# key-translation-stores examined: %v
# key-count: %v over all replicas
# id-count: %v over all replicas
#
# %v
# %v
# ========================================================
`,
cfg.Fix, index, nDir, cfg.ReplicaN, humanize.Comma(totalBytes), humanize.Comma(totalFiles), humanize.Comma(int64(nDir*pilosa.DefaultPartitionN)), humanize.Comma(int64(keyCount)), humanize.Comma(int64(idCount)), actionTaken, fragUpdate)
return
}
func (cfg *FsckConfig) verifyReplicasAvailable(replicas, nonReplicas map[string]bool, nodes2fragsum map[string]*pilosa.IndexFragmentSummary, fragsum *pilosa.FragSum) error {
for node := range replicas {
if nodes2fragsum[node] == nil {
return fmt.Errorf("error: node '%v' needed for a replica set was not availabe. Did you give ALL the directories for your cluster on the command line at once? In nodes2fragsum '%#v' (replicas: '%#v'; non-replicas '%#v') for fragsum '%v'", node, nodes2fragsum, replicas, nonReplicas, fragsum)
}
}
return nil
}
func cp(fromPath, toPath string) (err error) {
tmpTo := toPath + ".fsck.tmp"
toFd, err := os.Create(tmpTo)
if err != nil {
return err
}
defer toFd.Close()
fromFd, err := os.Open(fromPath)
if err != nil {
return err
}
defer fromFd.Close()
_, err = io.Copy(toFd, fromFd)
if err != nil {
return err
}
err = toFd.Close()
if err != nil {
return err
}
return os.Rename(tmpTo, toPath)
}
func (cfg *FsckConfig) getKeyIDCounts(index string, ats *pilosa.AllTranslatorSummary) (keyCount, idCount int) {
for _, sum := range ats.Sums {
if sum.Index == index {
keyCount += sum.KeyCount
idCount += sum.IDCount
}
}
return
}

View file

@ -0,0 +1,445 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"fmt"
"io/ioutil"
"reflect"
"strconv"
"testing"
"time"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/boltdb"
"github.com/pilosa/pilosa/v2/hash"
"github.com/pilosa/pilosa/v2/http"
"github.com/pilosa/pilosa/v2/server"
"github.com/pilosa/pilosa/v2/test"
)
func Test_Repair(t *testing.T) {
// a) setup 1 primary + 3 replicas of disagree-ing cluster dirs.
nNodes := 4
nReplicas := 3
name := t.Name()
var nodeid []string
for i := 0; i < nNodes; i++ {
// work around a bug in the test.MustRunCluster that corrupts
// the .topology file if we only join name with one "_" underscore.
nodeid = append(nodeid, name+"__"+strconv.Itoa(i))
}
c := test.MustRunCluster(t, nNodes,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID(nodeid[0]),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(http.GetOpenTranslateReaderFunc(nil)),
pilosa.OptServerReplicaN(nReplicas),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID(nodeid[1]),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(http.GetOpenTranslateReaderFunc(nil)),
pilosa.OptServerReplicaN(nReplicas),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID(nodeid[2]),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(http.GetOpenTranslateReaderFunc(nil)),
pilosa.OptServerReplicaN(nReplicas),
)},
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID(nodeid[3]),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(http.GetOpenTranslateReaderFunc(nil)),
pilosa.OptServerReplicaN(nReplicas),
)},
)
// note: do not defer c.Close() here. We manually close below.
var nodes []*test.Command
var dirs []string
for i := 0; i < nNodes; i++ {
nd := c.GetNode(i)
nodes = append(nodes, nd)
dirs = append(dirs, nd.Server.Holder().Path())
}
ctx := context.Background()
index := []string{"rick", "morty"}
fieldName := []string{"f", "flying_car"}
idx := make([]*pilosa.Index, len(index))
field := make([]*pilosa.Field, len(index))
var err error
for i := range index {
idx[i], err = nodes[0].API.CreateIndex(ctx, index[i], pilosa.IndexOptions{Keys: true, TrackExistence: true})
if err != nil {
t.Fatalf("creating index: %v", err)
}
if idx[i].CreatedAt() == 0 {
t.Fatal("index createdAt is empty")
}
field[i], err = nodes[0].API.CreateField(ctx, index[i], fieldName[i], pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, 100))
if err != nil {
t.Fatalf("creating field: %v", err)
}
if field[i].CreatedAt() == 0 {
t.Fatal("field createdAt is empty")
}
}
rowID := uint64(1)
timestamp := int64(0)
for i := range index {
// Generate some keyed records.
rowIDs := []uint64{}
timestamps := []int64{}
N := 10
for j := 1; j <= N; j++ {
rowIDs = append(rowIDs, rowID)
timestamps = append(timestamps, timestamp)
}
var colKeys []string
switch i {
case 0:
// Keys are sharded so ordering is not guaranteed.
colKeys = []string{"col10", "col8", "col9", "col6", "col7", "col4", "col5", "col2", "col3", "col1"}
colKeys = colKeys[:N]
case 1:
colKeys = []string{"col11", "col12"}
N = len(colKeys)
rowIDs = rowIDs[:N]
timestamps = timestamps[:N]
}
// Import data with keys to the coordinator (node0) and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
req := &pilosa.ImportRequest{
Index: index[i],
IndexCreatedAt: idx[i].CreatedAt(),
Field: fieldName[i],
FieldCreatedAt: field[i].CreatedAt(),
// even though this says Shard: 0, that won't matter. The column keys
// get hashed and that decides the actual shard.
Shard: 0,
RowIDs: rowIDs,
ColumnKeys: colKeys,
Timestamps: timestamps,
}
qcx := nodes[0].API.Txf().NewQcx()
if err := nodes[0].API.Import(ctx, qcx, req); err != nil {
t.Fatal(err)
}
panicOn(qcx.Finish())
//qcx.Reset()
pql := fmt.Sprintf("Row(%s=%d)", fieldName[i], rowID)
// Query node0.
if res, err := nodes[0].API.Query(ctx, &pilosa.QueryRequest{Index: index[i], Query: pql}); err != nil {
t.Fatal(err)
} else if keys := res.Results[0].(*pilosa.Row).Keys; !reflect.DeepEqual(keys, colKeys) {
t.Fatalf("expected colKeys='%#v'; observed column keys: %#v", colKeys, keys)
}
// Query node1.
if err := test.RetryUntil(5*time.Second, func() error {
if res, err := nodes[1].API.Query(ctx, &pilosa.QueryRequest{Index: index[i], Query: pql}); err != nil {
return err
} else if keys := res.Results[0].(*pilosa.Row).Keys; !reflect.DeepEqual(keys, colKeys) {
return fmt.Errorf("unexpected column keys: %#v", keys)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
// end of setup.
// partitionID in use: 6, 31, 57, 133, 185, 235
targetPartition := 31 // which partitionID we mess with.
targetNode := nodes[0] // this is the first replica.
targetIndex := index[0]
// 0 first replica
// 1 second replica
// 2 -- not a replica
// 3 primary
cfg := &FsckConfig{
Fix: false,
FixCol: false,
Quiet: true,
//Verbose: true,
ReplicaN: nReplicas,
Dirs: dirs,
ParallelReaders: 5,
}
panicOn(cfg.ValidateConfig())
// for this test, mess up a replica that is not the primary.
h := targetNode.API.Holder()
idx[0] = h.Index(index[0])
store := idx[0].TranslateStore(targetPartition)
fwd, rev := getFwdRev(store, targetPartition)
//vv("targetPartition=%v, store.PartitionID=%v, before corruption, fwd='%#v', rev='%#v'", targetPartition, store.PartitionID, fwd, rev)
// # fsck_test.go:288 2020-10-01T13:39:57.718995-05:00 partition 31, key 'col5' -> db00001
presz := len(rev)
delete(rev, fwd["col5"])
postsz := len(rev)
if postsz == presz {
panic("did not delete any key!")
}
bolt := store.(*boltdb.TranslateStore)
//vv("pre corruption, bolt = '%v'", fileChecksum(bolt.Path))
//bolt.DumpBolt("pre-corruption")
if err := bolt.SetFwdRevMaps(nil, fwd, rev); err != nil {
t.Fatal(err)
}
//vv("post corruption, bolt = '%v'", fileChecksum(bolt.Path))
//bolt.DumpBolt("post-corruption")
//fwd3, rev3 := getFwdRev(store, targetPartition)
//vv("after corruption, fwd='%#v', rev='%#v'", fwd3, rev3)
targetIndex1 := "morty"
targetPartition1 := 226 // for "col11"
// # fsck_test.go:248 2020-10-06T20:24:33.755576-05:00 on k=47, idx[1]: targetPartition=47, store.PartitionID=0x4abe160, before corruption, fwd1='map[string]uint64{"col12":0xcf00001}', rev1='map[uint64]string{0xcf00001:"col12"}'
//# fsck_test.go:248 2020-10-06T20:24:35.608568-05:00 on k=226, idx[1]: targetPartition=226, store.PartitionID=0x4abe160, before corruption, fwd1='map[string]uint64{"col11":0xcc00001}', rev1='map[uint64]string{0xcc00001:"col11"}'
idx[1] = h.Index(index[1])
store1 := idx[1].TranslateStore(targetPartition1)
fwd1, rev1 := getFwdRev(store1, targetPartition1)
//vv("on k=%v, idx[1]: targetPartition=%v, store.PartitionID=%v, before corruption, fwd1='%#v', rev1='%#v'", k, targetPartition1, store.PartitionID, fwd1, rev1)
presz1 := len(rev1)
delete(rev1, fwd1["col11"])
postsz1 := len(rev1)
if postsz1 == presz1 {
panic("did not delete any key!")
}
bolt1 := store1.(*boltdb.TranslateStore)
if err := bolt1.SetFwdRevMaps(nil, fwd1, rev1); err != nil {
t.Fatal(err)
}
// done corrupting.
for _, nd := range nodes {
nd.Command.Close()
}
//panicOn(bolt.Open())
//bolt.DumpBolt("post-corruption, after Close. bolt:")
//bolt.Close()
//chksums := getChecksums(dirs, cfg, targetPartition)
//vv("post corruption, pre repair chksums = '%#v'", chksums)
// first we check that the corruption can be detected
// by our test with the checksums.
chk, err := check(dirs, cfg, targetIndex, targetPartition)
_ = chk
//vv("pre-fix, chk='%v'; err='%v'", chk, err)
if err == nil {
panic("expected to see checksums not match! but no corruption detected.")
}
chk1, err := check(dirs, cfg, targetIndex1, targetPartition1)
_ = chk1
//vv("pre-fix, chk1='%v'; err='%v'", chk1, err)
if err == nil {
panic("expected to see checksums not match! but no corruption detected.")
}
// b) running in reporting mode only should report that a fix is needed.
fixNeeded, err := cfg.Run()
panicOn(err)
if !fixNeeded {
panic("fix should be needed now, before repair")
}
// c) run the fix.
cfg.Fix = true
cfg.FixCol = true
fixNeeded, err = cfg.Run()
panicOn(err)
if !fixNeeded {
panic("fix should be marked needed if repair was made")
}
// d) check that the replicas all look like the primary.
//chksums = getChecksums(dirs, cfg, targetPartition)
//vv("after repair chksums = '%#v'", chksums)
chk, err = check(dirs, cfg, targetIndex, targetPartition)
_ = chk
//vv("chk = '%v' after repair; err='%v'", chk, err)
panicOn(err)
chk1, err = check(dirs, cfg, targetIndex1, targetPartition1)
_ = chk1
//vv("chk = '%v' after repair; err='%v'", chk, err)
panicOn(err)
// e) run again, should see no fix needed.
fixNeeded, err = cfg.Run()
panicOn(err)
if fixNeeded {
panic("should see no fix needed after the prior repair")
}
}
func getFwdRev(store pilosa.TranslateStore, partitionID int) (fwd map[string]uint64, rev map[uint64]string) {
fwd = make(map[string]uint64)
rev = make(map[uint64]string)
_ = store.KeyWalker(func(key string, col uint64) {
//vv("partition %v, key '%v' -> %x", partitionID, key, col)
fwd[key] = col
})
_ = store.IDWalker(func(key string, col uint64) {
//vv("partition %v, id %x -> '%v'", partitionID, col, key)
rev[col] = key
})
return
}
func check(dirs []string, cfg *FsckConfig, targetIndex string, targetPartition int) (chksum string, err error) {
//vv("top of check, dirs = '%#v', targetIndex='%v', targetPartition='%v'", dirs, targetIndex, targetPartition)
//defer vv("returning from check()")
firstChecksum := ""
firstDir := ""
firstStorePath := ""
quiet := cfg.Quiet
defer func() {
cfg.Quiet = quiet
}()
cfg.Quiet = true
for i := range dirs {
dir := dirs[i]
_, _, ats, err := cfg.readOneDir(dir)
panicOn(err)
indexes := indexesFromAts(ats)
//vv("indexes = '%#v'", indexes)
for _, index := range indexes {
if index != targetIndex {
continue
}
for _, s := range ats.Sums {
//vv(" s= '%#v'", s)
if s.Index != index {
//vv("skipping s.Index '%v' != index '%v'", s.Index, index)
continue
}
if s.PartitionID != targetPartition {
continue
}
//vv("accepting s.PartitionID(%v) == targetPartition(%v); s.Index '%v'; "+
//"index '%v'; s.IsPrimary=%v, s.IsReplica=%v, s='%#v'; s.Checksum='%v', firstChecksum='%v'",
//s.PartitionID, targetPartition, s.Index, index,
//s.IsPrimary, s.IsReplica, s, s.Checksum, firstChecksum)
if s.IsPrimary || s.IsReplica {
chksum := s.Checksum
if firstChecksum == "" {
firstChecksum = chksum
firstDir = dir
firstStorePath = s.StorePath
} else {
//vv("targetIndex = '%v'; firstChecksum='%v', chksum='%v'", targetIndex, firstChecksum, chksum)
if chksum != firstChecksum {
return chksum, fmt.Errorf("bolt chksum on node %v '%v' disagrees with '%v' on '%v'; index='%v'; s.StorePath = '%v'; firstStorePath='%v'", dir, chksum, firstChecksum, firstDir, index, s.StorePath, firstStorePath)
}
}
}
}
}
}
return firstChecksum, nil
}
var _ = getChecksums
func getChecksums(dirs []string, cfg *FsckConfig, targetPartition int) (chksum []string) {
for i := range dirs {
dir := dirs[i]
_, _, ats, err := cfg.readOneDir(dir)
panicOn(err)
for _, s := range ats.Sums {
if s.PartitionID != targetPartition {
continue
}
chksum = append(chksum, s.Checksum)
}
}
return
}
/* on shardwidth 20
# fsck_test.go:211 2020-09-30T17:19:05.823278-05:00 partition 6, key 'col2' -> dc00001
# fsck_test.go:214 2020-09-30T17:19:05.823309-05:00 partition 6, id dc00001 -> 'col2'
# fsck_test.go:211 2020-09-30T17:19:05.823430-05:00 partition 31, key 'col5' -> db00001
# fsck_test.go:214 2020-09-30T17:19:05.823447-05:00 partition 31, id db00001 -> 'col5'
# fsck_test.go:211 2020-09-30T17:19:05.823970-05:00 partition 57, key 'col10' -> 5d00001
# fsck_test.go:214 2020-09-30T17:19:05.823998-05:00 partition 57, id 5d00001 -> 'col10'
# fsck_test.go:211 2020-09-30T17:19:05.827007-05:00 partition 133, key 'col7' -> d900001
# fsck_test.go:214 2020-09-30T17:19:05.827071-05:00 partition 133, id d900001 -> 'col7'
# fsck_test.go:211 2020-09-30T17:19:05.827549-05:00 partition 185, key 'col3' -> dd00001
# fsck_test.go:214 2020-09-30T17:19:05.827573-05:00 partition 185, id dd00001 -> 'col3'
# fsck_test.go:211 2020-09-30T17:19:05.827792-05:00 partition 235, key 'col9' -> d700001
# fsck_test.go:214 2020-09-30T17:19:05.827809-05:00 partition 235, id d700001 -> 'col9'
*/
var _ = fileChecksum
func fileChecksum(path string) string {
by, err := ioutil.ReadFile(path)
panicOn(err)
return hash.Blake3sum16(by)
}

View file

@ -0,0 +1 @@
pilosa-fsck

View file

@ -0,0 +1,252 @@
Design for pilosa-fsck
======================
Problem Background
------------------
Molecula Pilosa provides replication for fault-tolerance within a Pilosa cluster.
Three kinds of data are replicated: Roaring bitmap data, Column-Key translation data,
and Row-Key data are replicated. Only the first two, Roaring data and Column-Key
data are relevant here. Broadly, the Roaring bitmap data
forms the central features -- the bits -- of a large, sparse bitmap matrix.
The Column-Keys are the labels for the columns at the top margin of this matrix.
For speed, the Roaring bitmap data is stored separately from the
Key data. The Roaring data is stored in sharded files
within a directory heirarchy under PILOSA-DATA-DIR/index_name/field_name/...
The Key translation data is stored in sharded BoltDB databases within
the PILOSA-DATA-DIR/index_name/_key directory.
The current approach to Roaring file replication involves an
eventually consistent mechanism that uses an Anti-Entropy agent to
fix partial or incomplete replication from the primary shard to all
replica shards.
Unfortunately, the Anti-Entropy agent approach has proved inadequate on two
fronts. First, it does not provide for immediately consistent reads in the
event that the primary is lost. Second, the Anti-Entropy agent itself experienced
out-of-memory issues that have yet to be resolved.
Therefore, work is now underway to replace this replication
approach with a more consistent design.
However, in the meantime, for our customers in production with Molecula
Pilosa, we wish to provide a means to re-establish correct replication.
Thus even in the event of a node failure followed by a read from a replica, the
returned read will be correct.
The pilosa-fsck tool can therefore be seen as a temporary, stop-gap
measure to address immediate issues while the cluster replication
mechanism is replaced.
The second factor motivating the creation of pilosa-fsck was the discovery
of a bug in the Key-translation process. Unfortunately this was a hard
to reproduce bug. It happened only on the customer's premises,
and only after running the system for a long time, with a
large amount of data, and with various eccentric node failures
and recoveries.
However, we were able to reproduce a plausible explanation.
Non-primary replicas were creating keys when they should have been
forwarding the request to the primary. Correcting this bug is impetus
for the v2.1.4 release of Molecula Pilosa.
A fine point here: since we were not able to precisely reproduce the customer's
issue in the development environment, we cannot guarantee with 100%
certainty that we have actually addressed the bug that the customer
was seeing.
Therefore we also desired an additional insurance
policy. We wished to be able to empower customers to proactively discover any
future Key-translation issues that happen in their on-premise systems.
To do this, we proposed providing select customers with the pilosa-fsck
tool which can analyze their offline backups for issues.
Optionally, these issues can also be repaired in-place in the
offline backup on which pilosa-fsck is run.
The -fix flag repairs both kinds of replication issues.
Solution Approach: mechanism of action
--------------------------------------
The pilosa-fsck is run offline on a full set of backups taken from
all nodes in a Pilosa cluster. It runs on a single computer that
must be separate from the production or staging Pilosa environments.
When run, pilosa-fsck analyzes the differences between the
primary and its replicas. Both the Roaring
files and the Key translation databases are analyzed.
The computer running pilosa-fsck must have the same or more
memory as the Pilosa nodes in the cluster, as it will
"pretend" to be each Pilosa node in turn. However, as each
node's backup is closed before the next node's backup is
opened, we do not require substantially more memory than a single
production node. Short Blake3 cryptographic checksums are
computed for each Roaring fragment and each Key translation
database. These are held in memory (and printed to the log)
for comparing nodes. This comparison forms the heart of
the consistency checks, and is the basis for any subsequent
repair.
We recommend capturing both stdout and stderr to a log.
Use `&> log` or `2>&1 > log` at the end of the
pilosa-fsck invocation to save a log of the run to disk.
In a typical cluster, the Replication factor R may be less
than the number of nodes N in the cluster. For example, while
N may be 4, the R may be only 3. In this example, within
each replicated shard, one node will be the primary for
that shard, two nodes will be non-primary replicas, and one
node will be a non-replica. Note that the designation
of primary changes for different Roaring shards within an index,
even on a single node.
The essence of the the -fix repair operation that pilosa-fsck
can do is this: it will copy from the primary to the
the non-primary replicas. Further, it will remove data from
any non-replica node if it was mistakenly present.
The pilosa-fsck output log will contain
a sequence of command line 'cp' and 'rm' commands.
These commands are merely a record (with
accompanying justifcation in the comment following the
command) of what actions would be performed to repair
the Roaring file data.
Only with -fix will the repair actions actually happen
during the pilosa-fsck run.
Details: running pilosa-fsck
----------------------------
Errors in invocation are reported on stderr and the program will exit with a non-zero
error code if invocation errors are present. A non-zero error code
is returned if a repair is needed and -fix was not given.
A -fix run will return a zero error code to the shell if the fix was
successfully made; or if no fix was required.
The log of the run is printed to stdout.
The -h flag to pilosa-fsck prints a summary of its operation
and a guide to laying out the backup directories.
The help is reproduced below.
~~~
$ pilosa-fsck version: Molecula Pilosa v2.2.1-43-g9dacbccf (Oct 5 2020 1:28PM, 9dacbccf)
Use: pilosa-fsck -replicas R {-fix} {-q} /backup/1/.pilosa /backup/2/.pilosa ... /backup/N/.pilosa
-fix
(warning: alters the backed-up node images on disk) copy primary data to replicas to create a consistent cluster.
-replicas R
(required) R is a positive integer, giving the replicaN or replicator factor for the cluster. This is
the number of replicas maintained in the cluster. Must be the same as the
[cluster] 'replicas = R' entry shared across all the pilosa.conf files on each node.
-q
be very quiet during analysis and repair
Welcome to pilosa-fsck. This is a scan and repair
tool that is modeled after the classic unix file
system utility fsck.
WARNING: DO NOT RUN ON A LIVE SYSTEM.
The most important point to remember is that analysis
and repair must be done *offline*.
Just as fsck must be run on an unmounted disk,
pilosa-fsck must be run on a backup. It must
not be run on the directories where a live Pilosa system
is serving queries. Instead, take a backup first.
A backup is a set of N Pilosa data directories that have been
copied from your live system. They must all
be visible and mounted on one filesystem together.
pilosa-fsck can be run in scan-mode (without -fix),
or in repair-mode with -fix. The console output
supplies a log documenting the analysis
and showing what data changes would have been made.
REQUIRED COMMAND LINE ARGUMENTS
The paths to all the top-level Pilosa
data directories in a cluster must be given on the command
line. The -replicas R flag is also always required. It
must be correct for your cluser. Here R is the same as
the [cluster] stanza "replicas = R" line from your
pilosa.conf.
Example:
Suppose you are ready to run pilosa-fsck:
you have taken a backup of your four node Pilosa
cluster and stored it all on one filesystem with
all nodes visible and uncompressed. This
is a pre-requisite to running pilosa-fsck.
Let's suppose we have replication R = 3 set.
In this example, have stored our backed-up directories in
/backup/molecula
and the four node backups are in
subdirectories node1/ node2/ node3/ node4/ under this:
/backup/molecula/node1/
/backup/molecula/node1/.pilosa/.id
/backup/molecula/node1/.pilosa/.topology
/backup/molecula/node1/.pilosa/myindex
/backup/molecula/node2/
/backup/molecula/node2/.pilosa/.id
/backup/molecula/node2/.pilosa/.topology
/backup/molecula/node2/.pilosa/myindex
/backup/molecula/node3/
/backup/molecula/node3/.pilosa/.id
/backup/molecula/node3/.pilosa/.topology
/backup/molecula/node3/.pilosa/myindex
/backup/molecula/node4/
/backup/molecula/node4/.pilosa/.id
/backup/molecula/node4/.pilosa/.topology
/backup/molecula/node4/.pilosa/myindex
NOTE: your .pilosa directories need not be named .pilosa. They can
be something else, such as when the -d flag to pilosa server was used.
The .id file, the .topology file, and the index directories must be
found directly underneath.
Then a typical invocation to scan a cluster backup for issues:
$ cd /backup/molecula/
$ pilosa-fsck -replicas 3 node1/.pilosa node2/.pilosa node3/.pilosa node4/.pilosa &> log
A typical invocation to repair the replication in the same backup:
$ pilosa-fsck -replicas 3 -fix node1/.pilosa node2/.pilosa node3/.pilosa node4/.pilosa &> log
In both cases, the .id and .topology files must
be present in the backups.
Without -fix, no modifications will be made to the backups. Only
by running with -fix will repairs be made. The user can safely
always run with -fix to repair only if needed.
A zero error code will be returned to the shell if no repairs were needed.
A zero error code will be also be returned to the shell if
repairs were needed and they were accomplished under -fix.
A non-zero error code indicates that repairs were needed but
were not made.
~~~

Binary file not shown.

View file

@ -0,0 +1,21 @@
#!/bin/bash
set +x
export PATH=.:${PATH}
# unpack the sample Molecula Pilosa cluster.
tar xf backups.tar.gz
# check if repair is needed.
pilosa-fsck -replicas 3 backups/node0/pilosa backups/node1/pilosa backups/node2/pilosa backups/node3/pilosa
# yes, so do the repairs. This can be done first (only) as well.
#
pilosa-fsck -fix -replicas 3 backups/node0/pilosa backups/node1/pilosa backups/node2/pilosa backups/node3/pilosa
# check again if you like
#
pilosa-fsck -replicas 3 backups/node0/pilosa backups/node1/pilosa backups/node2/pilosa backups/node3/pilosa

177
cmd/pilosa-fsck/vprint.go Normal file
View file

@ -0,0 +1,177 @@
// home: https://github.com/glycerine/vprint
// Copyright 2019 Jason E. Aten, Ph.D. All rights reserved.
// License: MIT
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main
import (
"fmt"
"io"
"os"
"path"
"runtime"
"runtime/debug"
"sync"
"time"
)
const RFC3339MsecTz0 = "2006-01-02T15:04:05.000Z07:00"
const RFC3339UsecTz0 = "2006-01-02T15:04:05.000000Z07:00"
// for tons of debug output
var VerboseVerbose bool = false
// convience functions for . import
var pp = PP
var vv = VV
var panicOn = PanicOn
func init() {
// keeper linter happy
_ = pp
_ = vv
}
func PanicOn(err error) {
if err != nil {
panic(err)
}
}
func PP(format string, a ...interface{}) {
if VerboseVerbose {
TSPrintf(format, a...)
}
}
func VV(format string, a ...interface{}) {
TSPrintf(format, a...)
}
func AlwaysPrintf(format string, a ...interface{}) {
TSPrintf(format, a...)
}
var tsPrintfMut sync.Mutex
// time-stamped printf
func TSPrintf(format string, a ...interface{}) {
tsPrintfMut.Lock()
Printf("# %s %s ", FileLine(3), ts())
Printf(format+"\n", a...)
tsPrintfMut.Unlock()
}
// get timestamp for logging purposes
func ts() string {
return time.Now().Format(RFC3339UsecTz0)
}
// so we can multi write easily, use our own printf
var OurStdout io.Writer = os.Stdout
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(OurStdout, format, a...)
}
func FileLine(depth int) string {
_, fileName, fileLine, ok := runtime.Caller(depth)
var s string
if ok {
s = fmt.Sprintf("%s:%d", path.Base(fileName), fileLine)
} else {
s = ""
}
return s
}
func stack() string {
return string(debug.Stack())
}
func FileExists(name string) bool {
fi, err := os.Stat(name)
if err != nil {
return false
}
if fi.IsDir() {
return false
}
return true
}
func DirExists(name string) bool {
fi, err := os.Stat(name)
if err != nil {
return false
}
if fi.IsDir() {
return true
}
return false
}
func FileSize(name string) int64 {
fi, err := os.Stat(name)
if err != nil {
return 0
}
return fi.Size()
}
// Caller returns the name of the calling function.
func Caller(upStack int) string {
// elide ourself and runtime.Callers
target := upStack + 2
pc := make([]uintptr, target+2)
n := runtime.Callers(0, pc)
f := runtime.Frame{Function: "unknown"}
if n > 0 {
frames := runtime.CallersFrames(pc[:n])
for i := 0; i <= target; i++ {
contender, more := frames.Next()
if i == target {
f = contender
}
if !more {
break
}
}
}
return f.Function
}
// happy linter:
var _ = DirExists
var _ = FileExists
var _ = Caller
var _ = stack
var _ = RFC3339MsecTz0
var _ = RFC3339UsecTz0
var _ = AlwaysPrintf
var _ = FileSize

564
cmd/random-query/main.go Normal file
View file

@ -0,0 +1,564 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"flag"
"fmt"
"math"
"math/rand"
nethttp "net/http"
"os"
"strconv"
"strings"
"time"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/http"
)
// RandomQueryConfig
type RandomQueryConfig struct {
// user facing flags
HostPort string // -hostport
TreeDepth int // -d
QueryCount int // -n
Verbose bool // -v
VeryVerbose bool // -V
TimeFromArg string // --time.from
TimeToArg string // --time.to
TimeFrom time.Time // parsed time
TimeTo time.Time // parsed time
TimeRange int64 // hours between parsed times
IndexMap map[string]*Features
API *pilosa.API
Info []*pilosa.IndexInfo
BitmapFunc []string
Rnd *rand.Rand
}
type API interface {
// InternalClient
Schema(ctx context.Context) ([]*pilosa.IndexInfo, error)
Query(ctx context.Context, index string, queryRequest *pilosa.QueryRequest) (*pilosa.QueryResponse, error)
// API for contrast; just a little different:
//Schema(ctx context.Context) []*IndexInfo
//Query(ctx context.Context, req *pilosa.QueryRequest) (pilosa.QueryResponse, error)
}
// have to wrap because the ugly little differences between InternalClient and API
type wrapper struct {
api *pilosa.API
}
func (w *wrapper) Schema(ctx context.Context) ([]*pilosa.IndexInfo, error) {
return w.api.Schema(ctx), nil
}
func (w *wrapper) Query(ctx context.Context, index string, queryRequest *pilosa.QueryRequest) (*pilosa.QueryResponse, error) {
r, err := w.api.Query(ctx, queryRequest)
return &r, err
}
func wrapApiToInternalClient(api *pilosa.API) *wrapper {
return &wrapper{api: api}
}
// These times are copied from the "kitchen sink" data generator to serve as defaults.
var defaultEndTime = time.Date(2020, time.May, 4, 12, 2, 28, 0, time.UTC)
var defaultStartTime = defaultEndTime.Add(-5 * 365 * 24 * time.Hour)
// call DefineFlags before myflags.Parse()
func (cfg *RandomQueryConfig) DefineFlags(fs *flag.FlagSet) {
fs.StringVar(&cfg.HostPort, "hostport", "localhost:10101", "host:port of pilosa to run random queries on.")
fs.IntVar(&cfg.TreeDepth, "d", 4, "depth of random queries to generate.")
fs.IntVar(&cfg.QueryCount, "n", 100, "number of random queries to generate. Set to 0 for inifinite queries.")
fs.BoolVar(&cfg.Verbose, "v", false, "show queries as they are generated")
fs.BoolVar(&cfg.VeryVerbose, "V", false, "show query results")
fs.StringVar(&cfg.TimeFromArg, "time.from", defaultStartTime.Format(time.RFC3339), "starting time for time fields (format: 2006-01-02T15:04:05Z07:00)")
fs.StringVar(&cfg.TimeToArg, "time.to", defaultEndTime.Format(time.RFC3339), "starting time for time fields (format: 2006-01-02T15:04:05Z07:00)")
}
// call c.ValidateConfig() after myflags.Parse()
func (c *RandomQueryConfig) ValidateConfig() error {
if c.TreeDepth < 1 {
return fmt.Errorf("-d depth must be 1 or greater; saw %v", c.TreeDepth)
}
if c.QueryCount < 0 {
return fmt.Errorf("-n count must be 0 or greater; saw %v", c.QueryCount)
}
var err error
c.TimeFrom, err = time.Parse(time.RFC3339, c.TimeFromArg)
if err != nil {
return fmt.Errorf("-time.from value couldn't be parsed: %w", err)
}
c.TimeTo, err = time.Parse(time.RFC3339, c.TimeToArg)
if err != nil {
return fmt.Errorf("-time.to value couldn't be parsed: %w", err)
}
c.TimeRange = int64(c.TimeTo.Sub(c.TimeFrom).Hours())
if c.TimeRange < 1 {
return fmt.Errorf("time.to (%s) should be at least one hour after time.from (%s)",
c.TimeToArg, c.TimeFromArg)
}
return nil
}
var ProgramName = "random-query"
func main() {
myflags := flag.NewFlagSet(ProgramName, flag.ExitOnError)
cfg := NewRandomQueryConfig()
cfg.DefineFlags(myflags)
err := myflags.Parse(os.Args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "\n%v\n", err.Error())
os.Exit(1)
}
err = cfg.ValidateConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "%s error: %s\n", ProgramName, err)
os.Exit(1)
}
err = cfg.Run()
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
func (cfg *RandomQueryConfig) Run() (err error) {
remoteClient := nethttp.DefaultClient
cli, err := http.NewInternalClient(cfg.HostPort, remoteClient)
if err != nil {
return err
}
ctx := context.Background()
totalQ := 0
loops := 0
t0 := time.Now()
report := func() {
dur := time.Since(t0)
if dur > 0 {
qps := 1e9 * float64(totalQ) / float64(dur)
AlwaysPrintf("totalQueries run: %v elapsed: %v qps: %0.02f", totalQ, dur, qps)
} else {
AlwaysPrintf("totalQueries run: %v elapsed: %v qps: N/A", totalQ, dur)
}
}
defer report()
NewSetup:
err = cfg.Setup(cli)
if err != nil {
return err
}
if len(cfg.IndexMap) == 0 {
return fmt.Errorf("no rows to query")
}
var indexes []string
for index := range cfg.IndexMap {
indexes = append(indexes, index)
}
for j := 0; ; j++ {
if cfg.QueryCount > 0 {
if j >= cfg.QueryCount {
break
}
} else {
// else keep doing queries forever...
if loops > 0 && loops%500 == 0 {
// ...but account for any new data arrived by getting
// the schema and rows again every so often.
loops++
goto NewSetup
}
}
if totalQ > 0 && totalQ%100 == 0 {
report()
}
index := indexes[cfg.Rnd.Intn(len(indexes))]
pql, err := cfg.GenQuery(index)
panicOn(err)
if cfg.Verbose {
fmt.Printf("pql = '%v'\n", pql)
}
// Query node0.
res, err := cli.Query(ctx, index, &pilosa.QueryRequest{Index: index, Query: pql})
if err != nil {
AlwaysPrintf("QUERY FAILED! queries before this=%v; err = '%v', pql='%v'", loops, err, pql)
return err
}
if cfg.VeryVerbose {
fmt.Printf("success on pql = '%v'; res='%v'\n", pql, res.Results[0])
}
totalQ++
loops++
}
return nil
}
type Features struct {
Slc []IndexFieldRow
Ranges []IndexFieldRange
Distinctables []IndexFieldRange
SlcWeight int
RangeWeight int
}
// Pick either a feature entry or a random query on a range, weighted
// by number of features and approximate weight of ranges
func (f *Features) RandomQuery(cfg *RandomQueryConfig) *Tree {
r := cfg.Rnd.Intn(f.SlcWeight + f.RangeWeight)
if r < f.SlcWeight {
return f.Slc[r].Query(cfg)
}
r = cfg.Rnd.Intn(len(f.Ranges))
return f.Ranges[r].Query(cfg)
}
func NewRandomQueryConfig() *RandomQueryConfig {
return &RandomQueryConfig{
IndexMap: make(map[string]*Features),
}
}
type IndexFieldRow struct {
Index string
Field string
RowID uint64
RowKey string
IsRowKey bool
HasTime bool
IsInt bool
}
func (fea *IndexFieldRow) Query(cfg *RandomQueryConfig) *Tree {
fromTo := ""
// 5% of queries on a time field will use the standard view
// anyway.
if fea.HasTime && cfg.Rnd.Int63n(20) != 0 {
startHours := (cfg.Rnd.Int63n(cfg.TimeRange - 1))
endHours := cfg.Rnd.Int63n(cfg.TimeRange - startHours) + 1 + startHours
startTime := cfg.TimeFrom.Add(time.Duration(startHours) * time.Hour)
endTime := cfg.TimeFrom.Add(time.Duration(endHours) * time.Hour)
fromTo = fmt.Sprintf(", from=%s, to=%s",
startTime.Format(pilosaTimeFmt),
endTime.Format(pilosaTimeFmt))
}
if fea.IsRowKey {
return &Tree{S: fmt.Sprintf("Row(%v='%v'%s)", fea.Field, fea.RowKey, fromTo)}
}
return &Tree{S: fmt.Sprintf("Row(%v=%v%s)", fea.Field, fea.RowID, fromTo)}
}
type IndexFieldRange struct {
Index string
Field string
Min, Max, Scale int64
ScaleDiv float64
Range uint64
}
// We want to pick one of (1) a single-operation filter, (2) a
// between-filter of some kind.
// So, that's one of <=, >=, ==, !=, >, <, or
// one of [<, <], [<, <=], [<=, <=], [<=, <].
var binaryOps = []string{
"<=", ">=", "==", "!=", "<", ">",
}
func (i *IndexFieldRange) Query(cfg *RandomQueryConfig) *Tree {
r := cfg.Rnd.Int63n(10)
// this is unevenly weighted, but there's no Uint64N, and
// Int63n can't represent the whole range.
v1 := cfg.Rnd.Uint64() % i.Range
v2 := cfg.Rnd.Uint64() % i.Range
if v1 > v2 {
v1, v2 = v2, v1
}
v1 = v1 + uint64(i.Min)
v2 = v2 + uint64(i.Min)
var v1s, v2s string
if i.Scale != 0 {
v1s = fmt.Sprintf("%.*f", i.Scale, float64(int64(v1)) / i.ScaleDiv)
v2s = fmt.Sprintf("%.*f", i.Scale, float64(int64(v2)) / i.ScaleDiv)
} else {
v1s = strconv.FormatInt(int64(v1), 10)
v2s = strconv.FormatInt(int64(v2), 10)
}
if r < 4 {
lte := "<="
op1 := lte[:1+(r&1)]
op2 := lte[:1+((r>>1)&1)]
return &Tree{S: fmt.Sprintf("Row(%s %s %s %s %s)",
v1s, op1, i.Field, op2, v2s)}
} else {
if cfg.Rnd.Int63n(2) == 1 {
v1s = v2s
}
return &Tree{S: fmt.Sprintf("Row(%s %s %s)", i.Field, binaryOps[r - 4], v1s)}
}
}
// Run a RandomQuery takes a list of RowIDFeatures and ColumnKeyObjects
// and spits back a PQL query
//
func (cfg *RandomQueryConfig) Setup(api API) (err error) {
ctx := context.Background()
cfg.Info, err = api.Schema(ctx)
if err != nil {
return err
}
foundIntField := false
for i, ii := range cfg.Info {
_ = i
for k, fld := range ii.Fields {
_ = k
switch fld.Options.Type {
case "set", "mutex", "time":
pql := fmt.Sprintf("Rows(%v)", fld.Name)
res, err := api.Query(ctx, ii.Name, &pilosa.QueryRequest{Index: ii.Name, Query: pql})
panicOn(err)
if cfg.VeryVerbose {
fmt.Printf("success on pql = '%v'; res='%v'\n", pql, res.Results[0])
}
// if the option is set to use RowKeys, then must get the Keys instead of the Rows from the RowIdentifiers.
// e.g.
// success on pql = 'Rows(aba)'; res='&pilosa.RowIdentifiers{Rows:[]uint64(nil), Keys:[]string{"aba1", "aba2"}
// success on pql = 'Rows(f)'; res='pilosa.RowIdentifiers{Rows:[]uint64{0x1}, Keys:[]string(nil), field:"f"}'
switch x := res.Results[0].(type) {
case *pilosa.RowIdentifiers:
// internalClient gets this
cfg.AddResponse(ii.Name, fld.Name, x, fld.Options.Type == "time")
case pilosa.RowIdentifiers:
// test gets this
cfg.AddResponse(ii.Name, fld.Name, &x, fld.Options.Type == "time")
}
case "int":
foundIntField = true
fallthrough // I bet you thought you'd never see this used
case "decimal":
// we'll ignore row keys and just use value ranges
cfg.AddIntField(ii.Name, fld.Name, fld.Options.Min, fld.Options.Max, fld.Options.Scale, fld.Options.Type == "decimal")
default:
AlwaysPrintf("ignoring field %q: unhandled type %q\n", fld.Name, fld.Options.Type)
}
}
}
cfg.BitmapFunc = []string{"Union", "Intersect", "Xor", "Not", "Difference"}
if foundIntField {
cfg.BitmapFunc = append(cfg.BitmapFunc, "Distinct")
}
seed := int64(42)
cfg.Rnd = rand.New(rand.NewSource(seed))
return nil
}
func (cfg *RandomQueryConfig) AddResponse(index, field string, x *pilosa.RowIdentifiers, hasTime bool) {
for _, rowID := range x.Rows {
cfg.AddFeature(index, field, rowID, "", false, hasTime)
}
for _, rowKey := range x.Keys {
cfg.AddFeature(index, field, 0, rowKey, true, hasTime)
}
}
const maxEffectiveRange = 1000000
func (cfg *RandomQueryConfig) AddIntField(index, field string, min, max pql.Decimal, scale int64, decimal bool) {
f, ok := cfg.IndexMap[index]
if !ok {
f = &Features{}
cfg.IndexMap[index] = f
}
if min.Scale != scale || max.Scale != scale {
panic(fmt.Sprintf("scale error; min scale %d, max scale %d, field scale %d, assumed they'd be equal",
min.Scale, max.Scale, scale))
}
effectiveRange := uint64(max.Value) - uint64(min.Value) + 1
// if you have INT64_MAX and INT64_MIN, effectiveRange is 1<<64, which
// wraps to 0. Anything closer together will be fine. We accept the loss
// of accuracy in the range from not representing quite the full value
// in that edge case.
if effectiveRange == 0 {
effectiveRange--
}
// we assume that the Value of the field is already scaled, I guess?
newRange := IndexFieldRange{
Index: index,
Field: field,
Min: min.Value,
Max: max.Value,
Scale: scale,
ScaleDiv: math.Pow(10, float64(scale)),
Range: effectiveRange,
}
f.Ranges = append(f.Ranges, newRange)
if !decimal {
f.Distinctables = append(f.Distinctables, newRange)
}
// We want to add more values for larger int fields, but the
// default KitchenSink field has a range of 1<<64 which would make
// it completely dominate weights, so...
if effectiveRange > maxEffectiveRange {
effectiveRange = maxEffectiveRange
}
f.RangeWeight += int(effectiveRange)
}
func (cfg *RandomQueryConfig) GenQuery(index string) (pql string, err error) {
tree := cfg.GenTree(index, cfg.TreeDepth)
if cfg.Verbose {
fmt.Printf("%v\n", tree.StringIndent(0))
}
pql = tree.ToPQL()
// avoid using too much bandwidth, just count the final bitmap.
pql = fmt.Sprintf("Count(%v)", pql)
return
}
type Tree struct {
Chd []*Tree
S string
Args []string // Extra args to pass after children, such as a field for Distinct.
}
func (tr *Tree) StringIndent(ind int) (s string) {
spc := strings.Repeat(" ", ind)
spc1 := strings.Repeat(" ", ind+1)
var chds []string
leaf := true
if len(tr.Chd) == 0 {
// leaf
} else {
leaf = false
for _, chd := range tr.Chd {
chds = append(chds, chd.StringIndent(ind+1))
}
}
if leaf {
s += fmt.Sprintf("%v %v\n", spc1, tr.S)
} else {
for i, c := range chds {
if i == 0 {
s += fmt.Sprintf("%v %v\n%v", spc, tr.S, c)
} else {
s += fmt.Sprintf("%v", c)
}
}
}
return
}
const pilosaTimeFmt = "2006-01-02T15:04"
func (cfg *RandomQueryConfig) GenTree(index string, depth int) (tr *Tree) {
features := cfg.IndexMap[index]
if depth == 0 {
return features.RandomQuery(cfg)
}
r := cfg.Rnd.Intn(len(cfg.BitmapFunc))
f := cfg.BitmapFunc[r]
tr = &Tree{S: f}
numChild := 2
switch f {
case "Union", "Intersect", "Xor":
numChild = cfg.Rnd.Intn(8) + 2
case "Not":
numChild = 1
case "Difference":
numChild = 2
case "Distinct":
numChild = 1
// sometimes do a bare distinct without a filter
if cfg.Rnd.Intn(10) == 0 {
numChild = 0
}
r = cfg.Rnd.Intn(len(features.Distinctables))
tr.Args = append(tr.Args, fmt.Sprintf("field=%s", features.Distinctables[r].Field))
}
for i := 0; i < numChild; i++ {
tr.Chd = append(tr.Chd, cfg.GenTree(index, depth-1))
}
return
}
func (tr *Tree) ToPQL() (s string) {
if len(tr.Chd) == 0 {
// leaf
return tr.S
}
var chds []string
for _, c := range tr.Chd {
chds = append(chds, c.ToPQL())
}
// If we had no extra args, this does nothing.
chds = append(chds, tr.Args...)
all := strings.Join(chds, ", ")
return fmt.Sprintf("%v(%v)", tr.S, all)
}
func (cfg *RandomQueryConfig) AddFeature(index, field string, rowID uint64, rowKey string, isRowKey bool, hasTime bool) {
f, ok := cfg.IndexMap[index]
if !ok {
f = &Features{}
cfg.IndexMap[index] = f
}
f.Slc = append(f.Slc, IndexFieldRow{
Index: index,
Field: field,
RowID: rowID,
RowKey: rowKey,
IsRowKey: isRowKey,
HasTime: hasTime,
})
f.SlcWeight++
}

View file

@ -0,0 +1,165 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"math/rand"
"strconv"
"testing"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/boltdb"
"github.com/pilosa/pilosa/v2/http"
"github.com/pilosa/pilosa/v2/server"
"github.com/pilosa/pilosa/v2/test"
)
func Test_RandomQuery(t *testing.T) {
cfg := NewRandomQueryConfig()
nNodes := 1
nReplicas := 1
name := t.Name()
var nodeid []string
for i := 0; i < nNodes; i++ {
// work around a bug in the test.MustRunCluster that corrupts
// the .topology file if we only join name with one "_" underscore.
nodeid = append(nodeid, name+"__"+strconv.Itoa(i))
}
c := test.MustRunCluster(t, nNodes,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID(nodeid[0]),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(http.GetOpenTranslateReaderFunc(nil)),
pilosa.OptServerReplicaN(nReplicas),
)},
)
defer c.Close()
var nodes []*test.Command
var dirs []string
for i := 0; i < nNodes; i++ {
nd := c.GetNode(i)
nodes = append(nodes, nd)
dirs = append(dirs, nd.Server.Holder().Path())
}
_ = dirs
ctx := context.Background()
indexes := []string{"rick", "morty"}
fieldName := []string{"f", "flying_car"}
idx := make([]*pilosa.Index, len(indexes))
field := make([]*pilosa.Field, len(indexes))
var err error
for i := range indexes {
idx[i], err = nodes[0].API.CreateIndex(ctx, indexes[i], pilosa.IndexOptions{Keys: true, TrackExistence: true})
if err != nil {
t.Fatalf("creating index: %v", err)
}
if idx[i].CreatedAt() == 0 {
t.Fatal("index createdAt is empty")
}
field[i], err = nodes[0].API.CreateField(ctx, indexes[i], fieldName[i], pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, 100))
if err != nil {
t.Fatalf("creating field: %v", err)
}
if field[i].CreatedAt() == 0 {
t.Fatal("field createdAt is empty")
}
}
timestamp := int64(0)
for i := range indexes {
// Generate some keyed records.
rowIDs := []uint64{}
timestamps := []int64{}
N := 10
for j := 1; j <= N; j++ {
rowIDs = append(rowIDs, uint64(j))
timestamps = append(timestamps, timestamp)
}
var colKeys []string
switch i {
case 0:
// Keys are sharded so ordering is not guaranteed.
colKeys = []string{"col10", "col8", "col9", "col6", "col7", "col4", "col5", "col2", "col3", "col1"}
colKeys = colKeys[:N]
case 1:
colKeys = []string{"col11", "col12"}
N = len(colKeys)
rowIDs = rowIDs[:N]
timestamps = timestamps[:N]
}
// Import data with keys to the coordinator (node0) and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
req := &pilosa.ImportRequest{
Index: indexes[i],
IndexCreatedAt: idx[i].CreatedAt(),
Field: fieldName[i],
FieldCreatedAt: field[i].CreatedAt(),
// even though this says Shard: 0, that won't matter. The column keys
// get hashed and that decides the actual shard.
Shard: 0,
RowIDs: rowIDs,
ColumnKeys: colKeys,
Timestamps: timestamps,
}
//vv("rowIDs = '%#v'", rowIDs)
//vv("colKeys = '%#v'", colKeys)
qcx := nodes[0].API.Txf().NewQcx()
if err := nodes[0].API.Import(ctx, qcx, req); err != nil {
t.Fatal(err)
}
panicOn(qcx.Finish())
//qcx.Reset()
}
// end of setup.
panicOn(cfg.Setup(wrapApiToInternalClient(nodes[0].API)))
for j := 0; j < 4; j++ {
index := indexes[rand.Intn(len(indexes))]
pql, err := cfg.GenQuery(index)
panicOn(err)
//vv("pql = '%v'", pql)
// Query node0.
res, err := nodes[0].API.Query(ctx, &pilosa.QueryRequest{Index: index, Query: pql})
if err != nil {
t.Fatal(err)
}
_ = res
//vv("success on pql = '%v'; res='%v'", pql, res.Results[0])
}
}

177
cmd/random-query/vprint.go Normal file
View file

@ -0,0 +1,177 @@
// home: https://github.com/glycerine/vprint
// Copyright 2019 Jason E. Aten, Ph.D. All rights reserved.
// License: MIT
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main
import (
"fmt"
"io"
"os"
"path"
"runtime"
"runtime/debug"
"sync"
"time"
)
const RFC3339MsecTz0 = "2006-01-02T15:04:05.000Z07:00"
const RFC3339UsecTz0 = "2006-01-02T15:04:05.000000Z07:00"
// for tons of debug output
var VerboseVerbose bool = false
// convience functions for . import
var pp = PP
var vv = VV
var panicOn = PanicOn
func init() {
// keeper linter happy
_ = pp
_ = vv
}
func PanicOn(err error) {
if err != nil {
panic(err)
}
}
func PP(format string, a ...interface{}) {
if VerboseVerbose {
TSPrintf(format, a...)
}
}
func VV(format string, a ...interface{}) {
TSPrintf(format, a...)
}
func AlwaysPrintf(format string, a ...interface{}) {
TSPrintf(format, a...)
}
var tsPrintfMut sync.Mutex
// time-stamped printf
func TSPrintf(format string, a ...interface{}) {
tsPrintfMut.Lock()
Printf("# %s %s ", FileLine(3), ts())
Printf(format+"\n", a...)
tsPrintfMut.Unlock()
}
// get timestamp for logging purposes
func ts() string {
return time.Now().Format(RFC3339UsecTz0)
}
// so we can multi write easily, use our own printf
var OurStdout io.Writer = os.Stdout
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(OurStdout, format, a...)
}
func FileLine(depth int) string {
_, fileName, fileLine, ok := runtime.Caller(depth)
var s string
if ok {
s = fmt.Sprintf("%s:%d", path.Base(fileName), fileLine)
} else {
s = ""
}
return s
}
func stack() string {
return string(debug.Stack())
}
func FileExists(name string) bool {
fi, err := os.Stat(name)
if err != nil {
return false
}
if fi.IsDir() {
return false
}
return true
}
func DirExists(name string) bool {
fi, err := os.Stat(name)
if err != nil {
return false
}
if fi.IsDir() {
return true
}
return false
}
func FileSize(name string) int64 {
fi, err := os.Stat(name)
if err != nil {
return 0
}
return fi.Size()
}
// Caller returns the name of the calling function.
func Caller(upStack int) string {
// elide ourself and runtime.Callers
target := upStack + 2
pc := make([]uintptr, target+2)
n := runtime.Callers(0, pc)
f := runtime.Frame{Function: "unknown"}
if n > 0 {
frames := runtime.CallersFrames(pc[:n])
for i := 0; i <= target; i++ {
contender, more := frames.Next()
if i == target {
f = contender
}
if !more {
break
}
}
}
return f.Function
}
// happy linter:
var _ = DirExists
var _ = FileExists
var _ = Caller
var _ = stack
var _ = RFC3339MsecTz0
var _ = RFC3339UsecTz0
var _ = AlwaysPrintf
var _ = FileSize

160
cmd/rbf.go Normal file
View file

@ -0,0 +1,160 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"errors"
"fmt"
"io"
"strconv"
"github.com/pilosa/pilosa/v2/ctl"
"github.com/spf13/cobra"
)
func newRBFCommand(stdin io.Reader, stdout, stderr io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "rbf",
Short: "Inspect RBF data files.",
Long: `
Provides a set of commands for inspecting RBF data files.
`,
}
cmd.AddCommand(newRBFCheckCommand(stdin, stdout, stderr))
cmd.AddCommand(newRBFDumpCommand(stdin, stdout, stderr))
cmd.AddCommand(newRBFPagesCommand(stdin, stdout, stderr))
cmd.AddCommand(newRBFPageCommand(stdin, stdout, stderr))
return cmd
}
func newRBFCheckCommand(stdin io.Reader, stdout, stderr io.Writer) *cobra.Command {
c := ctl.NewRBFCheckCommand(stdin, stdout, stderr)
cmd := &cobra.Command{
Use: "check [flags] PATH",
Short: "Run consistency check on RBF data.",
Long: `
Executes a consistency check on an RBF data directory.
`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("data directory path required")
} else if len(args) > 1 {
return fmt.Errorf("too many command line arguments")
}
c.Path = args[0]
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return c.Run(context.Background())
},
}
return cmd
}
func newRBFDumpCommand(stdin io.Reader, stdout, stderr io.Writer) *cobra.Command {
c := ctl.NewRBFDumpCommand(stdin, stdout, stderr)
cmd := &cobra.Command{
Use: "dump [flags] PATH PGNO [PGNO...]",
Short: "Prints RBF raw page data",
Long: `
Dumps the raw hex data for one or more RBF pages.
`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("data directory path required")
} else if len(args) == 1 {
return fmt.Errorf("page number required")
}
c.Path = args[0]
for _, arg := range args[1:] {
pgno, err := strconv.Atoi(arg)
if err != nil {
return errors.New("invalid page number")
}
c.Pgnos = append(c.Pgnos, uint32(pgno))
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return c.Run(context.Background())
},
}
return cmd
}
func newRBFPagesCommand(stdin io.Reader, stdout, stderr io.Writer) *cobra.Command {
c := ctl.NewRBFPagesCommand(stdin, stdout, stderr)
cmd := &cobra.Command{
Use: "pages [flags] PATH",
Short: "Prints metadata for the list of all pages",
Long: `
Prints a line for every page in the database with its type/status.
`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("data directory path required")
} else if len(args) > 1 {
return fmt.Errorf("too many command line arguments")
}
c.Path = args[0]
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return c.Run(context.Background())
},
}
flags := cmd.Flags()
flags.BoolVar(&c.WithTree, "with-tree", false, "Display b-tree name for each row")
return cmd
}
func newRBFPageCommand(stdin io.Reader, stdout, stderr io.Writer) *cobra.Command {
c := ctl.NewRBFPageCommand(stdin, stdout, stderr)
cmd := &cobra.Command{
Use: "page [flags] PATH PGNO [PGNO...]",
Short: "Prints data for a page(s)",
Long: `
Prints the header & cell data for one or more pages.
`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("data directory path required")
} else if len(args) == 1 {
return fmt.Errorf("page number required")
}
c.Path = args[0]
for _, arg := range args[1:] {
pgno, err := strconv.Atoi(arg)
if err != nil {
return errors.New("invalid page number")
}
c.Pgnos = append(c.Pgnos, uint32(pgno))
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return c.Run(context.Background())
},
}
return cmd
}

View file

@ -26,23 +26,18 @@ import (
)
func NewRootCommand(stdin io.Reader, stdout, stderr io.Writer) *cobra.Command {
productName := "Pilosa " + pilosa.Version
if pilosa.EnterpriseEnabled {
productName = "Pilosa Enterprise " + pilosa.Version
}
rc := &cobra.Command{
Use: "pilosa",
Use: "pilosa",
// TODO: These short/long descriptions could use some updating.
Short: "Pilosa - A Distributed In-memory Binary Bitmap Index.",
// TODO - is documentation actually there?
Long: `Pilosa is a fast index to turbocharge your database.
This binary contains Pilosa itself, as well as common
tools for administering pilosa, importing/exporting data,
tools for administering Pilosa, importing/exporting data,
backing up, and more. Complete documentation is available
at https://www.pilosa.com/docs/.
` + productName + `
Build Time: ` + pilosa.BuildTime + "\n",
` + pilosa.VersionInfo() + "\n",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
v := viper.New()
err := setAllConfig(v, cmd.Flags(), "PILOSA")
@ -74,6 +69,7 @@ Build Time: ` + pilosa.BuildTime + "\n",
rc.AddCommand(newGenerateConfigCommand(stdin, stdout, stderr))
rc.AddCommand(newImportCommand(stdin, stdout, stderr))
rc.AddCommand(newInspectCommand(stdin, stdout, stderr))
rc.AddCommand(newRBFCommand(stdin, stdout, stderr))
rc.AddCommand(newServeCmd(stdin, stdout, stderr))
rc.AddCommand(newHolderCmd(stdin, stdout, stderr))

View file

@ -55,6 +55,9 @@ func tExec(t *testing.T, cmd *cobra.Command, out io.Reader, w io.WriteCloser) (o
if err := w.Close(); err != nil {
return output, fmt.Errorf("closing cmd's stdout: %v", err)
}
// NOTE: if cmd.Execute doesn't return, then this select (and
// therefore the one-second timeout, won't be reached)
select {
case <-done:
case <-time.After(time.Second * 1):
@ -64,7 +67,7 @@ func tExec(t *testing.T, cmd *cobra.Command, out io.Reader, w io.WriteCloser) (o
}
// ExecNewRootCommand executes the pilosa root command with the given arguments
// and returns it's output. It will fail if the command does not complete within
// and returns its output. It will fail if the command does not complete within
// 1 second.
func ExecNewRootCommand(t *testing.T, args ...string) (string, error) {
out, w := io.Pipe()
@ -145,6 +148,10 @@ func (ct *commandTest) setupCommand(t *testing.T) *cobra.Command {
err = os.Setenv(name, val)
failErr(t, err, fmt.Sprintf("setting environment variable '%s' to '%s'", name, val))
}
// address common case where system might have postgres bind
// setting that an existing running Pilosa is using (causing test
// failures due to port conflict)
os.Setenv("PILOSA_POSTGRES_BIND", "")
// make command and set args
rc := cmd.NewRootCommand(strings.NewReader(""), ioutil.Discard, ioutil.Discard)

View file

@ -15,6 +15,7 @@
package cmd_test
import (
"fmt"
"io/ioutil"
"strings"
"testing"
@ -42,9 +43,10 @@ func TestServerConfig(t *testing.T) {
tests := []commandTest{
// TEST 0
{
args: []string{"server", "--data-dir", actualDataDir, "--cluster.hosts", "localhost:42454,localhost:10110", "--bind", "localhost:42454", "--translation.map-size", "100000"},
args: []string{"server", "--data-dir", actualDataDir, "--cluster.hosts", "localhost:42454,localhost:10110", "--bind", "localhost:42454", "--bind-grpc", "localhost:30112", "--translation.map-size", "100000"},
env: map[string]string{
"PILOSA_DATA_DIR": "/tmp/myEnvDatadir",
"PILOSA_LONG_QUERY_TIME": "1m30s",
"PILOSA_CLUSTER_LONG_QUERY_TIME": "1m30s",
"PILOSA_MAX_WRITES_PER_REQUEST": "2000",
"PILOSA_PROFILE_BLOCK_RATE": "9123",
@ -53,8 +55,10 @@ func TestServerConfig(t *testing.T) {
cfgFileContent: `
data-dir = "/tmp/myFileDatadir"
bind = "localhost:0"
bind-grpc = "localhost:0"
max-writes-per-request = 3000
long-query-time = "1m10s"
[cluster]
disabled = true
replicas = 2
@ -72,6 +76,7 @@ func TestServerConfig(t *testing.T) {
v.Check(cmd.Server.Config.Bind, "localhost:42454")
v.Check(cmd.Server.Config.Cluster.ReplicaN, 2)
v.Check(cmd.Server.Config.Cluster.Hosts, []string{"localhost:42454", "localhost:10110"})
v.Check(cmd.Server.Config.LongQueryTime, toml.Duration(time.Second*90))
v.Check(cmd.Server.Config.Cluster.LongQueryTime, toml.Duration(time.Second*90))
v.Check(cmd.Server.Config.MaxWritesPerRequest, 2000)
v.Check(cmd.Server.Config.Translation.MapSize, 100000)
@ -96,6 +101,7 @@ func TestServerConfig(t *testing.T) {
},
cfgFileContent: `
bind = "localhost:0"
bind-grpc = "localhost:0"
data-dir = "` + actualDataDir + `"
[cluster]
disabled = true
@ -122,6 +128,7 @@ func TestServerConfig(t *testing.T) {
env: map[string]string{},
cfgFileContent: `
bind = "localhost:19444"
bind-grpc = "localhost:29444"
data-dir = "` + actualDataDir + `"
[cluster]
hosts = [
@ -163,28 +170,119 @@ func TestServerConfig(t *testing.T) {
// run server tests
for i, test := range tests {
com := test.setupCommand(t)
executed := make(chan struct{})
var execErr error
go func() {
execErr = com.Execute()
close(executed)
}()
select {
case <-cmd.Server.Started:
case <-executed:
}
if execErr != nil {
t.Fatalf("executing server command: %v", execErr)
}
err := cmd.Server.Close()
failErr(t, err, "closing pilosa server command")
<-executed
failErr(t, execErr, "executing command")
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
com := test.setupCommand(t)
executed := make(chan struct{})
var execErr error
go func() {
execErr = com.Execute()
close(executed)
}()
select {
case <-cmd.Server.Started:
case <-executed:
}
if execErr != nil {
t.Fatalf("executing server command: %v", execErr)
}
err := cmd.Server.Close()
failErr(t, err, "closing pilosa server command")
<-executed
failErr(t, execErr, "executing command")
if err := test.validation(); err != nil {
t.Fatalf("Failed test %d due to: %v", i, err)
}
test.reset()
if err := test.validation(); err != nil {
t.Fatalf("Failed test %d due to: %v", i, err)
}
test.reset()
})
}
}
func TestServerConfig_DeprecateLongQueryTime(t *testing.T) {
actualDataDir, err := ioutil.TempDir("", "")
failErr(t, err, "making data dir")
tests := []commandTest{
// TEST 0
{
args: []string{"server", "--long-query-time", "1m10s"},
env: map[string]string{},
cfgFileContent: `
bind = "localhost:0"
bind-grpc = "localhost:0"
data-dir = "` + actualDataDir + `"
[gossip]
port = "14321"
`,
validation: func() error {
v := validator{}
v.Check(cmd.Server.Config.LongQueryTime, toml.Duration(time.Second*70))
v.Check(toml.Duration(cmd.Server.API.LongQueryTime()), toml.Duration(time.Second*70))
return v.Error()
},
},
// TEST 1
{
args: []string{"server", "--cluster.long-query-time", "1m20s"},
env: map[string]string{},
cfgFileContent: `
bind = "localhost:0"
bind-grpc = "localhost:0"
[gossip]
port = "14321"
`,
validation: func() error {
v := validator{}
v.Check(cmd.Server.Config.Cluster.LongQueryTime, toml.Duration(time.Second*80))
v.Check(toml.Duration(cmd.Server.API.LongQueryTime()), toml.Duration(time.Second*80))
return v.Error()
},
},
// TEST 2: Use old value if both are provided because it is the simplest implementation
{
args: []string{"server", "--long-query-time", "50s", "--cluster.long-query-time", "1m30s"},
env: map[string]string{},
cfgFileContent: `
bind = "localhost:0"
bind-grpc = "localhost:0"
[gossip]
port = "14321"
`,
validation: func() error {
v := validator{}
v.Check(cmd.Server.Config.LongQueryTime, toml.Duration(time.Second*50))
v.Check(toml.Duration(cmd.Server.Config.Cluster.LongQueryTime), toml.Duration(time.Second*90))
v.Check(toml.Duration(cmd.Server.API.LongQueryTime()), toml.Duration(time.Second*90))
return v.Error()
},
},
}
// run server tests
for i, test := range tests {
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
com := test.setupCommand(t)
executed := make(chan struct{})
var execErr error
go func() {
execErr = com.Execute()
close(executed)
}()
select {
case <-cmd.Server.Started:
case <-executed:
}
if execErr != nil {
t.Fatalf("executing server command: %v", execErr)
}
err := cmd.Server.Close()
failErr(t, err, "closing pilosa server command")
<-executed
failErr(t, execErr, "executing command")
if err := test.validation(); err != nil {
t.Fatalf("Failed test %d due to: %v", i, err)
}
test.reset()
})
}
}

261
cmd/slurp/slurp.go Normal file
View file

@ -0,0 +1,261 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"flag"
"fmt"
"io"
"io/ioutil"
gohttp "net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/http"
)
// slurp: slurp is a load-tester for importing bulk data.
// It allows us to measure write performance.
type stateMachine struct {
viewData map[string][]byte
lastIndex string
lastField string
lastShard uint64
state string
client *http.InternalClient
start time.Time
profile string
host string
}
func (r *stateMachine) NewHeader(h *tar.Header, tr *tar.Reader) error {
parts := strings.Split(h.Name, "/")
switch parts[0] {
case "roaring":
index := parts[1]
field := parts[2]
view := parts[4]
shard, err := strconv.ParseUint(parts[6], 10, 64)
panicOn(err)
if index != r.lastIndex || field != r.lastField || shard != r.lastShard {
err := r.Upload()
if err != nil {
return err
}
}
roaringData, err := ioutil.ReadAll(tr)
if err != nil {
return err
}
if _, already := r.viewData[view]; already {
panic(fmt.Sprintf("view '%v' already present!", view))
}
r.viewData[view] = roaringData
r.lastIndex = index
r.lastField = field
r.lastShard = shard
case "bolt":
if r.state == "roaring" {
err := r.Upload()
if err != nil {
return err
}
vv("Finished import %v", time.Since(r.start))
if r.profile != "" {
stopProfile(r.host, r.profile)
}
}
//
uri := GetImportRoaringURI(r.lastIndex, r.lastShard)
switch v := parts[len(parts)-1]; v {
case "keys":
index := parts[1]
fieldName := parts[2]
if fieldName == "_keys" {
//skip index keys are not not real fields so will have no need for field keys
return nil
}
byteData, err := ioutil.ReadAll(tr)
panicOn(err)
br := bytes.NewReader(byteData)
err = r.client.ImportFieldKeys(context.Background(), uri, index, fieldName, false, br)
if err != nil {
return err
}
default:
pilosa.VV("%v", h.Name)
index := parts[1]
partition, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return err
}
byteData, err := ioutil.ReadAll(tr)
panicOn(err)
br := bytes.NewReader(byteData)
err = r.client.ImportIndexKeys(context.Background(), uri, index, int(partition), false, br)
if err != nil {
return err
}
}
}
r.state = parts[0]
return nil
}
func (r *stateMachine) Upload() error {
if len(r.viewData) > 0 {
request := &pilosa.ImportRoaringRequest{
Views: r.viewData,
}
uri := GetImportRoaringURI(r.lastIndex, r.lastShard)
err := r.client.ImportRoaring(context.Background(), uri, r.lastIndex, r.lastField, r.lastShard, false, request)
if err != nil {
return err
}
r.viewData = make(map[string][]byte)
}
return nil
}
func UploadTar(srcFile string, client *http.InternalClient, profile, host string) error {
f, err := os.Open(srcFile)
if err != nil {
return (err)
}
defer f.Close()
var tarReader *tar.Reader
if strings.HasSuffix(srcFile, "gz") {
gzf, err := gzip.NewReader(f)
if err != nil {
return err
}
tarReader = tar.NewReader(gzf)
} else {
tarReader = tar.NewReader(f)
}
runner := &stateMachine{
viewData: make(map[string][]byte),
start: time.Now(),
profile: profile,
host: host,
}
runner.client = client
for {
header, err := tarReader.Next()
if err == io.EOF {
_ = runner.Upload()
break
}
if err != nil {
panicOn(err)
}
err = runner.NewHeader(header, tarReader)
panicOn(err)
}
return nil
}
func main() {
var host string
var profile string
var tarSrcPath string
flag.StringVar(&host, "host", "127.0.0.1:10101", "host to import into")
flag.StringVar(&profile, "profile", "", "profile and save a cpu profile of the import to this file")
flag.StringVar(&tarSrcPath, "src", "q2.tar.gz", "data to import")
flag.Parse()
uri, err := pilosa.NewURIFromAddress(host)
panicOn(err)
globURI = uri
h := &gohttp.Client{}
if profile != "" {
startProfile(host)
}
c, err := http.NewInternalClient(host, h)
panicOn(err)
t0 := time.Now()
println("uploading", tarSrcPath)
panicOn(UploadTar(tarSrcPath, c, profile, host))
vv("total elapsed '%v'", time.Since(t0))
}
func startProfile(host string) {
cli := &gohttp.Client{}
req := &gohttp.Request{
Method: "GET",
URL: &url.URL{
Scheme: "http",
Host: host,
Path: "/cpu-profile/start",
},
}
resp, err := cli.Do(req)
if err != nil {
if resp != nil {
resp.Body.Close()
}
panic(err)
}
}
func stopProfile(host, outfile string) {
cli := &gohttp.Client{}
req := &gohttp.Request{
Method: "GET",
URL: &url.URL{
Scheme: "http",
Host: host,
Path: "/cpu-profile/stop",
},
}
resp, err := cli.Do(req)
if err != nil {
if resp != nil {
resp.Body.Close()
}
panic(err)
}
fd, err := os.Create(outfile)
panicOn(err)
defer fd.Close()
_, err = io.Copy(fd, resp.Body)
panicOn(err)
}
var globURI *pilosa.URI
// get correct node to go to.
func GetImportRoaringURI(index string, shard uint64) *pilosa.URI {
return globURI
}

177
cmd/slurp/vprint.go Normal file
View file

@ -0,0 +1,177 @@
// home: https://github.com/glycerine/vprint
// Copyright 2019 Jason E. Aten, Ph.D. All rights reserved.
// License: MIT
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main
import (
"fmt"
"io"
"os"
"path"
"runtime"
"runtime/debug"
"sync"
"time"
)
const RFC3339MsecTz0 = "2006-01-02T15:04:05.000Z07:00"
const RFC3339UsecTz0 = "2006-01-02T15:04:05.000000Z07:00"
// for tons of debug output
var VerboseVerbose bool = false
// convience functions for . import
var pp = PP
var vv = VV
var panicOn = PanicOn
func init() {
// keeper linter happy
_ = pp
_ = vv
}
func PanicOn(err error) {
if err != nil {
panic(err)
}
}
func PP(format string, a ...interface{}) {
if VerboseVerbose {
TSPrintf(format, a...)
}
}
func VV(format string, a ...interface{}) {
TSPrintf(format, a...)
}
func AlwaysPrintf(format string, a ...interface{}) {
TSPrintf(format, a...)
}
var tsPrintfMut sync.Mutex
// time-stamped printf
func TSPrintf(format string, a ...interface{}) {
tsPrintfMut.Lock()
Printf("\n%s %s ", FileLine(3), ts())
Printf(format+"\n", a...)
tsPrintfMut.Unlock()
}
// get timestamp for logging purposes
func ts() string {
return time.Now().Format(RFC3339UsecTz0)
}
// so we can multi write easily, use our own printf
var OurStdout io.Writer = os.Stdout
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(OurStdout, format, a...)
}
func FileLine(depth int) string {
_, fileName, fileLine, ok := runtime.Caller(depth)
var s string
if ok {
s = fmt.Sprintf("%s:%d", path.Base(fileName), fileLine)
} else {
s = ""
}
return s
}
func stack() string {
return string(debug.Stack())
}
func FileExists(name string) bool {
fi, err := os.Stat(name)
if err != nil {
return false
}
if fi.IsDir() {
return false
}
return true
}
func DirExists(name string) bool {
fi, err := os.Stat(name)
if err != nil {
return false
}
if fi.IsDir() {
return true
}
return false
}
func FileSize(name string) (int64, error) {
fi, err := os.Stat(name)
if err != nil {
return -1, err
}
return fi.Size(), nil
}
// Caller returns the name of the calling function.
func Caller(upStack int) string {
// elide ourself and runtime.Callers
target := upStack + 2
pc := make([]uintptr, target+2)
n := runtime.Callers(0, pc)
f := runtime.Frame{Function: "unknown"}
if n > 0 {
frames := runtime.CallersFrames(pc[:n])
for i := 0; i <= target; i++ {
contender, more := frames.Next()
if i == target {
f = contender
}
if !more {
break
}
}
}
return f.Function
}
// happy linter:
var _ = DirExists
var _ = FileExists
var _ = Caller
var _ = stack
var _ = RFC3339MsecTz0
var _ = RFC3339UsecTz0
var _ = AlwaysPrintf
var _ = FileSize

View file

@ -1,4 +1,4 @@
// Copyright 2017 Pilosa Corp.
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -11,11 +11,9 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// +build enterprise
package server
// +build amd64
import (
_ "github.com/pilosa/pilosa/v2/enterprise"
)
package pilosa
const TxInitialMmapSize = 4 << 30 // 4GB

21
const_other.go Normal file
View file

@ -0,0 +1,21 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !amd64
package pilosa
// this is a stubbed out file to let 386/arm build.
const TxInitialMmapSize = 1 << 30 // 1GB

View file

@ -31,12 +31,12 @@ type CommandWithTLSSupport interface {
}
// SetTLSConfig creates common TLS flags
func SetTLSConfig(flags *pflag.FlagSet, certificatePath *string, certificateKeyPath *string, caCertPath *string, skipVerify *bool, enableClientVerification *bool) {
flags.StringVarP(certificatePath, "tls.certificate", "", "", "TLS certificate path (usually has the .crt or .pem extension)")
flags.StringVarP(certificateKeyPath, "tls.key", "", "", "TLS certificate key path (usually has the .key extension)")
flags.StringVarP(caCertPath, "tls.ca-certificate", "", "", "TLS CA certificate path (usually has the .pem extension)")
flags.BoolVarP(skipVerify, "tls.skip-verify", "", false, "Skip TLS certificate server verification (not secure)")
flags.BoolVarP(enableClientVerification, "tls.enable-client-verification", "", false, "Enable TLS certificate client verification for incoming connections")
func SetTLSConfig(flags *pflag.FlagSet, prefix string, certificatePath *string, certificateKeyPath *string, caCertPath *string, skipVerify *bool, enableClientVerification *bool) {
flags.StringVarP(certificatePath, prefix+"tls.certificate", "", "", "TLS certificate path (usually has the .crt or .pem extension)")
flags.StringVarP(certificateKeyPath, prefix+"tls.key", "", "", "TLS certificate key path (usually has the .key extension)")
flags.StringVarP(caCertPath, prefix+"tls.ca-certificate", "", "", "TLS CA certificate path (usually has the .pem extension)")
flags.BoolVarP(skipVerify, prefix+"tls.skip-verify", "", false, "Skip TLS certificate server verification (not secure)")
flags.BoolVarP(enableClientVerification, prefix+"tls.enable-client-verification", "", false, "Enable TLS certificate client verification for incoming connections")
}
// commandClient returns a pilosa.InternalHTTPClient for the command

View file

@ -46,7 +46,7 @@ func TestExportCommand_Validation(t *testing.T) {
func TestExportCommand_Run(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
cmd := cluster.GetNode(0)
buf := bytes.Buffer{}
stdin, stdout, stderr := GetIO(buf)

View file

@ -20,6 +20,7 @@ import (
"fmt"
"io"
"log"
"math"
"os"
"sort"
"strconv"
@ -27,6 +28,7 @@ import (
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/http"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/server"
"github.com/pkg/errors"
)
@ -102,11 +104,13 @@ func (cmd *ImportCommand) Run(ctx context.Context) error {
if cmd.FieldOptions.Type == "" {
// set the correct type for the field
if cmd.FieldOptions.TimeQuantum != "" {
cmd.FieldOptions.Type = "time"
} else if cmd.FieldOptions.Min != 0 || cmd.FieldOptions.Max != 0 {
cmd.FieldOptions.Type = "int"
cmd.FieldOptions.Type = pilosa.FieldTypeTime
} else if cmd.FieldOptions.Min != pql.NewDecimal(0, 0) || cmd.FieldOptions.Max != pql.NewDecimal(0, 0) {
cmd.FieldOptions.Type = pilosa.FieldTypeInt
} else {
cmd.FieldOptions.Type = "set"
cmd.FieldOptions.Type = pilosa.FieldTypeSet
cmd.FieldOptions.CacheType = pilosa.CacheTypeRanked
cmd.FieldOptions.CacheSize = pilosa.DefaultCacheSize
}
}
err := cmd.ensureSchema(ctx)
@ -163,8 +167,8 @@ func (cmd *ImportCommand) ensureSchema(ctx context.Context) error {
// importPath parses a path into bits and imports it to the server.
func (cmd *ImportCommand) importPath(ctx context.Context, fieldType string, useColumnKeys, useRowKeys bool, path string) error {
// If fieldType is `int`, treat the import data as values to be range-encoded.
if fieldType == pilosa.FieldTypeInt {
return cmd.bufferValues(ctx, useColumnKeys, path)
if fieldType == pilosa.FieldTypeInt || fieldType == pilosa.FieldTypeDecimal {
return cmd.bufferValues(ctx, useColumnKeys, fieldType == pilosa.FieldTypeDecimal, path)
}
return cmd.bufferBits(ctx, useColumnKeys, useRowKeys, path)
}
@ -285,9 +289,13 @@ func (cmd *ImportCommand) importBits(ctx context.Context, useColumnKeys, useRowK
return nil
}
// bufferValues buffers slices of FieldValues to be imported as a batch.
func (cmd *ImportCommand) bufferValues(ctx context.Context, useColumnKeys bool, path string) error {
a := make([]pilosa.FieldValue, 0, cmd.BufferSize)
// bufferValues buffers slices of record identifiers and values to be imported as a batch.
func (cmd *ImportCommand) bufferValues(ctx context.Context, useColumnKeys, parseAsFloat bool, path string) error {
req := &pilosa.ImportValueRequest{
Index: cmd.Index,
Field: cmd.Field,
Shard: math.MaxUint64,
}
var r *csv.Reader
@ -307,6 +315,7 @@ func (cmd *ImportCommand) bufferValues(ctx context.Context, useColumnKeys bool,
r.FieldsPerRecord = -1
rnum := 0
for {
rnum++
@ -325,69 +334,44 @@ func (cmd *ImportCommand) bufferValues(ctx context.Context, useColumnKeys bool,
return fmt.Errorf("bad column count on row %d: col=%d", rnum, len(record))
}
var val pilosa.FieldValue
// Parse column id.
if useColumnKeys {
val.ColumnKey = record[0]
req.ColumnKeys = append(req.ColumnKeys, record[0])
} else if columnID, err := strconv.ParseUint(record[0], 10, 64); err == nil {
req.ColumnIDs = append(req.ColumnIDs, columnID)
} else {
if val.ColumnID, err = strconv.ParseUint(record[0], 10, 64); err != nil {
return fmt.Errorf("invalid column id on row %d: %q", rnum, record[0])
}
return fmt.Errorf("invalid column id on row %d: %q", rnum, record[0])
}
// Parse FieldValue.
value, err := strconv.ParseInt(record[1], 10, 64)
if err != nil {
return fmt.Errorf("invalid value on row %d: %q", rnum, record[1])
}
val.Value = value
a = append(a, val)
// If we've reached the buffer size then import FieldValues.
if len(a) == cmd.BufferSize {
if err := cmd.importValues(ctx, useColumnKeys, a); err != nil {
return err
// Parse value.
if parseAsFloat {
value, err := strconv.ParseFloat(record[1], 64)
if err != nil {
return errors.Wrapf(err, "parseing value '%s' as float", record[1])
}
a = a[:0]
req.FloatValues = append(req.FloatValues, value)
} else {
value, err := strconv.ParseInt(record[1], 10, 64)
if err != nil {
return errors.Wrapf(err, "invalid value on row %d: %q", rnum, record[1])
}
req.Values = append(req.Values, value)
}
// If we've reached the buffer size then import the batch.
if len(req.ColumnKeys) == cmd.BufferSize || len(req.ColumnIDs) == cmd.BufferSize {
if err := cmd.client.ImportValue2(ctx, req, &pilosa.ImportOptions{}); err != nil {
return errors.Wrap(err, "importing values")
}
req.ColumnIDs = req.ColumnIDs[:0]
req.ColumnKeys = req.ColumnKeys[:0]
req.Values = req.Values[:0]
req.FloatValues = req.FloatValues[:0]
}
}
// If there are still values in the buffer then flush them.
return cmd.importValues(ctx, useColumnKeys, a)
}
// importValues sends batches of FieldValues to the server.
func (cmd *ImportCommand) importValues(ctx context.Context, useColumnKeys bool, vals []pilosa.FieldValue) error {
logger := log.New(cmd.Stderr, "", log.LstdFlags)
// If keys are used, all values are sent to the primary translate store (i.e. coordinator).
if useColumnKeys {
logger.Printf("importing keyed values: n=%d", len(vals))
if err := cmd.client.ImportValueK(ctx, cmd.Index, cmd.Field, vals); err != nil {
return errors.Wrap(err, "importing keys")
}
return nil
}
// Group vals by shard.
logger.Printf("grouping %d vals", len(vals))
valsByShard := http.FieldValues(vals).GroupByShard()
// Parse path into FieldValues.
for shard, vals := range valsByShard {
if cmd.Sort {
sort.Sort(http.FieldValues(vals))
}
logger.Printf("importing shard: %d, n=%d", shard, len(vals))
if err := cmd.client.ImportValue(ctx, cmd.Index, cmd.Field, shard, vals, pilosa.OptImportOptionsClear(cmd.Clear)); err != nil {
return errors.Wrap(err, "importing values")
}
}
return nil
return errors.Wrap(cmd.client.ImportValue2(ctx, req, &pilosa.ImportOptions{}), "importing values")
}
func (cmd *ImportCommand) TLSHost() string {

View file

@ -25,6 +25,7 @@ import (
"reflect"
"strings"
"testing"
"time"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/test"
@ -72,7 +73,7 @@ func TestImportCommand_Basic(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
cm.Index = "i"
@ -101,7 +102,7 @@ func TestImportCommand_Basic(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
cm.Index = "i"
@ -134,7 +135,7 @@ func TestImportCommand_RunValue(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader("")))
@ -176,7 +177,7 @@ func TestImportCommand_RunValue(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader("")))
@ -218,7 +219,7 @@ func TestImportCommand_RunKeys(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader(`{"options":{"keys": true}}`)))
@ -270,8 +271,8 @@ func TestImportCommand_KeyReplication(t *testing.T) {
c := test.MustRunCluster(t, 2)
defer c.Close()
cmd0 := c[0]
cmd1 := c[1]
cmd0 := c.GetNode(0)
cmd1 := c.GetNode(1)
host0 := cmd0.API.Node().URI.HostPort()
host1 := cmd1.API.Node().URI.HostPort()
@ -299,20 +300,26 @@ func TestImportCommand_KeyReplication(t *testing.T) {
// Verify that the data is available on both nodes.
for _, host := range []string{host0, host1} {
qry := "Count(Row(f=foo0))"
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+host+"/index/i/query", strings.NewReader(qry)))
if err != nil {
t.Fatalf("Querying data for validation: %s", err)
}
if err := test.RetryUntil(2*time.Second, func() error {
qry := "Count(Row(f=foo0))"
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+host+"/index/i/query", strings.NewReader(qry)))
if err != nil {
return fmt.Errorf("Querying data for validation: %s", err)
}
// Read body and unmarshal response.
exp := `{"results":[100]}` + "\n"
if body, err := ioutil.ReadAll(resp.Body); err != nil {
t.Fatalf("reading: %s", err)
} else if !reflect.DeepEqual(body, []byte(exp)) {
t.Fatalf("expected: %s, but got: %s", exp, body)
// Read body and unmarshal response.
exp := `{"results":[100]}` + "\n"
if body, err := ioutil.ReadAll(resp.Body); err != nil {
return fmt.Errorf("reading: %s", err)
} else if !reflect.DeepEqual(body, []byte(exp)) {
return fmt.Errorf("expected: %s, but got: %s", exp, body)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
}
// Ensure that integer import with keys runs.
@ -332,7 +339,7 @@ func TestImportCommand_RunValueKeys(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader(`{"options":{"keys": true}}`)))
@ -358,7 +365,7 @@ func TestImportCommand_RunValueKeys(t *testing.T) {
func TestImportCommand_InvalidFile(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
cmd := cluster.GetNode(0)
buf := bytes.Buffer{}
stdin, stdout, stderr := GetIO(buf)
@ -446,7 +453,7 @@ func GetIO(buf bytes.Buffer) (io.Reader, io.Writer, io.Writer) {
func TestImportCommand_BugOverwriteValue(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
cmd := cluster.GetNode(0)
buf := bytes.Buffer{}
stdin, stdout, stderr := GetIO(buf)
@ -522,7 +529,7 @@ func TestImportCommand_RunBool(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
cmd := cluster.GetNode(0)
cm.Host = cmd.API.Node().URI.HostPort()
resp, err := http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader("")))

View file

@ -16,15 +16,23 @@ package ctl
import (
"context"
"encoding/binary"
"fmt"
"hash/fnv"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"syscall"
"text/tabwriter"
"time"
"unsafe"
"github.com/gogo/protobuf/proto"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/internal"
"github.com/pilosa/pilosa/v2/roaring"
"github.com/pkg/errors"
)
@ -33,6 +41,12 @@ import (
type InspectCommand struct {
// Path to data file
Path string
// don't list details of objects
Quiet bool
// list only this many objects
Max int
// Filters:
InspectOpts pilosa.InspectRequest
// Standard input/output
*pilosa.CmdIO
@ -45,8 +59,119 @@ func NewInspectCommand(stdin io.Reader, stdout, stderr io.Writer) *InspectComman
}
}
type pointerContext struct {
from, to uintptr
}
func (p *pointerContext) pretty(c roaring.ContainerInfo) string {
var pointer string
if c.Mapped {
if c.Pointer >= p.from && c.Pointer < p.to {
pointer = fmt.Sprintf("@+0x%x", c.Pointer-p.from)
} else {
pointer = fmt.Sprintf("!0x%x!", c.Pointer)
}
} else {
pointer = fmt.Sprintf("0x%x", c.Pointer)
}
return fmt.Sprintf("%s \t%d \t%d \t%s ", c.Type, c.N, c.Alloc, pointer)
}
func (cmd *InspectCommand) PrintOps(info roaring.BitmapInfo) {
fmt.Fprintln(cmd.Stdout, " Ops:")
tw := tabwriter.NewWriter(cmd.Stdout, 0, 8, 0, '\t', 0)
fmt.Fprintf(tw, " \t%s\t%s\t%s\t\n", "TYPE", "OpN", "SIZE")
printed := 0
for _, op := range info.OpDetails {
fmt.Fprintf(tw, "\t%s\t%d\t%d\t\n", op.Type, op.OpN, op.Size)
printed++
if cmd.Max != 0 && printed >= cmd.Max {
break
}
}
tw.Flush()
}
func (cmd *InspectCommand) PrintContainers(info roaring.BitmapInfo, pC pointerContext) {
fmt.Fprintln(cmd.Stdout, " Containers:")
tw := tabwriter.NewWriter(cmd.Stdout, 0, 8, 0, '\t', 0)
fmt.Fprintf(tw, " \t\tRoaring\t\t\t\tOps\t\t\t\tFlags\t\n")
fmt.Fprintf(tw, "\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n", "KEY", "TYPE", "N", "ALLOC", "OFFSET", "TYPE", "N", "ALLOC", "OFFSET", "FLAGS")
c1s := info.Containers
c2s := info.OpContainers
l1 := len(c1s)
l2 := len(c2s)
i1 := 0
i2 := 0
var c1, c2 roaring.ContainerInfo
c1.Key = ^uint64(0)
c2.Key = ^uint64(0)
c1e := false
c2e := false
if i1 < l1 {
c1 = c1s[i1]
i1++
c1e = true
}
if i2 < l2 {
c2 = c2s[i2]
i2++
c2e = true
}
printed := 0
for c1e || c2e {
c1used := false
c2used := false
var key uint64
c1fmt := "-\t\t\t"
c2fmt := "-\t\t\t"
// If c2 exists, we'll always prefer its flags,
// if it doesn't, this gets overwritten.
flags := c2.Flags
if !c2e || (c1e && c1.Key < c2.Key) {
c1fmt = pC.pretty(c1)
key = c1.Key
c1used = true
flags = c1.Flags
} else if !c1e || (c2e && c2.Key < c1.Key) {
c2fmt = pC.pretty(c2)
key = c2.Key
c2used = true
} else {
// c1e and c2e both set, and neither key is < the other.
c1fmt = pC.pretty(c1)
c2fmt = pC.pretty(c2)
key = c1.Key
c1used = true
c2used = true
}
if c1used {
if i1 < l1 {
c1 = c1s[i1]
i1++
} else {
c1e = false
}
}
if c2used {
if i2 < l2 {
c2 = c2s[i2]
i2++
} else {
c2e = false
}
}
fmt.Fprintf(tw, "\t%d\t%s\t%s\t%s\t\n", key, c1fmt, c2fmt, flags)
printed++
if cmd.Max > 0 && printed >= cmd.Max {
break
}
}
tw.Flush()
}
// Run executes the inspect command.
func (cmd *InspectCommand) Run(_ context.Context) error {
func (cmd *InspectCommand) Run(ctx context.Context) error {
// Open file handle.
f, err := os.Open(cmd.Path)
if err != nil {
@ -58,7 +183,172 @@ func (cmd *InspectCommand) Run(_ context.Context) error {
if err != nil {
return errors.Wrap(err, "statting file")
}
if fi.IsDir() {
total := 0
infos, err := f.Readdir(0)
if err != nil {
return err
}
if len(infos) == 0 {
return errors.New("directory contains no files")
}
names := make([]string, len(infos))
nameToInfo := make(map[string]os.FileInfo, len(infos))
// find numeric-only names; we'll operate on
// either those, or the whole holder if we find
// a .topology file.
n := 0
for _, fi := range infos {
name := fi.Name()
if name == ".topology" {
return cmd.InspectHolder(ctx, cmd.Path)
}
if _, err := strconv.Atoi(name); err == nil {
names[n] = name
nameToInfo[name] = fi
n++
}
}
if n == 0 {
return fmt.Errorf("directory contains no fragments (looking for numeric names)")
}
names = names[:n]
fmt.Fprintf(cmd.Stdout, "%s contains %d fragments:\n", cmd.Path, n)
for _, name := range names {
f2, err := os.Open(filepath.Join(cmd.Path, name))
if err != nil {
return fmt.Errorf("opening %q: %v", name, err)
}
fmt.Fprintf(cmd.Stdout, "%s/%s:\n", cmd.Path, name)
err = cmd.InspectFile(f2, nameToInfo[name])
total++
f2.Close()
if err != nil {
return fmt.Errorf("inspecting %q: %v", name, err)
}
}
return nil
}
return cmd.InspectFile(f, fi)
}
// loadTopology is copied almost exactly from pilosa/cluster.go.
func loadTopology(path string) (topology internal.Topology, myID string, err error) {
buf, err := ioutil.ReadFile(filepath.Join(path, ".topology"))
if os.IsNotExist(err) {
return topology, myID, err
} else if err != nil {
return topology, myID, errors.Wrap(err, "reading file")
}
if err := proto.Unmarshal(buf, &topology); err != nil {
return topology, myID, errors.Wrap(err, "unmarshalling")
}
sort.Slice(topology.NodeIDs,
func(i, j int) bool {
return topology.NodeIDs[i] < topology.NodeIDs[j]
})
buf, err = ioutil.ReadFile(filepath.Join(path, ".id"))
if os.IsNotExist(err) {
return topology, myID, err
} else if err != nil {
return topology, myID, nil
}
myID = strings.TrimSpace(string(buf))
return topology, myID, nil
}
var partitions = make(map[string]map[uint64]int)
func findPartition(index string, shard uint64, partitionN int) (partition int) {
var shardMap map[uint64]int
var ok bool
if shardMap, ok = partitions[index]; !ok {
shardMap = make(map[uint64]int)
partitions[index] = shardMap
}
if partition, ok = shardMap[shard]; !ok {
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], shard)
// Hash the bytes and mod by partition count.
h := fnv.New64a()
_, _ = h.Write([]byte(index))
_, _ = h.Write(buf[:])
partition = int(h.Sum64() % uint64(partitionN))
shardMap[shard] = partition
}
return partition
}
func findPartitionPath(path string, partitionN int) (int, error) {
parts := strings.Split(path, "/")
shard, err := strconv.ParseUint(parts[len(parts)-1], 10, 64)
if err != nil {
return 0, err
}
return findPartition(parts[0], shard, partitionN), nil
}
func (cmd *InspectCommand) InspectHolder(ctx context.Context, path string) error {
holder := pilosa.NewHolder(path, nil)
holder.Opts.Inspect = true
holder.Opts.ReadOnly = true
err := holder.Open()
if err != nil {
return fmt.Errorf("%s: holder open: %v", path, err)
}
holderInfo, err := holder.Inspect(ctx, &cmd.InspectOpts)
if err != nil {
return fmt.Errorf("%s: inspect: %v", path, err)
}
myPartition := 0
topology, myID, err := loadTopology(path)
if err == nil {
fmt.Fprintf(cmd.Stdout, "Cluster ID: %q\n", topology.ClusterID)
if len(topology.NodeIDs) > 1 {
fmt.Fprintf(cmd.Stdout, "Cluster of %d nodes, this node %q\n", len(topology.NodeIDs), myID)
} else {
fmt.Fprintf(cmd.Stdout, "Cluster has only one node: %q\n", myID)
}
found := false
for i := range topology.NodeIDs {
if topology.NodeIDs[i] == myID {
found = true
myPartition = i
break
}
}
if !found {
fmt.Fprintf(cmd.Stdout, "Warning: node ID %q not found in topology (%q)\n", myID, topology.NodeIDs)
}
} else {
fmt.Fprintf(cmd.Stdout, "warning: reading topology failed: %v\n", err)
}
for _, name := range holderInfo.FragmentNames {
partition, err := findPartitionPath(name, len(topology.NodeIDs))
if err != nil {
fmt.Fprintf(cmd.Stdout, "%s: [can't find partition: %v]\n", name, err)
} else {
if partition == myPartition {
fmt.Fprintf(cmd.Stdout, "%s:\n", name)
} else {
fmt.Fprintf(cmd.Stdout, "%s: [primary node %q]\n", name, topology.NodeIDs[partition])
}
}
details := holderInfo.FragmentInfo[name]
cmd.DisplayInfo(details.BitmapInfo)
if details.BlockChecksums != nil {
fmt.Fprintf(cmd.Stdout, " Checksums [%d total]:\n", len(details.BlockChecksums))
for _, block := range details.BlockChecksums {
fmt.Fprintf(cmd.Stdout, " %8d: %x\n", block.ID, block.Checksum)
}
}
}
return nil
}
func (cmd *InspectCommand) InspectFile(f *os.File, fi os.FileInfo) error {
// Memory map the file.
data, err := syscall.Mmap(int(f.Fd()), 0, int(fi.Size()), syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
@ -72,39 +362,37 @@ func (cmd *InspectCommand) Run(_ context.Context) error {
}()
// Attach the mmap file to the bitmap.
t := time.Now()
fmt.Fprintf(cmd.Stderr, "unmarshalling bitmap...")
bm := roaring.NewBitmap()
if err := bm.UnmarshalBinary(data); err != nil {
return errors.Wrap(err, "unmarshalling")
fmt.Fprintf(cmd.Stderr, "inspecting bitmap...")
var info roaring.BitmapInfo
_, _, err = roaring.InspectBinary(data, true, &info)
fmt.Fprintf(cmd.Stderr, " (%s)\n", time.Since(t))
cmd.DisplayInfo(info)
if err != nil {
return errors.Wrap(err, "inspecting")
}
fmt.Fprintf(cmd.Stderr, " (%s)\n", time.Since(t))
return nil
}
// Retrieve stats.
t = time.Now()
fmt.Fprintf(cmd.Stderr, "calculating stats...")
info := bm.Info()
fmt.Fprintf(cmd.Stderr, " (%s)\n", time.Since(t))
func (cmd *InspectCommand) DisplayInfo(info roaring.BitmapInfo) {
pC := pointerContext{
from: info.From,
to: info.To,
}
// Print top-level info.
fmt.Fprintf(cmd.Stdout, "== Bitmap Info ==\n")
fmt.Fprintf(cmd.Stdout, "Containers: %d\n", len(info.Containers))
fmt.Fprintf(cmd.Stdout, "Operations: %d\n", info.OpN)
fmt.Fprintf(cmd.Stdout, " Bitmap Info:\n")
fmt.Fprintf(cmd.Stdout, " Bits: %d\n", info.BitCount)
fmt.Fprintf(cmd.Stdout, " Containers: %d (%d roaring)\n", info.ContainerCount, len(info.Containers))
fmt.Fprintf(cmd.Stdout, " Operations: %d (%d bits)\n", info.Ops, info.OpN)
fmt.Fprintln(cmd.Stdout, "")
// Print info for each container.
fmt.Fprintln(cmd.Stdout, "== Containers ==")
tw := tabwriter.NewWriter(cmd.Stdout, 0, 8, 0, '\t', 0)
fmt.Fprintf(tw, "%s\t%s\t% 8s \t% 8s\t%s\n", "KEY", "TYPE", "N", "ALLOC", "OFFSET")
for _, ci := range info.Containers {
fmt.Fprintf(tw, "%d\t%s\t% 8d \t% 8d \t0x%08x\n",
ci.Key,
ci.Type,
ci.N,
ci.Alloc,
uintptr(ci.Pointer)-uintptr(unsafe.Pointer(&data[0])),
)
if !cmd.Quiet {
if info.ContainerCount > 0 {
cmd.PrintContainers(info, pC)
}
if info.Ops > 0 {
cmd.PrintOps(info)
}
}
tw.Flush()
return nil
}

View file

@ -41,7 +41,7 @@ func TestInspectCommand_Run(t *testing.T) {
file.Close()
cm.Path = file.Name()
err = cm.Run(context.Background())
expectedError := "unmarshalling: "
expectedError := "inspecting: "
if !strings.Contains(err.Error(), expectedError) {
t.Fatalf("expected error '%s', got '%v'", expectedError, err)
}
@ -52,7 +52,7 @@ func TestInspectCommand_Run(t *testing.T) {
if err != nil {
t.Fatalf("copying data: %v", err)
}
if !strings.Contains(buf.String(), "unmarshalling bitmap...") {
if !strings.Contains(buf.String(), "inspecting bitmap...") {
t.Fatalf("Inspect doesn't work: %s", err)
}

60
ctl/rbf_check.go Normal file
View file

@ -0,0 +1,60 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ctl
import (
"context"
"fmt"
"io"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/rbf"
)
// RBFCheckCommand represents a command for running a consistency check on RBF.
type RBFCheckCommand struct {
// Filepath to the RBF database.
Path string
// Standard input/output
*pilosa.CmdIO
}
// NewRBFCheckCommand returns a new instance of RBFCheckCommand.
func NewRBFCheckCommand(stdin io.Reader, stdout, stderr io.Writer) *RBFCheckCommand {
return &RBFCheckCommand{
CmdIO: pilosa.NewCmdIO(stdin, stdout, stderr),
}
}
// Run executes the export.
func (cmd *RBFCheckCommand) Run(ctx context.Context) error {
// Open database.
db := rbf.NewDB(cmd.Path, nil)
if err := db.Open(); err != nil {
return err
}
defer db.Close()
// Run check on the database.
if err := db.Check(); err != nil {
return err
}
// If successful, print a success message.
fmt.Fprintln(cmd.Stdout, "ok")
return nil
}

111
ctl/rbf_dump.go Normal file
View file

@ -0,0 +1,111 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ctl
import (
"context"
"encoding/hex"
"fmt"
"io"
"strings"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/rbf"
)
// RBFDumpCommand represents a command for dumping raw data for an RBF page.
type RBFDumpCommand struct {
// Filepath to the RBF database.
Path string
// Page numbers to print.
Pgnos []uint32
// Standard input/output
*pilosa.CmdIO
}
// NewRBFDumpCommand returns a new instance of RBFDumpCommand.
func NewRBFDumpCommand(stdin io.Reader, stdout, stderr io.Writer) *RBFDumpCommand {
return &RBFDumpCommand{
CmdIO: pilosa.NewCmdIO(stdin, stdout, stderr),
}
}
// Run executes the export.
func (cmd *RBFDumpCommand) Run(ctx context.Context) error {
// Open database.
db := rbf.NewDB(cmd.Path, nil)
if err := db.Open(); err != nil {
return err
}
defer db.Close()
// Execute with a transaction.
tx, err := db.Begin(false)
if err != nil {
return err
}
defer tx.Rollback()
// Fetch each page & dump.
for _, pgno := range cmd.Pgnos {
buf, err := tx.PageData(pgno)
if err != nil {
return err
}
fmt.Fprintf(cmd.Stdout, "## PAGE %d\n", pgno)
fmt.Fprintln(cmd.Stdout, compressedHexDump(buf))
fmt.Fprintln(cmd.Stdout, "")
}
return nil
}
func compressedHexDump(b []byte) string {
const prefixN = len("00000000")
var output []string
var prev string
var ellipsis bool
lines := strings.Split(strings.TrimSpace(hex.Dump(b)), "\n")
for i, line := range lines {
// Add line to output if it is not repeating or the last line.
if i == 0 || i == len(lines)-1 || trimPrefixN(line, prefixN) != trimPrefixN(prev, prefixN) {
output = append(output, line)
prev, ellipsis = line, false
continue
}
// Add an ellipsis for the first duplicate line.
if !ellipsis {
output = append(output, "...")
ellipsis = true
continue
}
}
return strings.Join(output, "\n")
}
// trimPrefixN trims n bytes from the beginning of a string.
func trimPrefixN(s string, n int) string {
if len(s) < n {
return ""
}
return s[n:]
}

140
ctl/rbf_page.go Normal file
View file

@ -0,0 +1,140 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ctl
import (
"context"
"fmt"
"io"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/rbf"
)
// RBFPageCommand represents a command for printing data for a single RBF page.
type RBFPageCommand struct {
// Filepath to the RBF database.
Path string
// Page numbers to print.
Pgnos []uint32
// Standard input/output
*pilosa.CmdIO
}
// NewRBFPageCommand returns a new instance of RBFPageCommand.
func NewRBFPageCommand(stdin io.Reader, stdout, stderr io.Writer) *RBFPageCommand {
return &RBFPageCommand{
CmdIO: pilosa.NewCmdIO(stdin, stdout, stderr),
}
}
// Run executes the export.
func (cmd *RBFPageCommand) Run(ctx context.Context) error {
// Open database.
db := rbf.NewDB(cmd.Path, nil)
if err := db.Open(); err != nil {
return err
}
defer db.Close()
// Execute with a transaction.
tx, err := db.Begin(false)
if err != nil {
return err
}
defer tx.Rollback()
// Fetch the page.
pages, err := tx.Pages(cmd.Pgnos)
if err != nil {
return err
}
for _, page := range pages {
switch page := page.(type) {
case *rbf.MetaPage:
cmd.printMetaPage(page)
case *rbf.RootRecordPage:
cmd.printRootRecordPage(page)
case *rbf.LeafPage:
cmd.printLeafPage(page)
case *rbf.BranchPage:
cmd.printBranchPage(page)
case *rbf.BitmapPage:
cmd.printBitmapPage(page)
case *rbf.FreePage:
cmd.printFreePage(page)
default:
return fmt.Errorf("unexpected page type %T", page)
}
fmt.Fprintln(cmd.Stdout, "")
}
return nil
}
func (cmd *RBFPageCommand) printMetaPage(page *rbf.MetaPage) {
fmt.Fprintf(cmd.Stdout, "Pgno: %d\n", page.Pgno)
fmt.Fprintf(cmd.Stdout, "Type: meta\n")
fmt.Fprintf(cmd.Stdout, "PageN: %d\n", page.PageN)
fmt.Fprintf(cmd.Stdout, "WALID: %d\n", page.WALID)
fmt.Fprintf(cmd.Stdout, "Root Record Pgno: %d\n", page.RootRecordPageNo)
fmt.Fprintf(cmd.Stdout, "Freelist Pgno: %d\n", page.FreelistPageNo)
}
func (cmd *RBFPageCommand) printRootRecordPage(page *rbf.RootRecordPage) {
fmt.Fprintf(cmd.Stdout, "Pgno: %d\n", page.Pgno)
fmt.Fprintf(cmd.Stdout, "Type: root record\n")
fmt.Fprintf(cmd.Stdout, "Next: %d\n", page.Next)
fmt.Fprintf(cmd.Stdout, "Records: n=%d\n", len(page.Records))
for i, rec := range page.Records {
fmt.Fprintf(cmd.Stdout, "[%d]: name=%q pgno=%d\n", i, rec.Name, rec.Pgno)
}
}
func (cmd *RBFPageCommand) printLeafPage(page *rbf.LeafPage) {
fmt.Fprintf(cmd.Stdout, "Pgno: %d\n", page.Pgno)
fmt.Fprintf(cmd.Stdout, "Type: leaf\n")
fmt.Fprintf(cmd.Stdout, "Cells: n=%d\n", len(page.Cells))
for i, cell := range page.Cells {
if cell.Type == rbf.ContainerTypeBitmapPtr {
fmt.Fprintf(cmd.Stdout, "[%d]: key=%d type=%s pgno=%d\n", i, cell.Key, cell.Type, cell.Pgno)
} else {
fmt.Fprintf(cmd.Stdout, "[%d]: key=%d type=%s values=%v\n", i, cell.Key, cell.Type, cell.Values)
}
}
}
func (cmd *RBFPageCommand) printBranchPage(page *rbf.BranchPage) {
fmt.Fprintf(cmd.Stdout, "Pgno: %d\n", page.Pgno)
fmt.Fprintf(cmd.Stdout, "Type: branch\n")
fmt.Fprintf(cmd.Stdout, "Cells: n=%d\n", len(page.Cells))
for i, cell := range page.Cells {
fmt.Fprintf(cmd.Stdout, "[%d]: key=%d flags=%d pgno=%d\n", i, cell.Key, cell.Flags, cell.Pgno)
}
}
func (cmd *RBFPageCommand) printBitmapPage(page *rbf.BitmapPage) {
fmt.Fprintf(cmd.Stdout, "Pgno: %d\n", page.Pgno)
fmt.Fprintf(cmd.Stdout, "Type: bitmap\n")
fmt.Fprintf(cmd.Stdout, "Values: %v\n", page.Values)
}
func (cmd *RBFPageCommand) printFreePage(page *rbf.FreePage) {
fmt.Fprintf(cmd.Stdout, "Pgno: %d\n", page.Pgno)
fmt.Fprintf(cmd.Stdout, "Type: free\n")
}

149
ctl/rbf_pages.go Normal file
View file

@ -0,0 +1,149 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ctl
import (
"context"
"fmt"
"io"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/rbf"
"github.com/pilosa/pilosa/v2/txkey"
)
// RBFPagesCommand represents a command for printing a list of RBF page metadata.
type RBFPagesCommand struct {
// Filepath to the RBF database.
Path string
// Print the b-tree key with each row.
WithTree bool
// Standard input/output
*pilosa.CmdIO
}
// NewRBFPagesCommand returns a new instance of RBFPagesCommand.
func NewRBFPagesCommand(stdin io.Reader, stdout, stderr io.Writer) *RBFPagesCommand {
return &RBFPagesCommand{
CmdIO: pilosa.NewCmdIO(stdin, stdout, stderr),
}
}
// Run executes the export.
func (cmd *RBFPagesCommand) Run(ctx context.Context) error {
// Open database.
db := rbf.NewDB(cmd.Path, nil)
if err := db.Open(); err != nil {
return err
}
defer db.Close()
// Execute with a transaction.
tx, err := db.Begin(false)
if err != nil {
return err
}
defer tx.Rollback()
// Iterate over each page and grab info.
infos, err := tx.PageInfos()
if err != nil {
return err
}
// Write header.
fmt.Fprint(cmd.Stdout, "ID ")
fmt.Fprint(cmd.Stdout, "TYPE ")
if cmd.WithTree {
fmt.Fprint(cmd.Stdout, "TREE ")
}
fmt.Fprintln(cmd.Stdout, "EXTRA")
fmt.Fprint(cmd.Stdout, "======== ")
fmt.Fprint(cmd.Stdout, "========== ")
if cmd.WithTree {
fmt.Fprint(cmd.Stdout, "============================== ")
}
fmt.Fprintln(cmd.Stdout, "====================")
// Print one line for each page.
for pgno, info := range infos {
switch info := info.(type) {
case *rbf.MetaPageInfo:
fmt.Fprintf(cmd.Stdout, "%-8d ", pgno)
fmt.Fprintf(cmd.Stdout, "%-10s ", "meta")
if cmd.WithTree {
fmt.Fprintf(cmd.Stdout, "%-30q ", "")
}
fmt.Fprintf(cmd.Stdout, "pageN=%d,walid=%d,rootrec=%d,freelist=%d\n", info.PageN, info.WALID, info.RootRecordPageNo, info.FreelistPageNo)
case *rbf.RootRecordPageInfo:
fmt.Fprintf(cmd.Stdout, "%-8d ", pgno)
fmt.Fprintf(cmd.Stdout, "%-10s ", "rootrec")
if cmd.WithTree {
fmt.Fprintf(cmd.Stdout, "%-30q ", "")
}
fmt.Fprintf(cmd.Stdout, "next=%d\n", info.Next)
case *rbf.LeafPageInfo:
fmt.Fprintf(cmd.Stdout, "%-8d ", pgno)
fmt.Fprintf(cmd.Stdout, "%-10s ", "leaf")
if cmd.WithTree {
fmt.Fprintf(cmd.Stdout, "%-30q ", prefixToString(info.Tree))
}
fmt.Fprintf(cmd.Stdout, "flags=x%x,celln=%d\n", info.Flags, info.CellN)
case *rbf.BranchPageInfo:
fmt.Fprintf(cmd.Stdout, "%-8d ", pgno)
fmt.Fprintf(cmd.Stdout, "%-10s ", "branch")
if cmd.WithTree {
fmt.Fprintf(cmd.Stdout, "%-30q ", prefixToString(info.Tree))
}
fmt.Fprintf(cmd.Stdout, "flags=x%x,celln=%d\n", info.Flags, info.CellN)
case *rbf.BitmapPageInfo:
fmt.Fprintf(cmd.Stdout, "%-8d ", pgno)
fmt.Fprintf(cmd.Stdout, "%-10s ", "bitmap")
if cmd.WithTree {
fmt.Fprintf(cmd.Stdout, "%-30q ", prefixToString(info.Tree))
}
fmt.Fprintf(cmd.Stdout, "-\n")
case *rbf.FreePageInfo:
fmt.Fprintf(cmd.Stdout, "%-8d ", pgno)
fmt.Fprintf(cmd.Stdout, "%-10s ", "free")
if cmd.WithTree {
fmt.Fprintf(cmd.Stdout, "%-30q ", "")
}
fmt.Fprintf(cmd.Stdout, "-\n")
default:
panic(fmt.Sprintf("unexpected page info type %T", info))
}
}
return nil
}
func prefixToString(s string) (ret string) {
defer func() {
if err := recover(); err != nil {
ret = s
}
}()
return txkey.PrefixToString([]byte(s))
}

View file

@ -15,8 +15,10 @@
package ctl
import (
"fmt"
"time"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/server"
"github.com/spf13/cobra"
)
@ -26,25 +28,30 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) {
flags := cmd.Flags()
flags.StringVarP(&srv.Config.DataDir, "data-dir", "d", srv.Config.DataDir, "Directory to store pilosa data files.")
flags.StringVarP(&srv.Config.Bind, "bind", "b", srv.Config.Bind, "Default URI on which pilosa should listen.")
flags.StringVar(&srv.Config.BindGRPC, "bind-grpc", srv.Config.BindGRPC, "URI on which pilosa should listen for gRPC requests.")
flags.StringVar(&srv.Config.Advertise, "advertise", srv.Config.Advertise, "Address to advertise externally.")
flags.StringVar(&srv.Config.AdvertiseGRPC, "advertise-grpc", srv.Config.AdvertiseGRPC, "Address to advertise externally for gRPC.")
flags.IntVarP(&srv.Config.MaxWritesPerRequest, "max-writes-per-request", "", srv.Config.MaxWritesPerRequest, "Number of write commands per request.")
flags.StringVar(&srv.Config.LogPath, "log-path", srv.Config.LogPath, "Log path")
flags.BoolVar(&srv.Config.Verbose, "verbose", srv.Config.Verbose, "Enable verbose logging")
flags.Uint64Var(&srv.Config.MaxMapCount, "max-map-count", srv.Config.MaxMapCount, "Limits the maximum number of active mmaps. Pilosa will fall back to reading files once this is exhausted. Set below your system's vm.max_map_count.")
flags.Uint64Var(&srv.Config.MaxFileCount, "max-file-count", srv.Config.MaxFileCount, "Soft limit on the maximum number of fragment files Pilosa keeps open simultaneously.")
flags.DurationVarP((*time.Duration)(&srv.Config.LongQueryTime), "long-query-time", "", time.Duration(srv.Config.LongQueryTime), "Duration that will trigger log and stat messages for slow queries. Zero to disable.")
flags.IntVar(&srv.Config.QueryHistoryLength, "query-history-length", srv.Config.QueryHistoryLength, "Number of queries to remember in history.")
// TLS
SetTLSConfig(flags, &srv.Config.TLS.CertificatePath, &srv.Config.TLS.CertificateKeyPath, &srv.Config.TLS.CACertPath, &srv.Config.TLS.SkipVerify, &srv.Config.TLS.EnableClientVerification)
SetTLSConfig(flags, "", &srv.Config.TLS.CertificatePath, &srv.Config.TLS.CertificateKeyPath, &srv.Config.TLS.CACertPath, &srv.Config.TLS.SkipVerify, &srv.Config.TLS.EnableClientVerification)
// Handler
flags.StringSliceVarP(&srv.Config.Handler.AllowedOrigins, "handler.allowed-origins", "", []string{}, "Comma separated list of allowed origin URIs (for CORS/WebUI).")
flags.StringSliceVarP(&srv.Config.Handler.AllowedOrigins, "handler.allowed-origins", "", []string{}, "Comma separated list of allowed origin URIs (for CORS/Web UI).")
// Cluster
flags.BoolVarP(&srv.Config.Cluster.Disabled, "cluster.disabled", "", srv.Config.Cluster.Disabled, "Disabled multi-node cluster communication (used for testing)")
flags.BoolVarP(&srv.Config.Cluster.Coordinator, "cluster.coordinator", "", srv.Config.Cluster.Coordinator, "Host that will act as cluster coordinator during startup and resizing.")
flags.IntVarP(&srv.Config.Cluster.ReplicaN, "cluster.replicas", "", 1, "Number of hosts each piece of data should be stored on.")
flags.StringSliceVarP(&srv.Config.Cluster.Hosts, "cluster.hosts", "", []string{}, "Comma separated list of hosts in cluster. Only used for testing.")
flags.DurationVarP((*time.Duration)(&srv.Config.Cluster.LongQueryTime), "cluster.long-query-time", "", time.Minute, "Duration that will trigger log and stat messages for slow queries.")
flags.DurationVarP((*time.Duration)(&srv.Config.Cluster.LongQueryTime), "cluster.long-query-time", "", time.Duration(srv.Config.Cluster.LongQueryTime), "RENAMED TO 'long-query-time': Duration that will trigger log and stat messages for slow queries.") // negative duration indicates invalid value because 0 is meaningful
flags.StringVar(&srv.Config.Cluster.Name, "cluster.name", srv.Config.Cluster.Name, "Human-readable name for the cluster.")
// Translation
flags.StringVarP(&srv.Config.Translation.PrimaryURL, "translation.primary-url", "", srv.Config.Translation.PrimaryURL, "DEPRECATED: URL for primary translation node for replication.")
@ -70,17 +77,38 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) {
flags.DurationVarP((*time.Duration)(&srv.Config.AntiEntropy.Interval), "anti-entropy.interval", "", (time.Duration)(srv.Config.AntiEntropy.Interval), "Interval at which to run anti-entropy routine.")
// Metric
flags.StringVarP(&srv.Config.Metric.Service, "metric.service", "", srv.Config.Metric.Service, "Where to send stats: can be expvar (in-memory served at /debug/vars), statsd or none.")
flags.StringVarP(&srv.Config.Metric.Service, "metric.service", "", srv.Config.Metric.Service, "Where to send stats: can be expvar (in-memory served at /debug/vars), prometheus, statsd or none.")
flags.StringVarP(&srv.Config.Metric.Host, "metric.host", "", srv.Config.Metric.Host, "URI to send metrics when metric.service is statsd.")
flags.DurationVarP((*time.Duration)(&srv.Config.Metric.PollInterval), "metric.poll-interval", "", (time.Duration)(srv.Config.Metric.PollInterval), "Polling interval metrics.")
flags.BoolVarP((&srv.Config.Metric.Diagnostics), "metric.diagnostics", "", srv.Config.Metric.Diagnostics, "Enabled diagnostics reporting.")
// Tracing
flags.StringVarP(&srv.Config.Tracing.AgentHostPort, "tracing.agent-host-port", "", srv.Config.Tracing.AgentHostPort, "Jaeger agent host:port.")
flags.StringVarP(&srv.Config.Tracing.SamplerType, "tracing.sampler-type", "", srv.Config.Tracing.SamplerType, "Jaeger sampler type or 'off' to disable tracing completely.")
flags.StringVarP(&srv.Config.Tracing.SamplerType, "tracing.sampler-type", "", srv.Config.Tracing.SamplerType, "Jaeger sampler type (remote, const, probabilistic, ratelimiting) or 'off' to disable tracing completely.")
flags.Float64VarP(&srv.Config.Tracing.SamplerParam, "tracing.sampler-param", "", srv.Config.Tracing.SamplerParam, "Jaeger sampler parameter.")
// Profiling
flags.IntVar(&srv.Config.Profile.BlockRate, "profile.block-rate", srv.Config.Profile.BlockRate, "Sampling rate for goroutine blocking profiler. One sample per <rate> ns.")
flags.IntVar(&srv.Config.Profile.MutexFraction, "profile.mutex-fraction", srv.Config.Profile.MutexFraction, "Sampling fraction for mutex contention profiling. Sample 1/<rate> of events.")
// Transactional storage engine
// Note: the default for --tx must be kept "" empty string. Otherwise we
// cannot detect and honor the PILOSA_TXSRC env var over-ride.
flags.StringVarP(&srv.Config.Txsrc, "txsrc", "", "", fmt.Sprintf("transaction/storage to use: one of roaring, rbf, bolt, or a blue-green setup: rbf_roaring, roaring_rbf, bolt_roaring, roaring_bolt, bolt_rbf, etc. The default is: %v. The env var PILOSA_TXSRC is over-ridden by --txsrc option on the command line.", pilosa.DefaultTxsrc))
// RowcacheOn
flags.BoolVarP((&srv.Config.RowcacheOn), "rowcache-on", "", srv.Config.RowcacheOn, "turn on the rowcache for all backends (may speed some queries)")
// RBF specific flags. See pilosa/rbf/cfg/cfg.go for definitions.
srv.Config.RBFConfig.DefineFlags(flags)
// Postgres endpoint
flags.StringVar(&srv.Config.Postgres.Bind, "postgres.bind", srv.Config.Postgres.Bind, "Address to which to bind a postgres endpoint (leave blank to disable)")
SetTLSConfig(flags, "postgres.", &srv.Config.Postgres.TLS.CertificatePath, &srv.Config.Postgres.TLS.CertificateKeyPath, &srv.Config.Postgres.TLS.CACertPath, &srv.Config.Postgres.TLS.SkipVerify, &srv.Config.Postgres.TLS.EnableClientVerification)
flags.DurationVar((*time.Duration)(&srv.Config.Postgres.StartupTimeout), "postgres.startup-timeout", time.Duration(srv.Config.Postgres.StartupTimeout), "Timeout for postgres connection startup. (set 0 to disable)")
flags.DurationVar((*time.Duration)(&srv.Config.Postgres.ReadTimeout), "postgres.read-timeout", time.Duration(srv.Config.Postgres.ReadTimeout), "Timeout for reads on a postgres connection. (set 0 to disable; does not include connection idling)")
flags.DurationVar((*time.Duration)(&srv.Config.Postgres.WriteTimeout), "postgres.write-timeout", time.Duration(srv.Config.Postgres.WriteTimeout), "Timeout for writes on a postgres connection. (set 0 to disable)")
flags.Uint32Var(&srv.Config.Postgres.MaxStartupSize, "postgres.max-startup-size", srv.Config.Postgres.MaxStartupSize, "Maximum acceptable size of a postgres startup packet, in bytes. (set 0 to disable)")
flags.Uint16Var(&srv.Config.Postgres.ConnectionLimit, "postgres.connection-limit", srv.Config.Postgres.ConnectionLimit, "Maximum number of simultaneous postgres connections to allow. (set 0 to disable)")
}

View file

@ -35,3 +35,14 @@ func TestBuildServerFlags(t *testing.T) {
t.Fatal("log-path flag is required")
}
}
func TestServerDefaultTxsrcFlags(t *testing.T) {
cm := &cobra.Command{}
buf := bytes.Buffer{}
stdin, stdout, stderr := GetIO(buf)
Server := server.NewCommand(stdin, stdout, stderr)
BuildServerFlags(cm, Server)
if cm.Flags().Lookup("txsrc").DefValue != "" {
t.Fatal("cannot set the txsrc default in ctl/server.go, otherwise we won't know to let the environment override the lack of --txsrc on the command line. We want explicit command line --txsrc to override the env value.")
}
}

1266
dbshard.go Normal file

File diff suppressed because it is too large Load diff

366
dbshard_internal_test.go Normal file
View file

@ -0,0 +1,366 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/pilosa/pilosa/v2/rbf"
"github.com/pilosa/pilosa/v2/shardwidth"
txkey "github.com/pilosa/pilosa/v2/short_txkey"
//txkey "github.com/pilosa/pilosa/v2/txkey"
)
// Shard per db evaluation
func TestShardPerDB_SetBit(t *testing.T) {
f, idx, tx := mustOpenFragment(t, "i", "f", viewStandard, 0, "")
_ = idx
defer f.Clean(t)
// Set bits on the fragment.
if _, err := f.setBit(tx, 120, 1); err != nil {
t.Fatal(err)
} else if _, err := f.setBit(tx, 120, 6); err != nil {
t.Fatal(err)
} else if _, err := f.setBit(tx, 121, 0); err != nil {
t.Fatal(err)
}
// should have two containers set in the fragment.
// Verify counts on rows.
if n := f.mustRow(tx, 120).Count(); n != 2 {
t.Fatalf("unexpected count: %d", n)
} else if n := f.mustRow(tx, 121).Count(); n != 1 {
t.Fatalf("unexpected count: %d", n)
}
// commit the change, and verify it is still there
panicOn(tx.Commit())
// Close and reopen the fragment & verify the data.
err := f.Reopen() // roaring data not being flushed? red on roaring
if err != nil {
t.Fatal(err)
}
tx = idx.holder.txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f, Shard: f.shard})
defer tx.Rollback()
if n := f.mustRow(tx, 120).Count(); n != 2 {
t.Fatalf("unexpected count (reopen): %d", n)
} else if n := f.mustRow(tx, 121).Count(); n != 1 {
t.Fatalf("unexpected count (reopen): %d", n)
}
}
// test that we find all *local* shards
func Test_DBPerShard_GetShardsForIndex_LocalOnly(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "Test_DBPerShard_GetShardsForIndex_LocalOnly")
panicOn(err)
orig := os.Getenv("PILOSA_TXSRC")
defer os.Setenv("PILOSA_TXSRC", orig) // must restore or will mess up other tests!
v2s := NewFieldView2Shards()
stdShardSet := newShardSet()
for _, shard := range []uint64{93, 223, 221, 215, 219, 217} {
stdShardSet.add(shard)
}
for _, field := range []string{"f", "_exists"} {
v2s.addViewShardSet(txkey.FieldView{Field: field, View: "standard"}, stdShardSet)
}
for _, src := range []string{"roaring", "bolt", "rbf"} {
os.Setenv("PILOSA_TXSRC", src)
// must make Holder AFTER setting src.
holder := NewHolder(tmpdir, nil)
index := "rick"
idx := makeSampleRoaringDir(tmpdir, index, src, 1, holder, v2s)
if idx == nil {
idx, err = NewIndex(holder, filepath.Join(tmpdir, index), index)
panicOn(err)
}
estd := "rick/_exists/views/standard"
std := "rick/f/views/standard"
shards, err := holder.txf.GetShardsForIndex(idx, tmpdir+sep+std, false)
panicOn(err)
for _, shard := range []uint64{93, 223, 221, 215, 219, 217} {
if !shards[shard] {
panic(fmt.Sprintf("missing shard=%v from shards='%#v'", shard, shards))
}
}
if src == "roaring" {
// check estd too
shards, err = holder.txf.GetShardsForIndex(idx, tmpdir+sep+estd, false)
panicOn(err)
for _, shard := range []uint64{93, 223, 221, 215, 219, 217} {
if !shards[shard] {
panic(fmt.Sprintf("missing shard=%v from shards='%#v'", shard, shards))
}
}
// check GetSortedFieldViewList() and roaringGetFieldView2Shards()
vs, err := roaringGetFieldView2Shards(idx)
panicOn(err)
for _, shard := range []uint64{93, 223, 221, 215, 219, 217} {
tx := idx.holder.txf.NewTx(Txo{Write: !writable, Index: idx, Shard: shard})
fvs, err := tx.GetSortedFieldViewList(idx, shard)
panicOn(err)
// expect these same two field/views for all 6 shards
expect0 := txkey.FieldView{Field: "_exists", View: "standard"}
expect1 := txkey.FieldView{Field: "f", View: "standard"}
if len(fvs) != 2 {
panic(fmt.Sprintf("fvs should be len 2, got '%#v'", fvs))
}
if fvs[0] != expect0 {
panic(fmt.Sprintf("expected fvs[0]='%#v', but got '%#v'", expect0, fvs[0]))
}
if fvs[1] != expect1 {
panic(fmt.Sprintf("expected fvs[1]='%#v', but got '%#v'", expect1, fvs[1]))
}
for _, fv := range fvs {
if !vs.has(fv.Field, fv.View, shard) {
panic(fmt.Sprintf("vs did not contain fv='%#v' for shard %v", fv, shard))
}
}
tx.Rollback()
}
} else {
// non-roaring: rbf, bolt
for _, shard := range []uint64{93, 223, 221, 215, 219, 217} {
tx := idx.holder.txf.NewTx(Txo{Write: !writable, Index: idx, Shard: shard})
fvs, err := tx.GetSortedFieldViewList(idx, shard)
panicOn(err)
// expect these same two field/views for all 6 shards
expect0 := txkey.FieldView{Field: "_exists", View: "standard"}
expect1 := txkey.FieldView{Field: "f", View: "standard"}
if len(fvs) != 2 {
panic(fmt.Sprintf("fvs should be len 2, got '%#v'", fvs))
}
if fvs[0] != expect0 {
panic(fmt.Sprintf("expected fvs[0]='%#v', but got '%#v'", expect0, fvs[0]))
}
if fvs[1] != expect1 {
panic(fmt.Sprintf("expected fvs[1]='%#v', but got '%#v'", expect1, fvs[1]))
}
tx.Rollback()
}
}
holder.Close()
}
}
// data for Test_DBPerShard_GetShardsForIndex
//
var sampleRoaringDirList = map[string]string{"roaring": `
rick/f/views/standard/fragments/215.cache
rick/f/views/standard/fragments/221.cache
rick/f/views/standard/fragments/223.cache
rick/f/views/standard/fragments/93.cache
rick/f/views/standard/fragments/217.cache
rick/f/views/standard/fragments/219.cache
rick/f/views/standard/fragments/217
rick/f/views/standard/fragments/219
rick/f/views/standard/fragments/215
rick/f/views/standard/fragments/221
rick/f/views/standard/fragments/223
rick/f/views/standard/fragments/93
rick/_exists/views/standard/fragments/221
rick/_exists/views/standard/fragments/215
rick/_exists/views/standard/fragments/217
rick/_exists/views/standard/fragments/93
rick/_exists/views/standard/fragments/219
rick/_exists/views/standard/fragments/223
`,
"bolt": `
rick.index.txstores@@@/store-boltdb@@/shard.0093-boltdb@/bolt.db
rick.index.txstores@@@/store-boltdb@@/shard.0215-boltdb@/bolt.db
rick.index.txstores@@@/store-boltdb@@/shard.0217-boltdb@/bolt.db
rick.index.txstores@@@/store-boltdb@@/shard.0219-boltdb@/bolt.db
rick.index.txstores@@@/store-boltdb@@/shard.0221-boltdb@/bolt.db
rick.index.txstores@@@/store-boltdb@@/shard.0223-boltdb@/bolt.db
`,
"rbf": `
rick.index.txstores@@@/store-rbfdb@@/shard.0093-rbfdb@
rick.index.txstores@@@/store-rbfdb@@/shard.0215-rbfdb@
rick.index.txstores@@@/store-rbfdb@@/shard.0217-rbfdb@
rick.index.txstores@@@/store-rbfdb@@/shard.0219-rbfdb@
rick.index.txstores@@@/store-rbfdb@@/shard.0221-rbfdb@
rick.index.txstores@@@/store-rbfdb@@/shard.0223-rbfdb@
`,
}
func makeSampleRoaringDir(root, index, txsrc string, minBytes int, h *Holder, view2shards *FieldView2Shards) (idx *Index) {
shards := []uint64{0, 93, 215, 217, 219, 221, 223}
fns := strings.Split(sampleRoaringDirList[txsrc], "\n")
firstDone := false
for i, fn := range fns {
if fn == "" {
continue
}
var shard uint64
if txsrc != "roaring" {
// only have shards for the non-roaring
shard = shards[i]
}
switch txsrc {
case "bolt", "rbf":
idx = helperCreateDBShard(h, index, shard)
// first time only, we'll actually make all the shards at this point because
// view2shards has them all anyway.
if !firstDone {
firstDone = true
makeTxTestDBWithViewsShards(h, idx, view2shards)
}
continue
}
path := root + sep + filepath.Dir(fn)
panicOn(os.MkdirAll(path, 0755))
fd, err := os.Create(root + sep + fn)
panicOn(err)
if minBytes > 0 {
_, err := fd.Write(make([]byte, minBytes))
panicOn(err)
}
fd.Close()
}
return
}
func helperCreateDBShard(h *Holder, index string, shard uint64) *Index {
idx, err := h.CreateIndexIfNotExists(index, IndexOptions{})
panicOn(err)
dbs, err := h.txf.dbPerShard.GetDBShard(index, shard, idx)
panicOn(err)
_ = dbs
return idx
}
// keep the ocd linter happy
var _ = makeBolttestDB
var _ = makeRBFtestDB
func makeBolttestDB(path string, h *Holder, shard uint64) {
i := uint64(1)
w, _ := mustOpenEmptyBoltWrapper(path)
BoltMustSetBitvalue(w, "index", "field", "view", shard, i)
w.Close()
}
func makeRBFtestDB(path string, h *Holder, shard uint64) {
i := uint64(1)
db := rbf.NewDB(path, nil)
err := db.Open()
panicOn(err)
defer db.Close()
tx, err := db.Begin(true)
panicOn(err)
err = tx.CreateBitmap("x")
panicOn(err)
_, err = tx.Add("x", i)
panicOn(err)
err = tx.Commit()
panicOn(err)
}
func makeTxTestDBWithViewsShards(holder *Holder, idx *Index, exp *FieldView2Shards) {
// TODO(jea): need date time quantum views!!
batched := false
for field, viewmap := range exp.m {
for view, shset := range viewmap {
ss := shset.CloneMaybe()
for shard := range ss {
// simply write 1 bit to each shard to force its creation.
bits := []uint64{(shard << shardwidth.Exponent) + 1}
tx := idx.holder.txf.NewTx(Txo{Write: writable, Index: idx, Shard: shard})
changeCount, err := tx.Add(idx.name, field, view, shard, batched, bits...)
panicOn(err)
if changeCount != len(bits) {
panic(fmt.Sprintf("writing field '%v', view '%v' shard '%v', expected changeCount to equal len bits = %v but was %v", field, view, shard, len(bits), changeCount))
}
panicOn(tx.Commit())
}
}
}
}
// test that rbf can give us a map[view]*shardSet
func Test_DBPerShard_GetFieldView2Shards_map_from_RBF(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "Test_DBPerShard_GetFieldView2Shards_map_from_RBF")
panicOn(err)
orig := os.Getenv("PILOSA_TXSRC")
defer os.Setenv("PILOSA_TXSRC", orig) // must restore or will mess up other tests!
os.Setenv("PILOSA_TXSRC", "rbf")
// must make Holder AFTER setting src.
holder := NewHolder(tmpdir, nil)
defer holder.Close()
index := "rick"
field := "f"
idx, err := holder.createIndex(index, IndexOptions{})
panicOn(err)
exp := NewFieldView2Shards()
stdShardSet := newShardSet()
stdShardSet.add(12)
stdShardSet.add(15)
exp.addViewShardSet(txkey.FieldView{Field: field, View: "standard"}, stdShardSet)
hrShardSet := newShardSet()
hrShardSet.add(7)
exp.addViewShardSet(txkey.FieldView{Field: field, View: "standard_2019092416"}, hrShardSet)
makeTxTestDBWithViewsShards(holder, idx, exp)
// setup is done
view2shard, err := holder.txf.GetFieldView2ShardsMapForIndex(idx)
panicOn(err)
// compare against setup
if !view2shard.equals(exp) {
panic(fmt.Sprintf("expected '%v' but got view2shard '%v'", exp, view2shard))
}
}

111
dbshard_test.go Normal file
View file

@ -0,0 +1,111 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa_test
import (
"context"
"fmt"
"reflect"
"testing"
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/boltdb"
"github.com/pilosa/pilosa/v2/http"
"github.com/pilosa/pilosa/v2/server"
"github.com/pilosa/pilosa/v2/test"
)
func TestAPI_SimplerOneNode_ImportColumnKey(t *testing.T) {
c := test.MustRunCluster(t, 1,
[]server.CommandOption{
server.OptCommandServerOptions(
pilosa.OptServerNodeID("node0"),
pilosa.OptServerClusterHasher(&offsetModHasher{}),
pilosa.OptServerOpenTranslateStore(boltdb.OpenTranslateStore),
pilosa.OptServerOpenTranslateReader(http.GetOpenTranslateReaderFunc(nil)),
)},
)
defer c.Close()
m0 := c.GetNode(0)
t.Run("RowIDColumnKey", func(t *testing.T) {
ctx := context.Background()
indexName := "rick"
fieldName := "f"
index, err := m0.API.CreateIndex(ctx, indexName, pilosa.IndexOptions{Keys: true, TrackExistence: true})
if err != nil {
t.Fatalf("creating index: %v", err)
}
if index.CreatedAt() == 0 {
t.Fatal("index createdAt is empty")
}
field, err := m0.API.CreateField(ctx, indexName, fieldName, pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, 100))
if err != nil {
t.Fatalf("creating field: %v", err)
}
if field.CreatedAt() == 0 {
t.Fatal("field createdAt is empty")
}
rowID := uint64(1)
timestamp := int64(0)
// Generate some keyed records.
rowIDs := []uint64{}
timestamps := []int64{}
for i := 1; i <= 10; i++ {
rowIDs = append(rowIDs, rowID)
timestamps = append(timestamps, timestamp)
}
// Keys are sharded so ordering is not guaranteed.
colKeys := []string{"col10", "col8", "col9", "col6", "col7", "col4", "col5", "col2", "col3", "col1"}
// Import data with keys to the coordinator (node0) and verify that it gets
// translated and forwarded to the owner of shard 0 (node1; because of offsetModHasher)
req := &pilosa.ImportRequest{
Index: indexName,
IndexCreatedAt: index.CreatedAt(),
Field: fieldName,
FieldCreatedAt: field.CreatedAt(),
Shard: 0, // import is all on shard 0, why are we making bocu other shards? b/c this is ignored.
RowIDs: rowIDs,
ColumnKeys: colKeys,
Timestamps: timestamps,
}
qcx := m0.API.Txf().NewQcx()
if err := m0.API.Import(ctx, qcx, req); err != nil {
t.Fatal(err)
}
panicOn(qcx.Finish())
//select {}
pql := fmt.Sprintf("Row(%s=%d)", fieldName, rowID)
// Query node0.
if res, err := m0.API.Query(ctx, &pilosa.QueryRequest{Index: indexName, Query: pql}); err != nil {
t.Fatal(err)
} else if keys := res.Results[0].(*pilosa.Row).Keys; !reflect.DeepEqual(keys, colKeys) {
t.Fatalf("unexpected column keys: %#v", keys)
}
})
}

142
debugstats/stats.go Normal file
View file

@ -0,0 +1,142 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package debugstats
import (
"fmt"
"math"
//"os"
"runtime"
"sort"
"sync"
"time"
)
type CallStats struct {
// protect elap
mu sync.Mutex
// track how much time each call took.
elap map[string]*elapsed
}
type elapsed struct {
dur []float64
}
func NewCallStats() *CallStats {
w := &CallStats{}
w.Reset()
return w
}
func (w *CallStats) Reset() {
w.mu.Lock()
defer w.mu.Unlock()
w.elap = make(map[string]*elapsed)
}
type LineSorter struct {
Line string
Tot float64
}
type SortByTot []*LineSorter
func (p SortByTot) Len() int {
return len(p)
}
func (p SortByTot) Less(i, j int) bool {
return p[i].Tot < p[j].Tot
}
func (p SortByTot) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
func (c *CallStats) Report(title string) (r string) {
//txsrc := os.Getenv("PILOSA_TXSRC")
r = fmt.Sprintf("CallStats: (%v)\n", title)
c.mu.Lock()
defer c.mu.Unlock()
var lines []*LineSorter
for id, elap := range c.elap {
slc := elap.dur
n := len(slc)
if n == 0 {
continue
}
mean, sd, totaltm := computeMeanSd(slc)
if n == 1 {
sd = 0
mean = slc[0]
totaltm = slc[0]
}
line := fmt.Sprintf(" %20v N=%8v avg/op: %12v sd: %12v total: %12v\n", id, n, time.Duration(mean), time.Duration(sd), time.Duration(totaltm))
lines = append(lines, &LineSorter{Line: line, Tot: totaltm})
}
sort.Sort(SortByTot(lines))
for i := range lines {
r += lines[i].Line
}
if false {
var m1 runtime.MemStats
runtime.ReadMemStats(&m1)
r += fmt.Sprintf("\n m1.TotalAlloc = %v\n", m1.TotalAlloc)
}
return
}
var NaN = math.NaN()
func computeMeanSd(slc []float64) (mean, sd, tot float64) {
if len(slc) < 2 {
return NaN, NaN, NaN
}
for _, v := range slc {
tot += v
}
n := float64(len(slc))
mean = tot / n
variance := 0.0
for _, v := range slc {
tmp := (v - mean)
variance += tmp * tmp
}
variance = variance / n // biased, but we don't care b/c we can have very small n
sd = math.Sqrt(variance)
if sd < 1e-8 {
// sd is super close to zero, NaN out the z-score rather than +/- Inf
sd = NaN
}
return
}
func (c *CallStats) Add(k string, dur time.Duration) {
if c == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
e, ok := c.elap[k]
if !ok {
e = &elapsed{}
c.elap[k] = e
}
e.dur = append(e.dur, float64(dur))
}

54
debugstats/stats_test.go Normal file
View file

@ -0,0 +1,54 @@
// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package debugstats
import (
"fmt"
"testing"
"time"
)
func TestCallStats(t *testing.T) {
callStats := NewCallStats()
for j := 0; j < 4; j++ {
t0 := time.Now()
doOperation0()
callStats.Add("op0", time.Since(t0))
t1 := time.Now()
doOperation1()
callStats.Add("op1", time.Since(t1))
t2 := time.Now()
doOperation2()
callStats.Add("op2", time.Since(t2))
}
fmt.Printf("report = \n%v\n", callStats.Report("test"))
}
func doOperation0() {
time.Sleep(50 * time.Millisecond)
}
func doOperation1() {
time.Sleep(100 * time.Millisecond)
}
func doOperation2() {
time.Sleep(200 * time.Millisecond)
}

View file

@ -229,11 +229,11 @@ func (d *diagnosticsCollector) EnrichWithSchemaProperties() {
timeQuantumEnabled := false
for _, index := range d.server.holder.Indexes() {
numShards += index.AvailableShards().Count()
numShards += index.AvailableShards(includeRemote).Count()
numIndexes++
for _, field := range index.Fields() {
numFields++
if field.Type() == FieldTypeInt {
if field.Type() == FieldTypeInt || field.Type() == FieldTypeDecimal {
bsiFieldCount++
}
if field.TimeQuantum() != "" {
@ -275,6 +275,7 @@ type SystemInfo interface {
CPUCores() (physical int, logical int, err error)
CPUMHz() (int, error)
CPUArch() string
DiskCapacity(string) (uint64, error)
}
// newNopSystemInfo creates a no-op implementation of SystemInfo.
@ -345,3 +346,8 @@ func (n *nopSystemInfo) CPUMHz() (int, error) {
func (n *nopSystemInfo) CPUCores() (physical, logical int, err error) {
return 0, 0, nil
}
// DiskCapacity returns the disk capacity
func (n *nopSystemInfo) DiskCapacity(path string) (uint64, error) {
return 0, nil
}

View file

@ -29,6 +29,7 @@ import (
func TestDiagnosticsClient(t *testing.T) {
// Mock server.
server := httptest.NewServer(nil)
defer server.Close()
// Create a new client.
d := newDiagnosticsCollector(server.URL)
@ -121,6 +122,7 @@ func TestDiagnosticsVersion_Check(t *testing.T) {
t.Fatalf("couldn't encode version response: %v", err)
}
}))
defer server.Close()
// Create a new client.
d := newDiagnosticsCollector("localhost:10101")
@ -158,6 +160,7 @@ func compareJSON(a, b []byte) (bool, error) {
func BenchmarkDiagnostics(b *testing.B) {
// Mock server.
server := httptest.NewServer(nil)
defer server.Close()
// Create a new client.
d := newDiagnosticsCollector(server.URL)

View file

@ -15,7 +15,7 @@ Is equivalent to `GET /schema` and returns the same response.
### List index schema
`GET /index/<index-name>`
`GET /index/{index-name}`
Returns the schema of the specified index in JSON.
@ -24,27 +24,31 @@ curl -XGET localhost:10101/index/user
```
``` response
{
"fields": [
{
"name": "event",
"options": {
"keys": false,
"timeQuantum": "YMD",
"type": "time"
}
}
],
"name": "user",
"options": {
"keys": false,
"trackExistence": true
"name": "user",
"createdAt": 1591178953061239000,
"options": {
"keys": false,
"trackExistence": true
},
"fields": [
{
"name": "event",
"createdAt": 1591178962332452000,
"options": {
"type": "set",
"cacheType": "ranked",
"cacheSize": 50000,
"keys": false
}
}
],
"shardWidth": 1048576
}
```
### Create index
`POST /index/<index-name>`
`POST /index/{index-name}`
Creates an index with the given name.
@ -57,7 +61,7 @@ The request payload is in JSON, and may contain the `options` field. The `option
curl -XPOST localhost:10101/index/user -d '{"options":{"keys":true}}'
```
``` response
{"success":true}
{"success":true,"name":"user","createdAt":1591179042178854000}
```
### Remove index
@ -75,7 +79,7 @@ curl -XDELETE localhost:10101/index/user
### Query index
`POST /index/<index-name>/query`
`POST /index/{index-name}/query`
Sends a [query](../query-language/) to the Pilosa server with the given index. The request body is UTF-8 encoded text and response body is in JSON by default.
@ -133,7 +137,7 @@ By default, all bits and attributes (*for `Row` queries only*) are returned. In
### Import Data
`POST /index/<index-name>/field/<field-name>/import`
`POST /index/{index-name}/field/{field-name}/import`
Supports high-rate data ingest to a particular shard of a particular field. The
official client libraries use this endpoint for their import functionality - it
@ -151,23 +155,37 @@ represents a particular bit to be set. Timestamps are optional, but if they
exist must also contain the same number of items as rows and columns. The
column IDs must all be in the shard specified in the request.
Some endpoints and data structures include a `CreatedAt` fields.
This is typically stored as a timestamp, but it's purpose is not to inform of the creation date of a particular index or field,
but to serve as a unique identifier for use in cache invalidation.
The problem is that users of Pilosa (such as ingesters e.g. the [IDK](https://github.com/molecula/idk))
can usually assume that translation keys for records and field values never change - they are only appended to, and can therefore be trivially cached.
This is true except in cases where an index or field gets deleted and then recreated,
or if Pilosa is restored from a backup.
So the ingesters must send their current `CreatedAt` value which will have changed if either of those two conditions has occured (or if Pilosa was just restarted),
and the ingester will know that it needs to drop its cache.
```
message ImportRequest {
string Index = 1;
string Field = 2;
uint64 Shard = 3;
repeated uint64 RowIDs = 4;
repeated uint64 ColumnIDs = 5;
repeated string RowKeys = 7;
repeated string ColumnKeys = 8;
repeated int64 Timestamps = 6;
string Index = 1;
string Field = 2;
uint64 Shard = 3;
repeated uint64 RowIDs = 4;
repeated uint64 ColumnIDs = 5;
repeated int64 Timestamps = 6;
repeated string RowKeys = 7;
repeated string ColumnKeys = 8;
int64 IndexCreatedAt = 9;
int64 FieldCreatedAt = 10;
}
```
### Create field
`POST /index/<index-name>/field/<field-name>`
`POST /index/{index-name}/field/{field-name}`
Creates a field in the given index with the given name.
@ -200,7 +218,7 @@ curl localhost:10101/index/user/field/quantity \
-d '{"options": {"type": "int", "min": -1000, "max":2000}}'
```
``` response
{"success":true}
{"success":true,"name":"quantity","createdAt":1591180110914425000}
```
Integer fields are stored as n-bit range-encoded values. Pilosa supports 63-bit, signed integers with values between `min` and `max`.
@ -209,21 +227,21 @@ Integer fields are stored as n-bit range-encoded values. Pilosa supports 63-bit,
curl localhost:10101/index/user/field/language -X POST
```
``` response
{"success":true}
{"success":true,"name":"language","createdAt":1591180128294321000}
```
``` request
curl localhost:10101/index/repository/field/stats \
-X POST \
-d '{"fields": [{"name": "pullrequests", "type": "int", "min": 0, "max": 1000000}]}'
-d '{"options":{"type": "int", "min": 0, "max": 1000000}}'
```
``` response
{"success":true}
{"success":true,"name":"stats","createdAt":1591180737881627000}
```
### Remove field
`DELETE /index/<index-name>/field/<field-name>`
`DELETE /index/{index-name}/field/{field-name}`
Removes the given field.
@ -245,34 +263,52 @@ curl -XGET localhost:10101/schema
```
``` response
{
"indexes": [
"indexes": [
{
"name": "user",
"createdAt": 1591178953061239000,
"options": {
"keys": false,
"trackExistence": true
},
"fields": [
{
"fields": [
{
"name": "event",
"options": {
"keys": false,
"timeQuantum": "YMD",
"type": "time"
}
},
{
"name": "language",
"options": {
"cacheSize": 50000,
"cacheType": "ranked",
"keys": false,
"type": "set"
}
}
],
"name": "user",
"options": {
"keys": false,
"trackExistence": true
}
"name": "event",
"createdAt": 1591178962332452000,
"options": {
"type": "set",
"cacheType": "ranked",
"cacheSize": 50000,
"keys": false
}
},
{
"name": "language",
"createdAt": 1591180128294321000,
"options": {
"type": "set",
"cacheType": "ranked",
"cacheSize": 50000,
"keys": false
}
},
{
"name": "quantity",
"createdAt": 1591180110914425000,
"options": {
"type": "int",
"base": 0,
"bitDepth": 0,
"min": -1000,
"max": 2000,
"keys": false,
"foreignIndex": ""
}
}
]
],
"shardWidth": 1048576
}
]
}
```
@ -304,7 +340,7 @@ Returns the version of the Pilosa server.
curl -XGET localhost:10101/version
```
``` response
{"version":"v0.6.0"}
{"version":"2.0.0-alpha.20-6-gb9d8d6b4"}
```
### Get status
@ -318,22 +354,49 @@ curl -XGET localhost:10101/status
```
```response
{
"localID": "d3369125-29d8-4305-a351-b4474d14a542",
"nodes": [
{
"id": "d3369125-29d8-4305-a351-b4474d14a542",
"isCoordinator": true,
"uri": {
"host": "localhost",
"port": 10101,
"scheme": "http"
}
}
],
"state": "NORMAL"
"state": "NORMAL",
"nodes": [
{
"id": "1b018ce0-5de5-4da9-9285-6c4c0d8106f9",
"uri": {
"scheme": "http",
"host": "localhost",
"port": 10101
},
"grpc-uri": {
"scheme": "http",
"host": "localhost",
"port": 20101
},
"isCoordinator": true,
"state": "READY"
}
],
"localID": "1b018ce0-5de5-4da9-9285-6c4c0d8106f9"
}
```
### Get active queries
`GET /queries`
Returns the set of active queries. Supports pretty printing in `text/plain` format or JSON output in `application/json` format.
Also includes the amount of time that the query has been running (in nanoseconds when using JSON).
```request
curl -XGET localhost:10101/queries
```
```response
182.412µs All()
```
```request
curl -XGET -H "Accept: application/json" localhost:10101/queries
```
```response
[{"query":"All()","age":135123}]
```
### Recalculate Caches
`POST /recalculate-caches`

View file

@ -114,6 +114,16 @@ The config file is in the [toml format](https://github.com/toml-lang/toml) and h
```toml
verbose = true
```
#### Long Query Time
* Description: Duration that will trigger log and stat messages for slow queries.
* Flag: `long-query-time="1m0s"`
* Env: `PILOSA_CLUSTER_LONG_QUERY_TIME="1m0s"`
* Config:
```toml
long-query-time = "1m0s"
```
#### Max Map Count
@ -220,6 +230,18 @@ The config file is in the [toml format](https://github.com/toml-lang/toml) and h
key = "/var/secret/gossip.key32"
```
#### Cluster Long Query Time
* Description (DEPRICATED, see Long Query Time): Duration that will trigger log and stat messages for slow queries.
* Flag: `cluster.long-query-time="1m0s"`
* Env: `PILOSA_CLUSTER_LONG_QUERY_TIME="1m0s"`
* Config:
```toml
[cluster]
long-query-time = "1m0s"
```
#### Cluster Coordinator
* Description: Indicates whether the node should act as the coordinator for the cluster. Only one node per cluster should be the coordinator.
@ -232,18 +254,6 @@ The config file is in the [toml format](https://github.com/toml-lang/toml) and h
coordinator = true
```
#### Cluster Long Query Time
* Description: Duration that will trigger log and stat messages for slow queries.
* Flag: `cluster.long-query-time="1m0s"`
* Env: `PILOSA_CLUSTER_LONG_QUERY_TIME="1m0s"`
* Config:
```toml
[cluster]
long-query-time = "1m0s"
```
#### Cluster Replicas
* Description: Number of hosts each piece of data should be stored on.
@ -302,7 +312,7 @@ The config file is in the [toml format](https://github.com/toml-lang/toml) and h
```toml
[metric]
service = “statsd”
service = "statsd"
```
#### Metric Host
@ -319,7 +329,7 @@ The config file is in the [toml format](https://github.com/toml-lang/toml) and h
#### Metric Poll Interval
* Description: Rate at which runtime metrics (such as open file handles and memory usage) are collected.
* Flag: `metric.poll-interval=”0m15s”`
* Flag: `metric.poll-interval="0m15s"`
* Env: `PILOSA_METRIC_POLL_INTERVAL=0m15s`
* Config:
@ -403,7 +413,7 @@ The config file is in the [toml format](https://github.com/toml-lang/toml) and h
#### Tracing Sampler Type
* Description: Jaeger sampler type (const, probabilistic, ratelimiting, or remote). Set to 'off' to disable tracing completely.
* Description: Jaeger sampler type (const, probabilistic, ratelimiting, or remote). Set to 'off' to disable tracing completely. Default is 'off'.
* Flag: `tracing.sampler-type`
* Env: `PILOSA_TRACING_SAMPLER_TYPE`
* Config:

View file

@ -121,6 +121,18 @@ Upon creation, fields are configured to be of a certain type. Pilosa supports th
#### Set
Set is the default field type in Pilosa. Set fields represent a standard, binary matrix of rows and columns where each row key represents a possible field value. The following example creates a `set` field called "info" with a ranked cache containing up to 100,000 records.
Row and/or column key can be a string literal (e.g. "value"). This mapping is also stored in a separate BoltDB data structure. Becauase BoltDB does not allow to have empty strings as keys, in pilosa we translate an empty string key into sentinel byte slice:
```go
[]byte{
0x00, 0x00, 0x00,
0x4d, 0x54, 0x4d, 0x54, // MTMT
0x00,
0xc2, 0xa0, // NO-BREAK SPACE
0x00,
}
```
(where the first three bytes are _zero_ bytes, next four bytes stands for `MTMT` literal and the rest four bytes represent NBSP prefixed and suffixed with _zero_ byte).
In reverse translation, if we get from BoltDB the sentinel key, pilosa will rewrite it into an empty string (`""`).
``` request
curl localhost:10101/index/repository/field/info \

View file

@ -81,7 +81,7 @@ lfm := pdk.LinearFloatMapper{
}
```
`Min` and `Max` define the linear function, and `Res` determines the maximum allowed value for the output row ID - we chose these values to produce a “round to nearest integer” behavior. Other predefined mappers have their own specific parameters, usually two or three.
`Min` and `Max` define the linear function, and `Res` determines the maximum allowed value for the output row ID - we chose these values to produce a "round to nearest integer" behavior. Other predefined mappers have their own specific parameters, usually two or three.
This mapper function is the core operation, but we need a few other pieces to define the overall process, which is encapsulated in the ColumnMapper object. This object defines which field(s) of the input data source to use (`Fields`), how to parse them (`Parsers`), what mapping to use (`Mapper`), and the name of the field to use (`Field`). <!-- TODO update so this makes sense -->
```go

View file

@ -100,7 +100,7 @@ curl localhost:10101/index/repository -X POST
``` response
{"success":true}
```
The index name must be 64 characters or fewer, start with a letter, and consist only of lowercase alphanumeric characters or `_-`. The same goes for field names.
The index name must be 230 characters or fewer, start with a letter, and consist only of lowercase alphanumeric characters or `_-`. The same goes for field names.
Let's create the `stargazer` field which has user IDs of stargazers as its rows:
``` request
@ -325,7 +325,7 @@ Next, let's create the `repository` index:
repository := schema.Index("repository")
```
The index name must be 64 characters or fewer, start with a letter, and consist only of lowercase alphanumeric characters or `_-`. The same goes for field names.
The index name must be 230 characters or fewer, start with a letter, and consist only of lowercase alphanumeric characters or `_-`. The same goes for field names.
Let's create the `stargazer` field which has user IDs of stargazers as its rows:
```
@ -615,7 +615,7 @@ Next, let's create the `repository` index:
```
Index repository = schema.index("repository");
```
The index name must be 64 characters or fewer, start with a letter, and consist only of lowercase alphanumeric characters or `_-`. The same goes for field names.
The index name must be 230 characters or fewer, start with a letter, and consist only of lowercase alphanumeric characters or `_-`. The same goes for field names.
Let's create the `stargazer` field which has user IDs of stargazers as its rows:
```
@ -818,7 +818,7 @@ Next, let's create the `repository` index:
```
repository = schema.index("repository")
```
The index name must be 64 characters or fewer, start with a letter, and consist only of lowercase alphanumeric characters or `_-`. The same goes for field names.
The index name must be 230 characters or fewer, start with a letter, and consist only of lowercase alphanumeric characters or `_-`. The same goes for field names.
Let's create the `stargazer` field which has user IDs of stargazers as its rows:
```

View file

@ -58,7 +58,7 @@ nav = []
<strong id="range-bsi">[Row (BSI)](../query-language/#row-bsi):</strong> A [PQL](#pql) query that returns bits based on comparison to integers stored in [BSI](#bsi) [fields](#field).
<strong id="rows">[Rows](../query-language/#rows):<strong> A [PQL](#pql) query that returns a list of row IDs in the given field which have at least one bit set. The field argument is mandatory, the others are optional. `Rows` is the primary argument used with the [GroupBy](#groupby) query.
<strong id="rows">[Rows](../query-language/#rows):</strong> A [PQL](#pql) query that returns a list of row IDs in the given field which have at least one bit set. The field argument is mandatory, the others are optional. `Rows` is the primary argument used with the [GroupBy](#groupby) query.
<strong id="slice">[Slice](../data-model/#shard):</strong> Prior to Pilosa 1.0, shards were known as slices.

View file

@ -43,7 +43,7 @@ curl localhost:10101/index/repository/query \
#### Arguments and Types
* `field` The field specifies on which Pilosa [field](../glossary/#field) the query will operate. Valid field names are lower case strings; they start with a lowercase letter, and contain only alphanumeric characters and `_-`. They must be 64 characters or less in length.
* `field` The field specifies on which Pilosa [field](../glossary/#field) the query will operate. Valid field names are lower case strings; they start with a lowercase letter, and contain only alphanumeric characters and `_-`. They must be 230 characters or less in length.
* `TIMESTAMP` This is a timestamp in the following format `YYYY-MM-DDTHH:MM` (e.g. 2006-01-02T15:04).
* `UINT` An unsigned integer (e.g. 42839).
* `BOOL` A boolean value, `true` or `false`.
@ -52,6 +52,7 @@ curl localhost:10101/index/repository/query \
* `CALL` Any query.
* `ROW_CALL` Any query which returns a row, such as `Row`, `Union`, `Difference`, `Xor`, `Intersect`, `Not`.
* `ROWS_CALL` A query that returns a `Rows` result (i.e. a list of row IDs). Currently only the `Rows` query.
* `ROWSET_CALL` A query that returns a set of rows. Currently only the `Rows` and `TopN` queries.
* `[]ATTR_VALUE` Denotes an array of `ATTR_VALUE`s. (e.g. `["a", "b", "c"]`).
### Write Operations
@ -428,9 +429,9 @@ Union([ROW_CALL ...])
**Description:**
Union performs a logical OR on the results of all `ROW_CALL` queries passed to it.
Union performs a set union on the column indexes in the results of all `ROW_CALL` queries passed to it. In comparison to a relational query, this is similar to combining clauses in the "OR" sense.
**Result Type:** object with attrs and bits
**Result Type:** object with attrs and columns
attrs will always be empty
@ -456,7 +457,7 @@ Intersect(<ROW_CALL>, [ROW_CALL ...])
**Description:**
Intersect performs a logical AND on the results of all `ROW_CALL` queries passed to it.
Intersect performs a set intersection on the column indexes in the results of all `ROW_CALL` queries passed to it. In comparison to a relational query, this is similar to combining clauses in the "AND" sense.
**Result Type:** object with attrs and columns
@ -570,6 +571,38 @@ Not(Row(stargazer=1))
* columns are repositories that were not starred by user 1
#### Limit
**Spec:**
```
Limit(<ROW_CALL>, [limit=<UINT>], [offset=<UINT>])
```
**Description:**
Limit executes a `ROW_CALL` and returns a subset of the results.
If a limit of `n` is specified, then this query will return the first `n` results of the row call.
If an offset of `m` is specified, then this query will skip the first `m` results of the row call.
If both a limit and offset are specified, the offset is applied before the limit.
This can be used to implement pagination.
**Result Type:** object with attrs and columns
attrs will always be empty
**Examples:**
Find the second column that has a bit set in the given row.
```request
Limit(Row(stargazer=1), limit=1, offset=1)
```
```response
{"results":[{"attrs":{},"columns":[30]}]}
```
* columns are repositories that were starred by user 1
#### Count
**Spec:**
@ -595,34 +628,6 @@ Count(Row(stargazer=1))
* Result is the number of repositories that user 1 has starred.
#### Shift
**Spec:**
```
Shift(<ROW_CALL>, [n=UINT])
```
**Description:**
Returns the row specified by `ROW_CALL` shifted by `n` bits.
**Result Type:** object with attrs and columns
attrs will always be empty
**Examples:**
Query all columns with a bit set in row 1 of the field `stargazer`
and shift the result by 2:
```request
Shift(Row(stargazer=1), n=2)
```
```response
{"attrs":{},"columns":[12, 22]}
```
* columns are the repositories which user 1 has starred shifted by 2 bits.
#### TopN
**Spec:**
@ -643,9 +648,15 @@ have the attribute specified by `attrName` with one of the values specified in
**Caveats:**
* Performing a TopN() query on a field with cache type ranked will return the top rows sorted by count in descending order.
* Fields with cache type lru will maintain an LRU (Least Recently Used replacement policy) cache, thus a TopN query on this type of field will return rows sorted in order of most recently set bit.
* The field's cache size determines the number of sorted rows to maintain in the cache for purposes of TopN queries. There is a tradeoff between performance and accuracy; increasing the cache size will improve accuracy of results at the cost of performance.
In general, the order of the resulting row keys is not guaranteed to reflect the true order of bit counts across an index. The exact solution to the problem of computing the TopN counts is prohibitively expensive, so TopN is instead implemented as a heuristic. This provides a significant performance improvement, at the cost of uncertainty in the result order.
The implementation is based on a per-shard cache. The accuracy of the results depends on how well the counts for the overall index are reflected in the individual shards (so TopN queries on a single-shard index are exact). If the distribution of bits across shards is uniform, shard counts are representative. This is often a reasonable assumption, especially for the top results for large data sets, in which counts might follow Zipfian, exponential, or other long-tail distributions. However, this assumption may not hold for some applications.
Additional implementation details:
* The field's cache size determines the number of sorted rows to maintain in the cache for purposes of TopN queries. There is a tradeoff between performance and accuracy; increasing the cache size will improve accuracy of results at the cost of performance. Note that this per-shard tradeoff is independent of the per-index performance/accuracy tradeoff mentioned above.
* Fields with cache type `ranked` will return the top rows sorted by count in descending order.
* Fields with cache type `lru` will maintain an LRU (Least Recently Used replacement policy) cache, thus a TopN query on this type of field will return rows sorted in order of most recently set bit.
* Once full, the cache will truncate the set of rows according to the field option CacheSize. Rows that straddle the limit and have the same count will be truncated in no particular order.
* The TopN query's attribute filter is applied to the existing sorted cache of rows. Rows that fall outside of the sorted cache range, even if they would normally pass the filter, are ignored.
@ -813,12 +824,39 @@ Options(Row(f1=10), shards=[0, 2])
{"attrs":{},"columns":[100, 2097152]}
```
#### Row Constant
**Spec:**
```
ConstRow(columns=<[]COLUMN>)
```
**Description:**
`ConstRow` provides a constant bitmap value that can be used in place of a `Row` call.
The columns can be specified as integer IDs or strings.
**Result Type:** row value columns.
e.g. `{"attrs":{},"columns":[10, 20]}`
**Examples:**
Filter specified columns to only those with a bit set in row 1 of the field `stargazer` (repositories that are starred by user 1):
```request
Intersect(ConstRow(columns=[10, 20, 30]), Row(stargazer=1))
```
```response
{"attrs":{},"columns":[10, 20]}
```
#### Rows
**Spec:**
```
Rows(<FIELD>, previous=<UINT|STRING>, limit=<UINT>, column=<UINT|STRING>, from=<TIMESTAMP>, to=<TIMESTAMP>)
Rows(<FIELD>, previous=<UINT|STRING>, limit=<UINT>, column=<UINT|STRING>, from=<TIMESTAMP>, to=<TIMESTAMP>, like=<STRING>)
```
**Description:**
@ -839,6 +877,10 @@ If the field is of type `time`, the `from` and `to` arguments can be provided
to restrict the result to a specific time span. If `from` and `to` are
not provided, the full range of existing data will be queried.
If `like` is given, only keys matching a pattern will be selected.
A `like` pattern may use `_` as a placeholder to match a single UTF-8 codepoint, and `%` to match 0 or more codepoints.
All other characters will be matched exactly.
**Result Type:** Object with `"rows" or "keys" and an array of integers or strings respectively.`
**Examples:**
@ -856,7 +898,50 @@ With keys:
Rows(job)
```
```response
{"rows":null,"keys":["engineer","management","student""]}
{"rows":null,"keys":["engineer","management","student"]}
```
With `like`:
```request
Rows(job, like="%t")
```
```response
{"rows":null,"keys":["management","student"]}
```
#### Extract
**Spec:**
```
Extract(<ROW_CALL>, [<ROWS_CALL>...])
```
**Description:**
Extract intersects a set of columns with a set of rows in order to extract a subset of the index.
The result is a table consisting of the matched columns and the rows which they intersect.
This is similar to a select query in a SQL database.
**Result Type:** Object with an array of the selected fields and an array of the selected columns.
The column array contains objects containing a column identifier and an array of field values.
Field values are typed as such:
- Bool Field - boolean or null
- Mutex Field (unkeyed) - 64-bit unsigned integer or null
- Mutex Field (keyed) - string or null
- Integer Field - 64-bit signed integer or null
- Decimal Field - Pilosa decimal value or null
- Set Field (unkeyed) - array of 64-bit unsigned integers
- Set Field (keyed) - array of strings
- Time Field - same as the equivalent Set
**Examples:**
List all stargazers who have starred repository 1, and the full set of repositories they have starred:
```request
Extract(Row(stargazer=1), Rows(stargazer))
```
```response
{"fields":[{"name":"stargazer","type":"set"}],"columns":[{"column":3,"rows":[[1, 2, 3]]}]}
```
#### Group By
@ -864,7 +949,7 @@ Rows(job)
**Spec:**
```
GroupBy(<ROWS_CALL>, [<ROWS_CALL>...], limit=<UINT>, filter=<ROW_CALL>)
GroupBy(<ROWS_CALL>, [<ROWS_CALL>...], limit=<UINT>, filter=<ROW_CALL>, aggregate=<CALL>)
```
**Description:**
@ -874,14 +959,18 @@ taking one row each from the specified `Rows` calls. It returns only those
combinations for which the count is greater than 0.
The optional `filter` argument takes any type of `Row` query (e.g. Row, Union,
Intersect, etc.) which will be intersected with each result prior to returning
the count. This is analogous to a WHERE clause applied to a relational GROUP BY
query.
Intersect, etc.) which will be intersected with each result prior to returning
the count. This is analagous to a WHERE clause applied to a relational GROUP BY
query.
The optional `limit` argument limits the number of results returned. The results
are ordered, so as long as the data isn't changing, the same query will return
the same result set.
The optional `aggregate` argument takes a `Sum()` query which will be used to
calculate the sum & count of each group. This is similar to using a `SUM()` in
the SELECT clause of a relation GROUP BY query.
Paging through results is supported by passing the `previous` argument to each
of the `Rows` calls in the GroupBy. Take the last result from your previous
`GroupBy` query, and pass each row ID in that result as the `previous` argument
@ -941,3 +1030,31 @@ GroupBy(Rows(age), Rows(job), limit=7, filter=Row(country=USA))
{"group":[{"field":"age","rowID":22},{"field":"job","rowKey":"student"}],"count":3},
{"group":[{"field":"age","rowID":29},{"field":"job","rowKey":"management"}],"count":7}]
```
#### UnionRows
**Spec:**
```
UnionRows([ROWSET_CALL ...])
```
**Description:**
UnionRows performs a logical OR on the rows matched by the results of all `ROWSET_CALL` queries passed to it.
**Result Type:** object with attrs and columns
attrs will always be empty
**Examples:**
Query columns with a bit set in any row (repositories that are starred by any user):
```request
UnionRows(Rows(stargazer))
```
```response
{"attrs":{},"columns":[10, 20, 30]}
```
* columns are repositories that were starred by any user

File diff suppressed because it is too large Load diff

View file

@ -1,661 +0,0 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

View file

@ -1,23 +0,0 @@
// Copyright (c) 2018 Pilosa Corp. All rights reserved.
//
// This file is part of Pilosa Enterprise Edition.
//
// Pilosa Enterprise Edition is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Pilosa Enterprise Edition is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Pilosa Enterprise Edition. If not, see <http://www.gnu.org/licenses/>.
// Package enterprise is now deprecated, and the functionality under
// enterprise/b has been copied into roaring/. It existed to inject enterprise
// implementations of various Pilosa features when Pilosa was built with
// "ENTERPRISE=1 make install". These features were dual-licensed separately
// from Pilosa community edition under the AGPL and Pilosa's commercial license.
package enterprise

File diff suppressed because it is too large Load diff

View file

@ -15,22 +15,27 @@
package pilosa
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"reflect"
"strconv"
"testing"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/testhook"
)
func TestExecutor_TranslateGroupByCall(t *testing.T) {
func TestExecutor_TranslateRowsOnBool(t *testing.T) {
path, _ := testhook.TempDirInDir(t, *TempDir, "pilosa-executor-")
holder := NewHolder(path, nil)
defer holder.Close()
e := &executor{
Holder: NewHolder(),
Holder: holder,
Cluster: NewTestCluster(t, 1),
}
e.Holder.Path, _ = ioutil.TempDir(*TempDir, "")
err := e.Holder.Open()
if err != nil {
if err := e.Holder.Open(); err != nil {
t.Fatalf("opening holder: %v", err)
}
@ -39,162 +44,55 @@ func TestExecutor_TranslateGroupByCall(t *testing.T) {
t.Fatalf("creating index: %v", err)
}
_, erra := idx.CreateField("ak", OptFieldKeys())
_, errb := idx.CreateField("b")
_, errc := idx.CreateField("ck", OptFieldKeys())
if erra != nil || errb != nil || errc != nil {
t.Fatalf("creating fields %v, %v, %v", erra, errb, errc)
shard := uint64(0)
tx := idx.holder.txf.NewTx(Txo{Write: writable, Index: idx, Shard: shard})
defer tx.Rollback()
fb, errb := idx.CreateField("b", OptFieldTypeBool())
_, errbk := idx.CreateField("bk", OptFieldTypeBool(), OptFieldKeys())
if errb != nil || errbk != nil {
t.Fatalf("creating fields %v, %v", errb, errbk)
}
query, err := pql.ParseString(`GroupBy(Rows(ak), Rows(b), Rows(ck), previous=["la", 0, "ha"])`)
if err != nil {
t.Fatalf("parsing query: %v", err)
}
c := query.Calls[0]
err = e.translateGroupByCall("i", idx, c)
if err != nil {
t.Fatalf("translating call: %v", err)
}
if len(c.Args["previous"].([]interface{})) != 3 {
t.Fatalf("unexpected length for 'previous' arg %v", c.Args["previous"])
}
for i, v := range c.Args["previous"].([]interface{}) {
if !isInt(v) {
t.Fatalf("expected all items in previous to be ints, but '%v' at index %d is %[1]T", v, i)
}
_, err1 := fb.SetBit(tx, 1, 1, nil)
_, err2 := fb.SetBit(tx, 2, 2, nil)
_, err3 := fb.SetBit(tx, 3, 3, nil)
if err1 != nil || err2 != nil || err3 != nil {
t.Fatalf("setting bit %v, %v, %v", err1, err2, err3)
}
errTests := []struct {
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
tests := []struct {
pql string
err string
}{
{
pql: `GroupBy(Rows(notfound), previous=1)`,
err: "'previous' argument must be list",
},
{
pql: `GroupBy(Rows(ak), previous=["la", 0])`,
err: "mismatched lengths",
},
{
pql: `GroupBy(Rows(ak), previous=[1])`,
err: "prev value must be a string",
},
{
pql: `GroupBy(Rows(notfound), previous=[1])`,
err: ErrFieldNotFound.Error(),
},
// TODO: an unknown key will actually allocate an id. this is probably bad.
// {
// pql: `GroupBy(Rows(ak), previous=["zoop"])`,
// err: "translating row key '",
// },
{
pql: `GroupBy(Rows(b), previous=["la"])`,
err: "which doesn't use string keys",
},
{pql: "Rows(b)"},
{pql: "GroupBy(Rows(b))"},
{pql: "Set(4, b=true)"},
}
for i, test := range errTests {
t.Run(fmt.Sprintf("#%d_%s", i, test.err), func(t *testing.T) {
for _, test := range tests {
t.Run(test.pql, func(t *testing.T) {
query, err := pql.ParseString(test.pql)
if err != nil {
t.Fatalf("parsing query: %v", err)
}
c := query.Calls[0]
err = e.translateGroupByCall("i", idx, c)
if err == nil {
t.Fatalf("expected error, but translated call is '%s", c)
colTranslations, rowTranslations, err := e.preTranslate(context.Background(), "i", c)
if err != nil {
t.Fatalf("pre-translating call: %v", err)
}
if !strings.Contains(err.Error(), test.err) {
t.Fatalf("expected '%s', got '%v'", test.err, err)
_, err = e.translateCall(c, "i", colTranslations, rowTranslations)
if err != nil {
t.Fatalf("translating call: %v", err)
}
})
}
}
func isInt(a interface{}) bool {
switch a.(type) {
case int, int64, uint, uint64:
return true
default:
return false
}
}
func TestFilterWithLimit(t *testing.T) {
f := filterWithLimit(5)
for i := uint64(0); i < 5; i++ {
include, done := f(i, i*(1<<shardVsContainerExponent), nil)
if done {
t.Fatalf("limit filter ended early on iteration %d", i)
}
if !include {
t.Fatalf("limit filter should always include until done")
}
}
inc, done := f(5, 5*(1<<shardVsContainerExponent)+1, nil)
if !done {
t.Fatalf("limit filter should have been done, but got inc: %v done: %v", inc, done)
}
}
func TestFilterWithRows(t *testing.T) {
tests := []struct {
rows []uint64
callWith []uint64
expect [][2]bool
}{
{
rows: []uint64{},
callWith: []uint64{0},
expect: [][2]bool{{false, true}},
},
{
rows: []uint64{0},
callWith: []uint64{0},
expect: [][2]bool{{true, true}},
},
{
rows: []uint64{1},
callWith: []uint64{0, 2},
expect: [][2]bool{{false, false}, {false, true}},
},
{
rows: []uint64{0},
callWith: []uint64{1, 2},
expect: [][2]bool{{false, true}, {false, true}},
},
{
rows: []uint64{3, 9},
callWith: []uint64{1, 2, 3, 10},
expect: [][2]bool{{false, false}, {false, false}, {true, false}, {false, true}},
},
{
rows: []uint64{0, 1, 2},
callWith: []uint64{0, 1, 2},
expect: [][2]bool{{true, false}, {true, false}, {true, true}},
},
}
for num, test := range tests {
t.Run(fmt.Sprintf("%d_%v_with_%v", num, test.rows, test.callWith), func(t *testing.T) {
if len(test.callWith) != len(test.expect) {
t.Fatalf("Badly specified test - must expect the same number of values as calls.")
}
f := filterWithRows(test.rows)
for i, id := range test.callWith {
inc, done := f(id, 0, nil)
if inc != test.expect[i][0] || done != test.expect[i][1] {
t.Fatalf("Calling with %d\nexp: %v,%v\ngot: %v,%v", id, test.expect[i][0], test.expect[i][1], inc, done)
}
}
})
}
}
func TestFieldRowMarshalJSON(t *testing.T) {
fr := FieldRow{
Field: "blah",
@ -222,3 +120,358 @@ func TestFieldRowMarshalJSON(t *testing.T) {
t.Fatalf("unexpected json: %s", b)
}
}
func TestExecutor_GroupCountCondition(t *testing.T) {
t.Run("satisfiesCondition", func(t *testing.T) {
type condCheck struct {
cond string
exp bool
}
tests := []struct {
groupCount GroupCount
checks []condCheck
}{
{
groupCount: GroupCount{Count: 100},
checks: []condCheck{
{cond: "count == 99", exp: false},
{cond: "count != 99", exp: true},
{cond: "count < 99", exp: false},
{cond: "count <= 99", exp: false},
{cond: "count > 99", exp: true},
{cond: "count >= 99", exp: true},
{cond: "count == 100", exp: true},
{cond: "count != 100", exp: false},
{cond: "count < 100", exp: false},
{cond: "count <= 100", exp: true},
{cond: "count > 100", exp: false},
{cond: "count >= 100", exp: true},
{cond: "count == 101", exp: false},
{cond: "count != 101", exp: true},
{cond: "count < 101", exp: true},
{cond: "count <= 101", exp: true},
{cond: "count > 101", exp: false},
{cond: "count >= 101", exp: false},
{cond: "98 < count < 100", exp: false},
{cond: "98 < count <= 100", exp: true},
{cond: "98 < count < 101", exp: true},
{cond: "100 <= count < 102", exp: true},
{cond: "100 < count < 102", exp: false},
{cond: "98 <= count <= 102", exp: true},
},
},
{
groupCount: GroupCount{Agg: 100},
checks: []condCheck{
{cond: "sum == 99", exp: false},
{cond: "sum != 99", exp: true},
{cond: "sum < 99", exp: false},
{cond: "sum <= 99", exp: false},
{cond: "sum > 99", exp: true},
{cond: "sum >= 99", exp: true},
{cond: "sum == 100", exp: true},
{cond: "sum != 100", exp: false},
{cond: "sum < 100", exp: false},
{cond: "sum <= 100", exp: true},
{cond: "sum > 100", exp: false},
{cond: "sum >= 100", exp: true},
{cond: "sum == 101", exp: false},
{cond: "sum != 101", exp: true},
{cond: "sum < 101", exp: true},
{cond: "sum <= 101", exp: true},
{cond: "sum > 101", exp: false},
{cond: "sum >= 101", exp: false},
{cond: "98 < sum < 100", exp: false},
{cond: "98 < sum <= 100", exp: true},
{cond: "98 < sum < 101", exp: true},
{cond: "100 <= sum < 102", exp: true},
{cond: "100 < sum < 102", exp: false},
{cond: "98 <= sum <= 102", exp: true},
},
},
{
groupCount: GroupCount{Agg: -100},
checks: []condCheck{
{cond: "sum == -99", exp: false},
{cond: "sum != -99", exp: true},
{cond: "sum < -99", exp: true},
{cond: "sum <= -99", exp: true},
{cond: "sum > -99", exp: false},
{cond: "sum >= -99", exp: false},
{cond: "sum == -100", exp: true},
{cond: "sum != -100", exp: false},
{cond: "sum < -100", exp: false},
{cond: "sum <= -100", exp: true},
{cond: "sum > -100", exp: false},
{cond: "sum >= -100", exp: true},
{cond: "sum == -101", exp: false},
{cond: "sum != -101", exp: true},
{cond: "sum < -101", exp: false},
{cond: "sum <= -101", exp: false},
{cond: "sum > -101", exp: true},
{cond: "sum >= -101", exp: true},
{cond: "-100 < sum < -98", exp: false},
{cond: "-100 <= sum < -98", exp: true},
{cond: "-101 < sum < -98", exp: true},
{cond: "-102 < sum <= -100", exp: true},
{cond: "-102 < sum < -100", exp: false},
{cond: "-102 <= sum <= -98", exp: true},
},
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("test (#%d):", i), func(t *testing.T) {
for j, check := range test.checks {
t.Run(fmt.Sprintf("check (#%d):", j), func(t *testing.T) {
query, err := pql.ParseString(fmt.Sprintf("GroupBy(Rows(a), having=Condition(%s))", check.cond))
if err != nil {
t.Fatalf("parsing query: %v", err)
}
c := query.Calls[0]
having := c.Args["having"].(*pql.Call)
var got bool
for subj, cond := range having.Args {
switch subj {
case "count", "sum":
condition, ok := cond.(*pql.Condition)
if !ok {
t.Fatalf("not a valid condition")
}
got = test.groupCount.satisfiesCondition(subj, condition)
}
}
if got != check.exp {
t.Fatalf("expected: %v, but got: %v", check.exp, got)
}
})
}
})
}
})
}
func TestValCountComparisons(t *testing.T) {
tests := []struct {
name string
vc ValCount
other ValCount
expLarger ValCount
expSmaller ValCount
}{
{
name: "zero",
},
{
name: "ints",
vc: ValCount{Val: 10, Count: 1},
other: ValCount{Val: 3, Count: 2},
expLarger: ValCount{Val: 10, Count: 1},
expSmaller: ValCount{Val: 3, Count: 2},
},
{
name: "floats",
vc: ValCount{FloatVal: 10.2, Count: 1},
other: ValCount{FloatVal: 3.4, Count: 2},
expLarger: ValCount{FloatVal: 10.2, Count: 1},
expSmaller: ValCount{FloatVal: 3.4, Count: 2},
},
{
name: "intsEquality",
vc: ValCount{Val: 10, Count: 1},
other: ValCount{Val: 10, Count: 2},
expLarger: ValCount{Val: 10, Count: 3},
expSmaller: ValCount{Val: 10, Count: 3},
},
{
name: "floatsEquality",
vc: ValCount{FloatVal: 10.7, Count: 1},
other: ValCount{FloatVal: 10.7, Count: 2},
expLarger: ValCount{FloatVal: 10.7, Count: 3},
expSmaller: ValCount{FloatVal: 10.7, Count: 3},
},
}
for i, test := range tests {
t.Run(test.name+strconv.Itoa(i), func(t *testing.T) {
gotLarger := test.vc.larger(test.other)
if gotLarger != test.expLarger {
t.Fatalf("larger failed, expected:\n%+v\ngot:\n%+v", test.expLarger, gotLarger)
}
gotSmaller := test.vc.smaller(test.other)
if gotSmaller != test.expSmaller {
t.Fatalf("smaller failed, expected:\n%+v\ngot:\n%+v", test.expSmaller, gotSmaller)
}
})
}
}
func TestToNegInt64(t *testing.T) {
tests := []struct {
u64 uint64
i64 int64
overflow bool
}{
{
u64: uint64(1 << 63),
i64: int64(-1 << 63),
},
{
u64: uint64(1<<63) - 1,
i64: int64(-1<<63) + 1,
},
{
u64: uint64(1<<63) + 1,
overflow: true,
},
}
for _, tc := range tests {
val, err := toNegInt64(tc.u64)
if err != nil && !tc.overflow {
t.Fatalf("error: %+v, expected: %+v", err, tc)
}
if val != tc.i64 {
t.Fatalf("Expected: %+v, Got: %+v", tc.i64, val)
}
}
}
func TestToInt64(t *testing.T) {
tests := []struct {
u64 uint64
i64 int64
overflow bool
}{
{
u64: uint64(1<<63) - 1,
i64: 1<<63 - 1,
},
{
u64: uint64(0),
i64: 0,
},
{
u64: uint64(1 << 63),
overflow: true,
},
{
u64: 1<<64 - 1,
overflow: true,
},
}
for _, tc := range tests {
val, err := toInt64(tc.u64)
if err != nil && !tc.overflow {
t.Fatalf("error: %+v, expected: %+v", err, tc)
}
if val != tc.i64 {
t.Fatalf("Expected: %+v, Got: %+v", tc.i64, val)
}
}
}
func TestGetSorter(t *testing.T) {
tests := []struct {
sortSpec string
expGCS *groupCountSorter
expErr string
}{
{
sortSpec: "count asc",
expGCS: &groupCountSorter{fields: []int{-1}, order: []order{asc}},
},
{
sortSpec: "count asc",
expGCS: &groupCountSorter{fields: []int{-1}, order: []order{asc}},
},
{
sortSpec: " count asc",
expGCS: &groupCountSorter{fields: []int{-1}, order: []order{asc}},
},
{
sortSpec: " count asc ",
expGCS: &groupCountSorter{fields: []int{-1}, order: []order{asc}},
},
{
sortSpec: "count",
expGCS: &groupCountSorter{fields: []int{-1}, order: []order{desc}},
},
{
sortSpec: "sum asc",
expGCS: &groupCountSorter{fields: []int{-2}, order: []order{asc}},
},
{
sortSpec: "aggregate asc",
expGCS: &groupCountSorter{fields: []int{-2}, order: []order{asc}},
},
{
sortSpec: "boondoggle asc",
expErr: "sorting is only supported on count, aggregate, or sum, not 'boondoggle'",
},
{
sortSpec: "sum asc, count desc",
expGCS: &groupCountSorter{fields: []int{-2, -1}, order: []order{asc, desc}},
},
{
sortSpec: "count asc, sum desc",
expGCS: &groupCountSorter{fields: []int{-1, -2}, order: []order{asc, desc}},
},
{
sortSpec: " count asc , sum desc ",
expGCS: &groupCountSorter{fields: []int{-1, -2}, order: []order{asc, desc}},
},
{
sortSpec: " count asc , sum desc blah",
expErr: "parsing sort directive: 'sum desc blah': too many elements",
},
{
sortSpec: "count asc, sum fesc",
expErr: "unknown sort direction 'fesc'",
},
{
sortSpec: " , sum fesc",
expErr: "invalid sorting directive: ''",
},
{
// weird and useless, but I guess fine?
sortSpec: "count asc,count asc ",
expGCS: &groupCountSorter{fields: []int{-1, -1}, order: []order{asc, asc}},
},
}
for i, tst := range tests {
t.Run(fmt.Sprintf("%s_%d", tst.sortSpec, i), func(t *testing.T) {
gcs, err := getSorter(tst.sortSpec)
if err != nil {
if tst.expErr == "" {
t.Errorf("unexpected error: %v", err)
return
}
if tst.expErr != err.Error() {
t.Errorf("mismatched errors got: '%v', exp: '%s'", err, tst.expErr)
}
return
}
if !reflect.DeepEqual(gcs, tst.expGCS) {
t.Errorf("exp:\n%+v\ngot:\n%v\n", tst.expGCS, gcs)
}
})
}
}

File diff suppressed because it is too large Load diff

1107
field.go

File diff suppressed because it is too large Load diff

View file

@ -16,15 +16,18 @@ package pilosa
import (
"fmt"
"io/ioutil"
"math"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/pilosa/pilosa/v2/pql"
"github.com/pilosa/pilosa/v2/roaring"
"github.com/pilosa/pilosa/v2/testhook"
)
// Ensure a bsiGroup can adjust to its baseValue.
@ -66,12 +69,12 @@ func TestBSIGroup_BaseValue(t *testing.T) {
{b0, pql.LT, 5, 105, false},
{b0, pql.LT, -8, 92, false},
{b0, pql.LT, -108, -8, false},
{b0, pql.LT, 1005, 1023, false},
{b0, pql.LT, 1005, 1024, false},
{b0, pql.LT, 0, 100, false},
{b1, pql.LT, 5, 5, false},
{b1, pql.LT, -8, -8, false},
{b1, pql.LT, 1005, 255, false},
{b1, pql.LT, 1005, 256, false},
{b1, pql.LT, 0, 0, false},
{b2, pql.LT, 5, -95, false},
@ -89,6 +92,7 @@ func TestBSIGroup_BaseValue(t *testing.T) {
{b1, pql.GT, -8, -8, false},
{b1, pql.GT, 1005, 0, true},
{b1, pql.GT, 0, 0, false},
{b1, pql.GT, -300, -256, false},
{b2, pql.GT, 5, -95, false},
{b2, pql.GT, -8, -108, false},
@ -141,6 +145,7 @@ func TestBSIGroup_BaseValue(t *testing.T) {
{b1, 5, 20, 5, 20, false},
{b1, 20, 1005, 20, 255, false},
{b1, 1005, 2000, 0, 0, true},
{b1, 0, -1, 0, 0, true},
{b2, 5, 95, -95, -5, false},
{b2, 95, 120, -5, 20, false},
@ -158,7 +163,7 @@ func TestBSIGroup_BaseValue(t *testing.T) {
// Ensure field can open and retrieve a view.
func TestField_DeleteView(t *testing.T) {
f := MustOpenField(OptFieldTypeDefault())
f := OpenField(t, OptFieldTypeDefault())
defer f.Close()
viewName := viewStandard + "_v"
@ -192,64 +197,73 @@ func TestField_DeleteView(t *testing.T) {
// TestField represents a test wrapper for Field.
type TestField struct {
*Field
parent *Index
tb testing.TB
}
// NewTestField returns a new instance of TestField d/0.
func NewTestField(opts FieldOption) *TestField {
path, err := ioutil.TempDir(*TempDir, "pilosa-field-")
func NewTestField(t *testing.T, opts FieldOption) *TestField {
path, err := testhook.TempDirInDir(t, *TempDir, "pilosa-field-")
if err != nil {
t.Fatal(err)
}
h := NewHolder(path, nil)
panicOn(h.Open())
idx, err := h.CreateIndex("i", IndexOptions{})
if err != nil {
panic(err)
}
field, err := NewField(path, "i", "f", opts)
field, err := idx.CreateField("f", opts)
if err != nil {
panic(err)
t.Fatal(err)
}
return &TestField{Field: field}
tf := &TestField{Field: field, parent: idx, tb: t}
testhook.Cleanup(t, func() {
h.Close()
})
return tf
}
// MustOpenField returns a new, opened field at a temporary path. Panic on error.
func MustOpenField(opts FieldOption) *TestField {
f := NewTestField(opts)
if err := f.Open(); err != nil {
panic(err)
}
// OpenField returns a new, opened field at a temporary path.
func OpenField(t *testing.T, opts FieldOption) *TestField {
f := NewTestField(t, opts)
return f
}
// Close closes the field and removes the underlying data.
func (f *TestField) Close() error {
if f.idx != nil {
panicOn(f.idx.holder.txf.CloseIndex(f.idx))
}
defer os.RemoveAll(f.Path())
return f.Field.Close()
}
// Reopen closes the index and reopens it.
func (f *TestField) Reopen() error {
var err error
if err := f.Field.Close(); err != nil {
name := f.Field.Name()
if err := f.parent.Close(); err != nil {
f.parent = nil
return err
}
path, index, name := f.Path(), f.Index(), f.Name()
f.Field, err = NewField(path, index, name, OptFieldTypeDefault())
if err != nil {
return err
}
if err := f.Open(); err != nil {
if err := f.parent.Open(); err != nil {
f.parent = nil
return err
}
f.Field = f.parent.Field(name)
return nil
}
func (f *TestField) MustSetBit(row, col uint64, ts ...time.Time) {
func (f *TestField) MustSetBit(tx Tx, row, col uint64, ts ...time.Time) {
if len(ts) == 0 {
_, err := f.Field.SetBit(row, col, nil)
_, err := f.Field.SetBit(tx, row, col, nil)
if err != nil {
panic(err)
}
}
for _, t := range ts {
_, err := f.Field.SetBit(row, col, &t)
_, err := f.Field.SetBit(tx, row, col, &t)
if err != nil {
panic(err)
}
@ -258,7 +272,7 @@ func (f *TestField) MustSetBit(row, col uint64, ts ...time.Time) {
// Ensure field can open and retrieve a view.
func TestField_CreateViewIfNotExists(t *testing.T) {
f := MustOpenField(OptFieldTypeDefault())
f := OpenField(t, OptFieldTypeDefault())
defer f.Close()
// Create view.
@ -283,7 +297,7 @@ func TestField_CreateViewIfNotExists(t *testing.T) {
}
func TestField_SetTimeQuantum(t *testing.T) {
f := MustOpenField(OptFieldTypeTime(TimeQuantum("")))
f := OpenField(t, OptFieldTypeTime(TimeQuantum("")))
defer f.Close()
// Set & retrieve time quantum.
@ -302,44 +316,54 @@ func TestField_SetTimeQuantum(t *testing.T) {
}
func TestField_RowTime(t *testing.T) {
f := MustOpenField(OptFieldTypeTime(TimeQuantum("")))
f := OpenField(t, OptFieldTypeTime(TimeQuantum("")))
defer f.Close()
// Obtain transaction.
tx := f.idx.holder.txf.NewTx(Txo{Write: writable, Index: f.idx, Field: f.Field, Shard: 0})
defer tx.Rollback()
if err := f.setTimeQuantum(TimeQuantum("YMDH")); err != nil {
t.Fatal(err)
}
f.MustSetBit(1, 1, time.Date(2010, time.January, 5, 12, 0, 0, 0, time.UTC))
f.MustSetBit(1, 2, time.Date(2011, time.January, 5, 12, 0, 0, 0, time.UTC))
f.MustSetBit(1, 3, time.Date(2010, time.February, 5, 12, 0, 0, 0, time.UTC))
f.MustSetBit(1, 4, time.Date(2010, time.January, 6, 12, 0, 0, 0, time.UTC))
f.MustSetBit(1, 5, time.Date(2010, time.January, 5, 13, 0, 0, 0, time.UTC))
f.MustSetBit(tx, 1, 1, time.Date(2010, time.January, 5, 12, 0, 0, 0, time.UTC))
f.MustSetBit(tx, 1, 2, time.Date(2011, time.January, 5, 12, 0, 0, 0, time.UTC))
f.MustSetBit(tx, 1, 3, time.Date(2010, time.February, 5, 12, 0, 0, 0, time.UTC))
f.MustSetBit(tx, 1, 4, time.Date(2010, time.January, 6, 12, 0, 0, 0, time.UTC))
f.MustSetBit(tx, 1, 5, time.Date(2010, time.January, 5, 13, 0, 0, 0, time.UTC))
if r, err := f.RowTime(1, time.Date(2010, time.November, 5, 12, 0, 0, 0, time.UTC), "Y"); err != nil {
panicOn(tx.Commit())
// obtain 2nd transaction to read it back.
tx = f.idx.holder.txf.NewTx(Txo{Write: !writable, Index: f.idx, Field: f.Field, Shard: 0})
defer tx.Rollback()
if r, err := f.RowTime(tx, 1, time.Date(2010, time.November, 5, 12, 0, 0, 0, time.UTC), "Y"); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(r.Columns(), []uint64{1, 3, 4, 5}) {
t.Fatalf("wrong columns: %#v", r.Columns())
}
if r, err := f.RowTime(1, time.Date(2010, time.February, 7, 13, 0, 0, 0, time.UTC), "YM"); err != nil {
if r, err := f.RowTime(tx, 1, time.Date(2010, time.February, 7, 13, 0, 0, 0, time.UTC), "YM"); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(r.Columns(), []uint64{3}) {
t.Fatalf("wrong columns: %#v", r.Columns())
}
if r, err := f.RowTime(1, time.Date(2010, time.February, 7, 13, 0, 0, 0, time.UTC), "M"); err != nil {
if r, err := f.RowTime(tx, 1, time.Date(2010, time.February, 7, 13, 0, 0, 0, time.UTC), "M"); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(r.Columns(), []uint64{3}) {
t.Fatalf("wrong columns: %#v", r.Columns())
}
if r, err := f.RowTime(1, time.Date(2010, time.January, 5, 12, 0, 0, 0, time.UTC), "MD"); err != nil {
if r, err := f.RowTime(tx, 1, time.Date(2010, time.January, 5, 12, 0, 0, 0, time.UTC), "MD"); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(r.Columns(), []uint64{1, 5}) {
t.Fatalf("wrong columns: %#v", r.Columns())
}
if r, err := f.RowTime(1, time.Date(2010, time.January, 5, 13, 0, 0, 0, time.UTC), "MDH"); err != nil {
if r, err := f.RowTime(tx, 1, time.Date(2010, time.January, 5, 13, 0, 0, 0, time.UTC), "MDH"); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(r.Columns(), []uint64{5}) {
t.Fatalf("wrong columns: %#v", r.Columns())
@ -348,7 +372,9 @@ func TestField_RowTime(t *testing.T) {
}
func TestField_PersistAvailableShards(t *testing.T) {
f := MustOpenField(OptFieldTypeDefault())
availableShardFileFlushDuration.Set(200 * time.Millisecond) //shorten the default time to force a file write
f := OpenField(t, OptFieldTypeDefault())
defer f.Close()
// bm represents remote available shards.
bm := roaring.NewBitmap(1, 2, 3)
@ -356,6 +382,7 @@ func TestField_PersistAvailableShards(t *testing.T) {
if err := f.AddRemoteAvailableShards(bm); err != nil {
t.Fatal(err)
}
time.Sleep(2 * availableShardFileFlushDuration.Get())
// Reload field and verify that shard data is persisted.
if err := f.Reopen(); err != nil {
@ -366,10 +393,74 @@ func TestField_PersistAvailableShards(t *testing.T) {
}
func TestField_CorruptAvailableShards(t *testing.T) {
availableShardFileFlushDuration.Set(200 * time.Millisecond) //shorten the default time to force a file write
f := OpenField(t, OptFieldTypeDefault())
defer f.Close()
// bm represents remote available shards.
bm := roaring.NewBitmap(1, 2, 3)
if err := f.AddRemoteAvailableShards(bm); err != nil {
t.Fatal(err)
}
time.Sleep(2 * availableShardFileFlushDuration.Get())
path := filepath.Join(f.path, ".available.shards")
avail, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
n, err := avail.Write([]byte{23})
if err != nil || n != 1 {
t.Fatal(err)
}
avail.Close()
// Reload field and verify that shard data is persisted.
if err := f.Reopen(); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(f.remoteAvailableShards.Slice(), []uint64(nil)) {
t.Fatalf("unexpected available shards (reopen). expected: %#v, but got: %#v", []uint64{}, f.remoteAvailableShards.Slice())
}
}
func TestField_TruncatedAvailableShards(t *testing.T) {
availableShardFileFlushDuration.Set(200 * time.Millisecond) //shorten the default time to force a file write
f := OpenField(t, OptFieldTypeDefault())
defer f.Close()
// bm represents remote available shards.
bm := roaring.NewBitmap(1, 2, 3)
if err := f.AddRemoteAvailableShards(bm); err != nil {
t.Fatal(err)
}
time.Sleep(2 * availableShardFileFlushDuration.Get())
path := filepath.Join(f.path, ".available.shards")
avail, err := os.OpenFile(path, os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
avail.Close()
// Reload field and verify that shard data is persisted.
if err := f.Reopen(); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(f.remoteAvailableShards.Slice(), []uint64(nil)) {
t.Fatalf("unexpected available shards (reopen). expected: %#v, but got: %#v", []uint64{}, f.remoteAvailableShards.Slice())
}
}
// Ensure that persisting available shards having a smaller footprint (for example,
// when going from a bitmap to a smaller, RLE representation) succeeds.
func TestField_PersistAvailableShardsFootprint(t *testing.T) {
f := MustOpenField(OptFieldTypeDefault())
availableShardFileFlushDuration.Set(200 * time.Millisecond) //shorten the default time to force a file write
f := OpenField(t, OptFieldTypeDefault())
defer f.Close()
// bm represents remote available shards.
bm := roaring.NewBitmap()
@ -383,12 +474,14 @@ func TestField_PersistAvailableShardsFootprint(t *testing.T) {
if err := f.AddRemoteAvailableShards(bm); err != nil {
t.Fatal(err)
}
time.Sleep(2 * availableShardFileFlushDuration.Get())
// Reload field and verify that shard data is persisted.
if err := f.Reopen(); err != nil {
t.Fatal(err)
} else if !reflect.DeepEqual(f.remoteAvailableShards.Slice(), bm.Slice()) {
t.Fatalf("unexpected available shards (reopen). expected: %v, but got: %v", bm.Slice(), f.remoteAvailableShards.Slice())
t.Fatalf("unexpected available shards (reopen). expected: %v, \n but got: %v", bm.Slice(), f.remoteAvailableShards.Slice())
}
bm1 := roaring.NewBitmap()
@ -438,3 +531,394 @@ func TestBSIGroup_BaseDefaultValue(t *testing.T) {
}
}
}
func TestField_ApplyOptions(t *testing.T) {
for i, tt := range []struct {
opts FieldOptions
expOpts FieldOptions
}{
{
FieldOptions{
Type: FieldTypeSet,
CacheType: CacheTypeNone,
CacheSize: 0,
},
FieldOptions{
Type: FieldTypeSet,
CacheType: CacheTypeNone,
CacheSize: 0,
},
},
} {
fld := &Field{}
fld.options = *applyDefaultOptions(&FieldOptions{})
if err := fld.applyOptions(tt.opts); err != nil {
t.Fatal(err)
}
if fld.options.CacheType != tt.expOpts.CacheType {
t.Fatalf("test %d, unexpected FieldOptions.CacheType value. expected: %s, but got: %s", i, tt.expOpts.CacheType, fld.options.CacheType)
} else if fld.options.CacheSize != tt.expOpts.CacheSize {
t.Fatalf("test %d, unexpected FieldOptions.CacheSize value. expected: %d, but got: %d", i, tt.expOpts.CacheSize, fld.options.CacheSize)
}
}
}
// Ensure that importValue handles requiredDepth correctly.
// This test sets the same column value to 1, then 8, then 1.
// A previous bug was incorrectly determining bitDepth based
// on the values in the import, and not taking existing values
// into consideration. This would cause an import of 1/8/1
// to result in a value of 9 instead of 1.
func TestBSIGroup_importValue(t *testing.T) {
f := OpenField(t, OptFieldTypeInt(-100, 200))
defer f.Close()
qcx := f.idx.holder.txf.NewQcx()
defer qcx.Abort()
options := &ImportOptions{}
for i, tt := range []struct {
columnIDs []uint64
values []int64
checkVal int64
expCols []uint64
}{
{
[]uint64{100},
[]int64{1},
1,
[]uint64{100},
},
{
[]uint64{100},
[]int64{8},
8,
[]uint64{100},
},
{
[]uint64{100},
[]int64{1},
1,
[]uint64{100},
},
} {
if err := f.importValue(qcx, tt.columnIDs, tt.values, options); err != nil {
t.Fatalf("test %d, importing values: %s", i, err.Error())
}
panicOn(qcx.Finish())
qcx.Reset()
if row, err := f.Range(qcx, f.name, pql.EQ, tt.checkVal); err != nil {
t.Fatalf("test %d, getting range: %s", i, err.Error())
} else if !reflect.DeepEqual(row.Columns(), tt.expCols) {
t.Fatalf("test %d, expected columns: %v, but got: %v", i, tt.expCols, row.Columns())
}
panicOn(qcx.Finish())
qcx.Reset()
} // loop
}
func TestIntField_MinMaxForShard(t *testing.T) {
f := OpenField(t, OptFieldTypeInt(-100, 200))
defer f.Close()
qcx := f.idx.holder.txf.NewQcx()
defer qcx.Abort()
options := &ImportOptions{}
for i, test := range []struct {
name string
columnIDs []uint64
values []int64
expMax ValCount
expMin ValCount
}{
{
name: "zero",
columnIDs: []uint64{},
values: []int64{},
},
{
name: "single",
columnIDs: []uint64{1},
values: []int64{10},
expMax: ValCount{Val: 10, Count: 1},
expMin: ValCount{Val: 10, Count: 1},
},
{
name: "twovals",
columnIDs: []uint64{1, 2},
values: []int64{10, 20},
expMax: ValCount{Val: 20, Count: 1},
expMin: ValCount{Val: 10, Count: 1},
},
{
name: "multiplecounts",
columnIDs: []uint64{1, 2, 3, 4, 5},
values: []int64{10, 20, 10, 10, 20},
expMax: ValCount{Val: 20, Count: 2},
expMin: ValCount{Val: 10, Count: 3},
},
{
name: "middlevals",
columnIDs: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
values: []int64{10, 20, 10, 10, 20, 11, 12, 11, 13, 11},
expMax: ValCount{Val: 20, Count: 2},
expMin: ValCount{Val: 10, Count: 3},
},
{
name: "middlevals",
columnIDs: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100000000, 100000001},
values: []int64{10, 20, 10, 10, 20, 11, 12, 11, 13, 11, 44, 1},
expMax: ValCount{Val: 20, Count: 2},
expMin: ValCount{Val: 10, Count: 3},
},
} {
t.Run(test.name+strconv.Itoa(i), func(t *testing.T) {
if err := f.importValue(qcx, test.columnIDs, test.values, options); err != nil {
t.Fatalf("test %d, importing values: %s", i, err.Error())
}
panicOn(qcx.Finish())
qcx.Reset()
shard := uint64(0)
tx := f.idx.holder.txf.NewTx(Txo{Write: !writable, Index: f.idx, Field: f.Field, Shard: shard})
// Rollback below manually, because we are in a loop.
maxvc, err := f.MaxForShard(tx, shard, nil)
if err != nil {
t.Fatalf("getting max for shard: %v", err)
}
if maxvc != test.expMax {
t.Fatalf("max expected:\n%+v\ngot:\n%+v", test.expMax, maxvc)
}
minvc, err := f.MinForShard(tx, shard, nil)
if err != nil {
t.Fatalf("getting min for shard: %v", err)
}
if minvc != test.expMin {
t.Fatalf("min expected:\n%+v\ngot:\n%+v", test.expMin, minvc)
}
tx.Rollback()
})
}
}
// Ensure we get errors when they are expected.
func TestDecimalField_MinMaxBoundaries(t *testing.T) {
th := newTestHolder(t)
for i, test := range []struct {
scale int64
min pql.Decimal
max pql.Decimal
expErr bool
}{
{
scale: 3,
min: pql.NewDecimal(math.MinInt64, 0),
max: pql.NewDecimal(math.MaxInt64, 0),
expErr: true,
},
{
scale: 3,
min: pql.NewDecimal(math.MinInt64, 3),
max: pql.NewDecimal(math.MaxInt64, 3),
expErr: false,
},
{
scale: 3,
min: pql.NewDecimal(44, 0),
max: pql.NewDecimal(88, 0),
expErr: false,
},
{
scale: 3,
min: pql.NewDecimal(-44, 0),
max: pql.NewDecimal(88, 0),
expErr: false,
},
{
scale: 19,
min: pql.NewDecimal(1, 0),
max: pql.NewDecimal(2, 0),
expErr: true,
},
{
scale: 19,
min: pql.NewDecimal(math.MinInt64, 18),
max: pql.NewDecimal(math.MaxInt64, 18),
expErr: true,
},
{
scale: 0,
min: pql.NewDecimal(1, 20),
max: pql.NewDecimal(2, 20),
expErr: true,
},
{
scale: 0,
min: pql.NewDecimal(1, -1),
max: pql.NewDecimal(2, -1),
expErr: false,
},
{
scale: 0,
min: pql.NewDecimal(1, -19),
max: pql.NewDecimal(2, -19),
expErr: true,
},
} {
t.Run("minmax"+strconv.Itoa(i), func(t *testing.T) {
_, err := NewField(th, "no-path", "i", "f", OptFieldTypeDecimal(test.scale, test.min, test.max))
if err != nil && test.expErr {
if !strings.Contains(err.Error(), "is not supported") {
t.Fatal(err)
}
} else if err != nil && !test.expErr {
t.Fatalf("did not expect error, but got: %s", err)
} else if err == nil && test.expErr {
t.Fatal("expected error, but got none")
}
})
}
}
func TestDecimalField_MinMaxForShard(t *testing.T) {
f := OpenField(t, OptFieldTypeDecimal(3))
defer f.Close()
qcx := f.idx.holder.txf.NewQcx()
defer qcx.Abort()
options := &ImportOptions{}
for i, test := range []struct {
name string
columnIDs []uint64
values []float64
expMax ValCount
expMin ValCount
}{
{
name: "zero",
columnIDs: []uint64{},
values: []float64{},
},
{
name: "single",
columnIDs: []uint64{1},
values: []float64{10.1},
expMax: ValCount{DecimalVal: &pql.Decimal{Value: 10100, Scale: 3}, Count: 1},
expMin: ValCount{DecimalVal: &pql.Decimal{Value: 10100, Scale: 3}, Count: 1},
},
{
name: "twovals",
columnIDs: []uint64{1, 2},
values: []float64{10.1, 20.2},
expMax: ValCount{DecimalVal: &pql.Decimal{Value: 20200, Scale: 3}, Count: 1},
expMin: ValCount{DecimalVal: &pql.Decimal{Value: 10100, Scale: 3}, Count: 1},
},
{
name: "multiplecounts",
columnIDs: []uint64{1, 2, 3, 4, 5},
values: []float64{10.1, 20.2, 10.1, 10.1, 20.2},
expMax: ValCount{DecimalVal: &pql.Decimal{Value: 20200, Scale: 3}, Count: 2},
expMin: ValCount{DecimalVal: &pql.Decimal{Value: 10100, Scale: 3}, Count: 3},
},
{
name: "middlevals",
columnIDs: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
values: []float64{10.1, 20.2, 10.1, 10.1, 20.2, 11, 12, 11, 13, 11},
expMax: ValCount{DecimalVal: &pql.Decimal{Value: 20200, Scale: 3}, Count: 2},
expMin: ValCount{DecimalVal: &pql.Decimal{Value: 10100, Scale: 3}, Count: 3},
},
{
name: "another shard",
columnIDs: []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100000000, 100000001},
values: []float64{10.1, 20.2, 10.1, 10.1, 20.2, 11, 12, 11, 13, 11, 44.39, 0.23},
expMax: ValCount{DecimalVal: &pql.Decimal{Value: 20200, Scale: 3}, Count: 2},
expMin: ValCount{DecimalVal: &pql.Decimal{Value: 10100, Scale: 3}, Count: 3},
},
} {
t.Run(test.name+strconv.Itoa(i), func(t *testing.T) {
if err := f.importFloatValue(qcx, test.columnIDs, test.values, options); err != nil {
t.Fatalf("test %d, importing values: %s", i, err.Error())
}
shard := uint64(0)
tx := f.idx.holder.txf.NewTx(Txo{Write: !writable, Index: f.idx, Field: f.Field, Shard: shard})
defer tx.Rollback()
maxvc, err := f.MaxForShard(tx, shard, nil)
if err != nil {
t.Fatalf("getting max for shard: %v", err)
}
if !reflect.DeepEqual(maxvc, test.expMax) {
t.Fatalf("max expected:\n%+v\ngot:\n%+v", test.expMax, maxvc)
}
minvc, err := f.MinForShard(tx, shard, nil)
if err != nil {
t.Fatalf("getting min for shard: %v", err)
}
if !reflect.DeepEqual(minvc, test.expMin) {
t.Fatalf("min expected:\n%+v\ngot:\n%+v", test.expMin, minvc)
}
})
}
}
func TestBSIGroup_TxReopenDB(t *testing.T) {
f := OpenField(t, OptFieldTypeInt(-100, 200))
defer f.Close()
qcx := f.idx.holder.txf.NewQcx()
defer qcx.Abort()
options := &ImportOptions{}
for i, tt := range []struct {
columnIDs []uint64
values []int64
checkVal int64
expCols []uint64
}{
{
[]uint64{100},
[]int64{1},
1,
[]uint64{100},
},
{
[]uint64{100},
[]int64{8},
8,
[]uint64{100},
},
{
[]uint64{100},
[]int64{1},
1,
[]uint64{100},
},
} {
if err := f.importValue(qcx, tt.columnIDs, tt.values, options); err != nil {
t.Fatalf("test %d, importing values: %s", i, err.Error())
}
panicOn(qcx.Finish())
qcx.Reset()
if row, err := f.Range(qcx, f.name, pql.EQ, tt.checkVal); err != nil {
t.Fatalf("test %d, getting range: %s", i, err.Error())
} else if !reflect.DeepEqual(row.Columns(), tt.expCols) {
t.Fatalf("test %d, expected columns: %v, but got: %v", i, tt.expCols, row.Columns())
}
panicOn(qcx.Finish())
qcx.Reset()
} // loop
// the test: can we re-open a BSI fragment under Tx store
_ = f.Reopen()
}

View file

@ -15,7 +15,6 @@
package pilosa_test
import (
"io/ioutil"
"math"
"testing"
@ -23,12 +22,13 @@ import (
"github.com/pilosa/pilosa/v2"
"github.com/pilosa/pilosa/v2/roaring"
"github.com/pilosa/pilosa/v2/test"
"github.com/pilosa/pilosa/v2/testhook"
)
// Ensure a field can set & read a bsiGroup value.
func TestField_SetValue(t *testing.T) {
t.Run("OK", func(t *testing.T) {
idx := test.MustOpenIndex()
idx := test.MustOpenIndex(t)
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
@ -36,15 +36,18 @@ func TestField_SetValue(t *testing.T) {
t.Fatal(err)
}
// It is okay to pass in a nil tx. f.SetValue will lazily instantiate Tx.
var tx pilosa.Tx
// Set value on field.
if changed, err := f.SetValue(100, 21); err != nil {
if changed, err := f.SetValue(tx, 100, 21); err != nil {
t.Fatal(err)
} else if !changed {
t.Fatal("expected change")
}
// Read value.
if value, exists, err := f.Value(100); err != nil {
if value, exists, err := f.Value(tx, 100); err != nil {
t.Fatal(err)
} else if value != 21 {
t.Fatalf("unexpected value: %d", value)
@ -53,7 +56,7 @@ func TestField_SetValue(t *testing.T) {
}
// Setting value should return no change.
if changed, err := f.SetValue(100, 21); err != nil {
if changed, err := f.SetValue(tx, 100, 21); err != nil {
t.Fatal(err)
} else if changed {
t.Fatal("expected no change")
@ -61,7 +64,7 @@ func TestField_SetValue(t *testing.T) {
})
t.Run("Overwrite", func(t *testing.T) {
idx := test.MustOpenIndex()
idx := test.MustOpenIndex(t)
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
@ -69,22 +72,25 @@ func TestField_SetValue(t *testing.T) {
t.Fatal(err)
}
// It is okay to pass in a nil tx. f.SetValue will lazily instantiate Tx.
var tx pilosa.Tx
// Set value.
if changed, err := f.SetValue(100, 21); err != nil {
if changed, err := f.SetValue(tx, 100, 21); err != nil {
t.Fatal(err)
} else if !changed {
t.Fatal("expected change")
}
// Set different value.
if changed, err := f.SetValue(100, 23); err != nil {
if changed, err := f.SetValue(tx, 100, 23); err != nil {
t.Fatal(err)
} else if !changed {
t.Fatal("expected change")
}
// Read value.
if value, exists, err := f.Value(100); err != nil {
if value, exists, err := f.Value(tx, 100); err != nil {
t.Fatal(err)
} else if value != 23 {
t.Fatalf("unexpected value: %d", value)
@ -94,7 +100,7 @@ func TestField_SetValue(t *testing.T) {
})
t.Run("ErrBSIGroupNotFound", func(t *testing.T) {
idx := test.MustOpenIndex()
idx := test.MustOpenIndex(t)
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeDefault())
@ -102,14 +108,17 @@ func TestField_SetValue(t *testing.T) {
t.Fatal(err)
}
// It is okay to pass in a nil tx. f.SetValue will lazily instantiate Tx.
var tx pilosa.Tx
// Set value.
if _, err := f.SetValue(100, 21); err != pilosa.ErrBSIGroupNotFound {
if _, err := f.SetValue(tx, 100, 21); err != pilosa.ErrBSIGroupNotFound {
t.Fatalf("unexpected error: %s", err)
}
})
t.Run("ErrBSIGroupValueTooLow", func(t *testing.T) {
idx := test.MustOpenIndex()
idx := test.MustOpenIndex(t)
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(20, 30))
@ -117,14 +126,17 @@ func TestField_SetValue(t *testing.T) {
t.Fatal(err)
}
// It is okay to pass in a nil tx. f.SetValue will lazily instantiate Tx.
var tx pilosa.Tx
// Set value.
if _, err := f.SetValue(100, 15); err != pilosa.ErrBSIGroupValueTooLow {
if _, err := f.SetValue(tx, 100, 15); err != pilosa.ErrBSIGroupValueTooLow {
t.Fatalf("unexpected error: %s", err)
}
})
t.Run("ErrBSIGroupValueTooHigh", func(t *testing.T) {
idx := test.MustOpenIndex()
idx := test.MustOpenIndex(t)
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(20, 30))
@ -132,19 +144,22 @@ func TestField_SetValue(t *testing.T) {
t.Fatal(err)
}
// It is okay to pass in a nil tx. f.SetValue will lazily instantiate Tx.
var tx pilosa.Tx
// Set value.
if _, err := f.SetValue(100, 31); err != pilosa.ErrBSIGroupValueTooHigh {
if _, err := f.SetValue(tx, 100, 31); err != pilosa.ErrBSIGroupValueTooHigh {
t.Fatalf("unexpected error: %s", err)
}
})
}
func TestField_NameRestriction(t *testing.T) {
path, err := ioutil.TempDir("", "pilosa-field-")
path, err := testhook.TempDir(t, "pilosa-field-")
if err != nil {
panic(err)
}
field, err := pilosa.NewField(path, "i", ".meta", pilosa.OptFieldTypeDefault())
field, err := pilosa.NewField(pilosa.NewHolder(path, nil), path, "i", ".meta", pilosa.OptFieldTypeDefault())
if field != nil {
t.Fatalf("unexpected field name %s", err)
}
@ -158,6 +173,7 @@ func TestField_NameValidation(t *testing.T) {
"under_score",
"abc123",
"trailing_",
"charact2301234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890",
}
invalidFieldNames := []string{
"",
@ -168,30 +184,32 @@ func TestField_NameValidation(t *testing.T) {
"abc def",
"camelCase",
"UPPERCASE",
"a12345678901234567890123456789012345678901234567890123456789012345",
"charact23112345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901",
}
path, err := ioutil.TempDir("", "pilosa-field-")
path, err := testhook.TempDir(t, "pilosa-field-")
if err != nil {
panic(err)
}
for _, name := range validFieldNames {
_, err := pilosa.NewField(path, "i", name, pilosa.OptFieldTypeDefault())
_, err := pilosa.NewField(pilosa.NewHolder(path, nil), path, "i", name, pilosa.OptFieldTypeDefault())
if err != nil {
t.Fatalf("unexpected field name: %s %s", name, err)
}
}
for _, name := range invalidFieldNames {
_, err := pilosa.NewField(path, "i", name, pilosa.OptFieldTypeDefault())
_, err := pilosa.NewField(pilosa.NewHolder(path, nil), path, "i", name, pilosa.OptFieldTypeDefault())
if err == nil {
t.Fatalf("expected error on field name: %s", name)
}
}
}
const includeRemote = false // for calls to Index.AvailableShards(localOnly bool)
// Ensure can update and delete available shards.
func TestField_AvailableShards(t *testing.T) {
idx := test.MustOpenIndex()
idx := test.MustOpenIndex(t)
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeDefault())
@ -199,12 +217,15 @@ func TestField_AvailableShards(t *testing.T) {
t.Fatal(err)
}
// It is okay to pass in a nil tx. f.SetBit will lazily instantiate Tx.
var tx pilosa.Tx
// Set values on shards 0 & 2, and verify.
if _, err := f.SetBit(0, 100, nil); err != nil {
if _, err := f.SetBit(tx, 0, 100, nil); err != nil {
t.Fatal(err)
} else if _, err := f.SetBit(0, ShardWidth*2, nil); err != nil {
} else if _, err := f.SetBit(tx, 0, ShardWidth*2, nil); err != nil {
t.Fatal(err)
} else if diff := cmp.Diff(f.AvailableShards().Slice(), []uint64{0, 2}); diff != "" {
} else if diff := cmp.Diff(f.AvailableShards(includeRemote).Slice(), []uint64{0, 2}); diff != "" {
t.Fatal(diff)
}
@ -212,7 +233,7 @@ func TestField_AvailableShards(t *testing.T) {
if err := f.AddRemoteAvailableShards(roaring.NewBitmap(1, 2, 4)); err != nil {
t.Fatalf("adding remote shards: %v", err)
}
if diff := cmp.Diff(f.AvailableShards().Slice(), []uint64{0, 1, 2, 4}); diff != "" {
if diff := cmp.Diff(f.AvailableShards(includeRemote).Slice(), []uint64{0, 1, 2, 4}); diff != "" {
t.Fatal(diff)
}
@ -223,7 +244,50 @@ func TestField_AvailableShards(t *testing.T) {
t.Fatalf("removing shard %d: %v", i, err)
}
}
if diff := cmp.Diff(f.AvailableShards().Slice(), []uint64{0, 2}); diff != "" {
if diff := cmp.Diff(f.AvailableShards(includeRemote).Slice(), []uint64{0, 2}); diff != "" {
t.Fatal(diff)
}
}
func TestField_ClearValue(t *testing.T) {
t.Run("OK", func(t *testing.T) {
idx := test.MustOpenIndex(t)
defer idx.Close()
f, err := idx.CreateField("f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
if err != nil {
t.Fatal(err)
}
// It is okay to pass in a nil tx. f.SetValue will lazily instantiate Tx.
var tx pilosa.Tx
// Set value on field.
if changed, err := f.SetValue(tx, 100, 21); err != nil {
t.Fatal(err)
} else if !changed {
t.Fatal("expected change")
}
// Read value.
if value, exists, err := f.Value(tx, 100); err != nil {
t.Fatal(err)
} else if value != 21 {
t.Fatalf("unexpected value: %d", value)
} else if !exists {
t.Fatal("expected value to exist")
}
if changed, err := f.ClearValue(tx, 100); err != nil {
t.Fatal(err)
} else if !changed {
t.Fatal(err)
}
// Read value.
if _, exists, err := f.Value(tx, 100); err != nil {
t.Fatal(err)
} else if exists {
t.Fatal("expected value to not exist")
}
})
}

42
filesystem.go Normal file
View file

@ -0,0 +1,42 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"fmt"
"net/http"
)
// Ensure nopFileSystem implements interface.
var _ FileSystem = &nopFileSystem{}
// FileSystem represents an interface for file system for serving the Lattice UI.
type FileSystem interface {
New() (http.FileSystem, error)
}
func init() {
NopFileSystem = &nopFileSystem{}
}
// NopFileSystem represents a FileSystem that returns an error if called.
var NopFileSystem FileSystem
type nopFileSystem struct{}
// New is a no-op implementation of FileSystem New method.
func (n *nopFileSystem) New() (http.FileSystem, error) {
return nil, fmt.Errorf("file system not implemented")
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more