@@ -619,3 +619,104 @@ func TestQuerierWithChunksStorage(t *testing.T) {
619619 assertServiceMetricsPrefixes (t , Querier , querier )
620620 assertServiceMetricsPrefixes (t , TableManager , tableManager )
621621}
622+
623+ func TestHashCollisionHandling (t * testing.T ) {
624+ s , err := e2e .NewScenario (networkName )
625+ require .NoError (t , err )
626+ defer s .Close ()
627+
628+ require .NoError (t , writeFileToSharedDir (s , cortexSchemaConfigFile , []byte (cortexSchemaConfigYaml )))
629+ flags := mergeFlags (ChunksStorageFlags , map [string ]string {})
630+
631+ // Start dependencies.
632+ dynamo := e2edb .NewDynamoDB ()
633+
634+ consul := e2edb .NewConsul ()
635+ require .NoError (t , s .StartAndWaitReady (consul , dynamo ))
636+
637+ tableManager := e2ecortex .NewTableManager ("table-manager" , ChunksStorageFlags , "" )
638+ require .NoError (t , s .StartAndWaitReady (tableManager ))
639+
640+ // Wait until the first table-manager sync has completed, so that we're
641+ // sure the tables have been created.
642+ require .NoError (t , tableManager .WaitSumMetrics (e2e .Greater (0 ), "cortex_table_manager_sync_success_timestamp_seconds" ))
643+
644+ // Start Cortex components for the write path.
645+ distributor := e2ecortex .NewDistributor ("distributor" , consul .NetworkHTTPEndpoint (), flags , "" )
646+ ingester := e2ecortex .NewIngester ("ingester" , consul .NetworkHTTPEndpoint (), flags , "" )
647+ require .NoError (t , s .StartAndWaitReady (distributor , ingester ))
648+
649+ // Wait until the distributor has updated the ring.
650+ require .NoError (t , distributor .WaitSumMetrics (e2e .Equals (512 ), "cortex_ring_tokens_total" ))
651+
652+ // Push a series for each user to Cortex.
653+ now := time .Now ()
654+
655+ c , err := e2ecortex .NewClient (distributor .HTTPEndpoint (), "" , "" , "" , "user-0" )
656+ require .NoError (t , err )
657+
658+ var series []prompb.TimeSeries
659+ var expectedVector model.Vector
660+ // Generate two series which collide on fingerprints and fast fingerprints.
661+ tsMillis := e2e .TimeToMilliseconds (now )
662+ metric1 := []prompb.Label {
663+ {Name : "A" , Value : "K6sjsNNczPl" },
664+ {Name : labels .MetricName , Value : "fingerprint_collision" },
665+ }
666+ metric2 := []prompb.Label {
667+ {Name : "A" , Value : "cswpLMIZpwt" },
668+ {Name : labels .MetricName , Value : "fingerprint_collision" },
669+ }
670+
671+ series = append (series , prompb.TimeSeries {
672+ Labels : metric1 ,
673+ Samples : []prompb.Sample {
674+ {Value : float64 (0 ), Timestamp : tsMillis },
675+ },
676+ })
677+ expectedVector = append (expectedVector , & model.Sample {
678+ Metric : prompbLabelsToModelMetric (metric1 ),
679+ Value : model .SampleValue (float64 (0 )),
680+ Timestamp : model .Time (tsMillis ),
681+ })
682+ series = append (series , prompb.TimeSeries {
683+ Labels : metric2 ,
684+ Samples : []prompb.Sample {
685+ {Value : float64 (1 ), Timestamp : tsMillis },
686+ },
687+ })
688+ expectedVector = append (expectedVector , & model.Sample {
689+ Metric : prompbLabelsToModelMetric (metric2 ),
690+ Value : model .SampleValue (float64 (1 )),
691+ Timestamp : model .Time (tsMillis ),
692+ })
693+
694+ res , err := c .Push (series )
695+ require .NoError (t , err )
696+ require .Equal (t , 200 , res .StatusCode )
697+
698+ querier := e2ecortex .NewQuerier ("querier" , consul .NetworkHTTPEndpoint (), flags , "" )
699+ require .NoError (t , s .StartAndWaitReady (querier ))
700+
701+ // Wait until the querier has updated the ring.
702+ require .NoError (t , querier .WaitSumMetrics (e2e .Equals (512 ), "cortex_ring_tokens_total" ))
703+
704+ // Query the series.
705+ c , err = e2ecortex .NewClient ("" , querier .HTTPEndpoint (), "" , "" , "user-0" )
706+ require .NoError (t , err )
707+
708+ result , err := c .Query ("fingerprint_collision" , now )
709+ require .NoError (t , err )
710+ require .Equal (t , model .ValVector , result .Type ())
711+ require .Equal (t , expectedVector , result .(model.Vector ))
712+ }
713+
714+ func prompbLabelsToModelMetric (pbLabels []prompb.Label ) model.Metric {
715+ metric := model.Metric {}
716+
717+ for _ , l := range pbLabels {
718+ metric [model .LabelName (l .Name )] = model .LabelValue (l .Value )
719+ }
720+
721+ return metric
722+ }
0 commit comments