Replies: 8 comments 19 replies
-
function number_comparator(a, b)
if type(a) == type(b) then
return a > b
end
if type(a) == 'double' or type(b) == 'double' then -- pseudocode, not real
return double(a) > double(b)
end
-- etc
end also we should define rules for comparing positive ints vs uints |
Beta Was this translation helpful? Give feedback.
-
at the point we CAN prefer signed or unsigned value by space:format.field.type or always prefer unsigned |
Beta Was this translation helpful? Give feedback.
-
I lean towards the first option. It's better not to introduce any behavior changes. Besides, it seems logical that we have an index type matching each possible field type. AFAICS double field type was added for the sake of SQL DOUBLE. Is there any place where SQL relies on the fact that numbers are compared exactly like doubles?
|
Beta Was this translation helpful? Give feedback.
-
Does it mean that 51af059 is going to be reverted? If not, I don't understand why we can't convert 'double' to 'number' without index rebuild: 'double' stores |
Beta Was this translation helpful? Give feedback.
-
I see we have solution-1 with 2 votes, and other solutions - 3 votes. I've updated the document to use the solution-3, because I couldn't find any logical explanation why include ints but exclude decimal from those indexes. @unera and @sergepetrenko, please, have another look at the end of the doc with the chosen solution, and check out the latest comments. @Totktonada and @locker - you've voted for solution-2 and solution-3. I've chosen solution-3. Do you have any objections why solution-2 is better? |
Beta Was this translation helpful? Give feedback.
-
Just a minor: fix the last resolution about making "double an alias for number" - I searched for some time to figure out who should be aliased along with double. |
Beta Was this translation helpful? Give feedback.
-
@locker I realized one thing 😂. What do we do with vinyl Is there any way how at start up we could see the index version or something? So it was created in Tarantool version which had a different sorting order, and we could then abort and tell the user to change the field type to Right now we get this: --
-- Old Tarantool
--
box.cfg{}
s = box.schema.create_space('test', {engine = 'vinyl'})
pk = s:create_index('pk')
sk = s:create_index('sk', {parts = {2, 'double'}, unique = false})
uint64_max = 0xffffffffffffffffULL
for i = 1, 100 do
s:replace{i, uint64_max - i}
end
-- Prints in reversed order actually, sorted by PK.
s:select{}
box.snapshot()
os.exit()
--
-- New Tarantool.
--
box.cfg{}
-- Crashes.
box.space.test.index.sk:select()
And question to @sergepetrenko and @Totktonada - do we even want to somehow prevent users from starting the instance containing a broken index like |
Beta Was this translation helpful? Give feedback.
-
The fixes are merged. |
Beta Was this translation helpful? Give feedback.
-
Reviewers
Tickets
Changelog
v2:
double
index field type has a new preferrable solution.Summary
The document explains solution for a problem that Tarantool is facing at the moment of writing about incorrect sorting and hashing of numbers in indexes. It resulted in unaccessible values in unique/non-unique indexes and duplicates in unique indexes, both memtx and vinyl, both hash and tree index types.
Initially the issue was thought to be small and only about suboptimal MessagePack handling. Like when a number is stored not in the most compact way, or when a positive integer was stored with an
MP_INT
header.But later the problem revealed a larger scale. Here all the known untrivial issues are listed with solutions. Some of them have multiple solutions, unclear which to choose. Hence the RFC. To gain a consensus on those uncertainties.
The issues
✅ Suboptimal MessagePack
Some unsigned numbers can be stored in 9 different ways in MessagePack. Only one of them being the most optimal and taking a single byte of space. All numbers in
[int32_min, uint32_max]
range can be stored in more than one way.The most compact way is obviously better to use, because it takes less space in memory and on disk. However:
[128, int32_max]
can be stored in equal number of bytes asMP_INT
andMP_UINT
. The argument of compactness doesn't work here.The decision was to support the suboptimal MessagePack. All the comparators and hash functions must provide the same results regardless of how numbers are encoded.
✅ Float values in hash index
The same value stored as
double
andfloat
had different hashes. One could argument for this as something intentional, but there is virtually no case when a value stored infloat
wouldn't be also available indouble
type. The difference is the same as storing a number in 5-byteMP_UINT
vs 9-byteMP_UINT
.The decision is to hash all floating numbers as
double
.✅ Hash index uses different hashing algorithm for some field types
Example:
This is broken, because a single
unsigned
field uses one hashing algorithm, and a single field of any other type uses another algorithm.One solution could be that the hash index always uses the same algo for a single field, regardless of its type. But that would complicate the code, and according to the benches isn't super beneficial.
The decision is to remove the templated optimization for hash indexes having up to 3 string/unsigned fields.
✅ Index behaviour depending on field type
There is quite an untrivial bug, affecting both hash and tree indexes in memtx at least. The problem is that some indexes' behaviour depends on their field types.
For example, if an indexed field type is
double
, then all its values are forcefully cast todouble
before comparison/hashing. This was done so people, having a field of typedouble
, could insert there also integers literally asMP_INT
andMP_UINT
, but they would "behave" like doubles. From user's PoV it would look like the numbers were cast todouble
before being saved.That in turn breaks other assumptions. For instance, there is an assumption, that
double
is included intonumber
type completely. One would expect that conversion of adouble
field into anumber
field doesn't need any checks. But it can break the index. Because in anumber
index the comparison/hashing is based entirely on field values, without any lossy casting. A specific buggy example:Initially the unique index contains 2 unique integer numbers. But then the field type is changed to
double
. If Tarantooldouble
is treated as C/C++double
, thenunsigned
->double
field type change shouldn't have worked. Because the different unsigned values stored in the index atm are the samedouble
value.get
, only visible viaselect
/pairs
).Another example:
This is completely nonsense which is hard to explain. But it seems to be related to the same problem.
Another example:
Same value is found or not only depending on the field type. Nothing else changes.
The bug has 3 possible solutions:
double
type work entirely the same as C/C++double
when it comes to indexes. That is, index field type alterunsigned
->double
would cause index rebuild. Besides, if the index is unique, this field alter might fail if some integers get converted to the samedouble
. The conversiondouble
<->number
will do the same. Because it changes the ordering and changes the definition of duplicate values. This solution wouldn't break anything that isn't already broken.double
type work same asnumber
withoutdecimal
. This solution can breakget
s in indexes when people expect integers not fitting thedouble
type to be cast todouble
. For example, when people specifically wantget{uint64_max}
to also find{uint64_max - 1}
,{uint64_max - 2}
and so on. Thus the users intentionally make lookups of value by not matching keys. But that is highly unlikely and looks broken.double
type an alias fornumber
. Same as (2) but even simpler.The decision is to follow solution-3. I.e. make
double
an alias fornumber
. Solution-1 implies, that somebody deliberately wants large close unequal numbers to be equal with some C language-specific precision - can't imagine such a scenario. Solution-2 is weird because why include large integers, but exclude large-precision floating points? What is the logic, a reason?Beta Was this translation helpful? Give feedback.
All reactions