From dc2b9f5b6fe961070945fc3b397135d7328e6ce4 Mon Sep 17 00:00:00 2001 From: Philip O'Toole Date: Wed, 22 Jun 2022 19:30:53 -0400 Subject: [PATCH] Ignore freshness when serving queries on Leader It doesn't make sense to check the last leader-contact time when the node itself is the leader. Any read, when served by the leader, is going to be within the freshness bound. Fixes https://github.com/rqlite/rqlite/issues/1048 --- store/store.go | 4 ++-- store/store_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/store/store.go b/store/store.go index e283ece3..34e31747 100644 --- a/store/store.go +++ b/store/store.go @@ -803,8 +803,8 @@ func (s *Store) Query(qr *command.QueryRequest) ([]*command.QueryRows, error) { return nil, ErrNotLeader } - if qr.Level == command.QueryRequest_QUERY_REQUEST_LEVEL_NONE && qr.Freshness > 0 && - time.Since(s.raft.LastContact()).Nanoseconds() > qr.Freshness { + if s.raft.State() != raft.Leader && qr.Level == command.QueryRequest_QUERY_REQUEST_LEVEL_NONE && + qr.Freshness > 0 && time.Since(s.raft.LastContact()).Nanoseconds() > qr.Freshness { return nil, ErrStaleRead } diff --git a/store/store_test.go b/store/store_test.go index c772e521..4b47a1f0 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -1475,6 +1475,49 @@ func Test_MultiNodeExecuteQuery(t *testing.T) { } } +func Test_SingleNodeExecuteQueryFreshness(t *testing.T) { + s0, ln0 := mustNewStore(true) + defer os.RemoveAll(s0.Path()) + defer ln0.Close() + if err := s0.Open(); err != nil { + t.Fatalf("failed to open single-node store: %s", err.Error()) + } + defer s0.Close(true) + if err := s0.Bootstrap(NewServer(s0.ID(), s0.Addr(), true)); err != nil { + t.Fatalf("failed to bootstrap single-node store: %s", err.Error()) + } + if _, err := s0.WaitForLeader(10 * time.Second); err != nil { + t.Fatalf("Error waiting for leader: %s", err) + } + + er := executeRequestFromStrings([]string{ + `CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)`, + `INSERT INTO foo(id, name) VALUES(1, "fiona")`, + }, false, false) + _, err := s0.Execute(er) + if err != nil { + t.Fatalf("failed to execute on single node: %s", err.Error()) + } + + _, err = s0.WaitForAppliedFSM(5 * time.Second) + if err != nil { + t.Fatalf("failed to wait for fsmIndex: %s", err.Error()) + } + qr := queryRequestFromString("SELECT * FROM foo", false, false) + qr.Level = command.QueryRequest_QUERY_REQUEST_LEVEL_NONE + qr.Freshness = mustParseDuration("100h").Nanoseconds() + r, err := s0.Query(qr) + if err != nil { + t.Fatalf("failed to query leader node: %s", err.Error()) + } + if exp, got := `["id","name"]`, asJSON(r[0].Columns); exp != got { + t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got) + } + if exp, got := `[[1,"fiona"]]`, asJSON(r[0].Values); exp != got { + t.Fatalf("unexpected results for query\nexp: %s\ngot: %s", exp, got) + } +} + func Test_MultiNodeExecuteQueryFreshness(t *testing.T) { s0, ln0 := mustNewStore(true) defer os.RemoveAll(s0.Path())