-
Notifications
You must be signed in to change notification settings - Fork 268
transaction: Support file based transaction #1998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+4,135
−14
Merged
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
74c5b70
transaction: Support file based transaction
pingyu 5560ce5
fix build error
pingyu 8db1362
fix CI errors
pingyu 6c02671
Merge branch 'master' into txn-file-cse
pingyu f558577
address comments
pingyu 44fa212
prepareTxnFileCommitTS
pingyu a209cbe
bo
pingyu d50071d
handle binlog
pingyu 26bd55b
undetermined
pingyu 70cee6e
resource group tag
pingyu b15c470
note for skip case
pingyu dd46c19
register metrics
pingyu 06541e7
validate config
pingyu ace38d1
comment for GetMaxStartKey/GetMinEndKey
pingyu 4f653f2
require -> assert
pingyu ad49ae2
Merge branch 'master' into txn-file-cse
pingyu c522a3e
Merge branch 'master' into txn-file-cse
pingyu 707c2cf
fix CI
pingyu 8b878c4
always get resource group tag
pingyu b30dcc8
handle primary not first
pingyu 83ca973
no pipeline txn
pingyu 88763ff
handle shared lock
pingyu 46861ab
add lock_test
pingyu d82de8f
cleanup ctx
pingyu ebab9a9
MaxTxnChunkSizeInParallel
pingyu fdca77b
resource control
pingyu b8b8131
skip valid config
pingyu 0d12389
txn file split region
pingyu 9b6b05c
no txn file for shared lock
pingyu f0a60e9
handle assertion level
pingyu ae6b452
Merge remote-tracking branch 'upstream/master' into txn-file-cse
pingyu fd46635
txn file assertion
pingyu 0bb1ee0
accouting error
pingyu 8cf5a02
rollback key error
pingyu 98432af
http close
pingyu 1825891
discard value
pingyu 77facf4
fix ci
pingyu 3c5576b
Merge remote-tracking branch 'upstream/master' into txn-file-cse
pingyu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,287 @@ | ||
| // Copyright 2021 TiKV Authors | ||
| // | ||
| // 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. | ||
|
|
||
| // NOTE: The code in this file is based on code from the | ||
| // TiDB project, licensed under the Apache License v 2.0 | ||
| // | ||
| // https://github.com/pingcap/tidb/tree/cc5e161ac06827589c4966674597c137cc9e809c/store/tikv/tests/prewrite_test.go | ||
| // | ||
|
|
||
| // Copyright 2020 PingCAP, Inc. | ||
| // | ||
| // 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 tikv_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "sync" | ||
| "sync/atomic" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/pingcap/failpoint" | ||
| "github.com/pingcap/kvproto/pkg/kvrpcpb" | ||
| "github.com/stretchr/testify/require" | ||
| "github.com/tikv/client-go/v2/config" | ||
| "github.com/tikv/client-go/v2/kv" | ||
| "github.com/tikv/client-go/v2/testutils" | ||
| "github.com/tikv/client-go/v2/tikv" | ||
| "github.com/tikv/client-go/v2/tikvrpc" | ||
| "github.com/tikv/client-go/v2/txnkv/transaction" | ||
| ) | ||
|
|
||
| func TestTxnFilePrewriteTxnSize(t *testing.T) { | ||
| require := require.New(t) | ||
| const maxChunkSize = 1024 | ||
|
|
||
| var chunkIDCounter atomic.Uint64 | ||
| srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| if r.Method != http.MethodPost { | ||
| http.Error(w, "method not allowed", http.StatusMethodNotAllowed) | ||
| return | ||
| } | ||
| id := chunkIDCounter.Add(1) | ||
| resp, _ := json.Marshal(map[string]uint64{"chunk_id": id}) | ||
| w.Header().Set("Content-Type", "application/json") | ||
| w.WriteHeader(http.StatusOK) | ||
| _, _ = w.Write(resp) | ||
| })) | ||
| defer srv.Close() | ||
|
|
||
| origCfg := config.GetGlobalConfig() | ||
| newCfg := *origCfg | ||
| newCfg.TiKVClient.TxnChunkWriterAddr = srv.Listener.Addr().String() | ||
| newCfg.TiKVClient.TxnChunkMaxSize = maxChunkSize | ||
| newCfg.TiKVClient.TxnFileMinMutationSize = 1 | ||
| config.StoreGlobalConfig(&newCfg) | ||
| defer config.StoreGlobalConfig(origCfg) | ||
|
|
||
| client, cluster, pdClient, err := testutils.NewMockTiKV("", nil) | ||
| require.Nil(err) | ||
| _, _, regionID := testutils.BootstrapWithSingleStore(cluster) | ||
| store, err := tikv.NewTestTiKVStore(client, pdClient, nil, nil, 0) | ||
| require.Nil(err) | ||
| defer store.Close() | ||
|
|
||
| type capturedPrewrite struct { | ||
| txnFileChunks []uint64 | ||
| txnSize uint64 | ||
| } | ||
| var mu sync.Mutex | ||
| var captured []capturedPrewrite | ||
|
|
||
| hook := func(req *tikvrpc.Request) { | ||
| if req.Type != tikvrpc.CmdPrewrite { | ||
| return | ||
| } | ||
| inner := req.Req.(*kvrpcpb.PrewriteRequest) | ||
| if len(inner.TxnFileChunks) == 0 { | ||
| return | ||
| } | ||
| chunks := make([]uint64, len(inner.TxnFileChunks)) | ||
| copy(chunks, inner.TxnFileChunks) | ||
| mu.Lock() | ||
| captured = append(captured, capturedPrewrite{ | ||
| txnFileChunks: chunks, | ||
| txnSize: inner.TxnSize, | ||
| }) | ||
| mu.Unlock() | ||
| } | ||
|
|
||
| require.Nil(failpoint.Enable("tikvclient/beforeSendReqToRegion", "return")) | ||
| defer failpoint.Disable("tikvclient/beforeSendReqToRegion") | ||
| ctx := context.WithValue(context.Background(), "sendReqToRegionHook", hook) | ||
|
|
||
| commitTxn := func(keys [][]byte) { | ||
| tx, err := store.Begin() | ||
| require.Nil(err) | ||
| txn := transaction.TxnProbe{KVTxn: tx} | ||
|
|
||
| vars := *kv.DefaultVars | ||
| vars.TxnFileMinMutationSize = 1 | ||
| txn.SetVars(&vars) | ||
|
|
||
| for _, key := range keys { | ||
| val := make([]byte, 64) | ||
| require.Nil(txn.Set(key, val)) | ||
| } | ||
|
|
||
| // The mock environment is only used to inspect outgoing txn-file prewrite requests. | ||
| // Commit may fail later because the mock stack does not fully model txn-file follow-up behavior. | ||
| _ = txn.Commit(ctx) | ||
| } | ||
|
|
||
| assertCaptured := func(expectedRequests int, expectedTxnSize uint64) { | ||
| mu.Lock() | ||
| defer mu.Unlock() | ||
| require.GreaterOrEqual(len(captured), expectedRequests) | ||
| for _, c := range captured { | ||
| require.NotEmpty(c.txnFileChunks) | ||
| require.Equal(expectedTxnSize, c.txnSize) | ||
| } | ||
| } | ||
|
|
||
| // Single-region case: exact txn size should match the mutation count. | ||
| commitTxn([][]byte{[]byte("a"), []byte("b"), []byte("c"), []byte("d"), []byte("e")}) | ||
| assertCaptured(1, 5) | ||
|
|
||
| // Split the single region into two. With the default large chunk size, one chunk spans both | ||
| // regions, so each region batch should conservatively reuse the full chunk entry count. | ||
| newRegionID := cluster.AllocID() | ||
| newPeerID := cluster.AllocID() | ||
| cluster.Split(regionID, newRegionID, []byte("m"), []uint64{newPeerID}, newPeerID) | ||
|
|
||
| mu.Lock() | ||
| captured = nil | ||
| mu.Unlock() | ||
|
|
||
| commitTxn([][]byte{[]byte("a"), []byte("b"), []byte("x"), []byte("y"), []byte("z")}) | ||
| assertCaptured(2, 5) | ||
| } | ||
|
|
||
| func TestTxnFilePrewriteTxnSizeAfterRegionRegroup(t *testing.T) { | ||
| require := require.New(t) | ||
| const maxChunkSize = 1024 | ||
|
|
||
| var chunkIDCounter atomic.Uint64 | ||
| srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| if r.Method != http.MethodPost { | ||
| http.Error(w, "method not allowed", http.StatusMethodNotAllowed) | ||
| return | ||
| } | ||
| id := chunkIDCounter.Add(1) | ||
| resp, _ := json.Marshal(map[string]uint64{"chunk_id": id}) | ||
| w.Header().Set("Content-Type", "application/json") | ||
| w.WriteHeader(http.StatusOK) | ||
| _, _ = w.Write(resp) | ||
| })) | ||
| defer srv.Close() | ||
|
|
||
| origCfg := config.GetGlobalConfig() | ||
| newCfg := *origCfg | ||
| newCfg.TiKVClient.TxnChunkWriterAddr = srv.Listener.Addr().String() | ||
| newCfg.TiKVClient.TxnChunkMaxSize = maxChunkSize | ||
| newCfg.TiKVClient.TxnFileMinMutationSize = 1 | ||
| config.StoreGlobalConfig(&newCfg) | ||
| defer config.StoreGlobalConfig(origCfg) | ||
|
|
||
| client, cluster, pdClient, err := testutils.NewMockTiKV("", nil) | ||
| require.Nil(err) | ||
| _, peerID, regionID := testutils.BootstrapWithSingleStore(cluster) | ||
| store, err := tikv.NewTestTiKVStore(client, pdClient, nil, nil, 0) | ||
| require.Nil(err) | ||
| defer store.Close() | ||
|
|
||
| type capturedPrewrite struct { | ||
| txnFileChunks []uint64 | ||
| txnSize uint64 | ||
| isRetry bool | ||
| regionErr bool | ||
| } | ||
| var mu sync.Mutex | ||
| var captured []capturedPrewrite | ||
|
|
||
| hook := func(req *tikvrpc.Request, resp *tikvrpc.Response, sendErr error) { | ||
| if req.Type != tikvrpc.CmdPrewrite { | ||
| return | ||
| } | ||
| inner, ok := req.Req.(*kvrpcpb.PrewriteRequest) | ||
| if !ok || len(inner.TxnFileChunks) == 0 { | ||
| return | ||
| } | ||
| if sendErr != nil { | ||
| return | ||
| } | ||
| chunks := make([]uint64, len(inner.TxnFileChunks)) | ||
| copy(chunks, inner.TxnFileChunks) | ||
| var regionErr bool | ||
| if resp != nil { | ||
| if respRegionErr, err := resp.GetRegionError(); err == nil && respRegionErr != nil { | ||
| regionErr = true | ||
| } | ||
| } | ||
| mu.Lock() | ||
| captured = append(captured, capturedPrewrite{ | ||
| txnFileChunks: chunks, | ||
| txnSize: inner.TxnSize, | ||
| isRetry: req.Context.IsRetryRequest, | ||
| regionErr: regionErr, | ||
| }) | ||
| mu.Unlock() | ||
| } | ||
|
|
||
| require.Nil(failpoint.Enable("tikvclient/mockRetrySendReqToRegion", "1*return(true)->return(false)")) | ||
| defer failpoint.Disable("tikvclient/mockRetrySendReqToRegion") | ||
| require.Nil(failpoint.Enable("tikvclient/invalidCacheAndRetry", "1*off->pause")) | ||
| defer failpoint.Disable("tikvclient/invalidCacheAndRetry") | ||
| require.Nil(failpoint.Enable("tikvclient/afterSendReqToRegion", "return")) | ||
| defer failpoint.Disable("tikvclient/afterSendReqToRegion") | ||
| ctx := context.WithValue(context.Background(), "sendReqToRegionFinishHook", hook) | ||
|
|
||
| tx, err := store.Begin() | ||
| require.Nil(err) | ||
| txn := transaction.TxnProbe{KVTxn: tx} | ||
|
|
||
| vars := *kv.DefaultVars | ||
| vars.TxnFileMinMutationSize = 1 | ||
| txn.SetVars(&vars) | ||
|
|
||
| for _, key := range [][]byte{[]byte("a"), []byte("z")} { | ||
| val := make([]byte, 64) | ||
| require.Nil(txn.Set(key, val)) | ||
| } | ||
|
|
||
| done := make(chan struct{}) | ||
| go func() { | ||
| _ = txn.Commit(ctx) | ||
| close(done) | ||
| }() | ||
|
|
||
| time.Sleep(3 * time.Second) | ||
| cluster.Split(regionID, cluster.AllocID(), []byte("h"), []uint64{peerID}, peerID) | ||
| require.Nil(failpoint.Disable("tikvclient/invalidCacheAndRetry")) | ||
| <-done | ||
|
|
||
| mu.Lock() | ||
| defer mu.Unlock() | ||
| require.GreaterOrEqual(len(captured), 4, "expected initial send, stale-region retry, and regrouped region requests") | ||
| regionErrRetries := 0 | ||
| successfulRetryPrewrites := 0 | ||
| for _, c := range captured { | ||
| require.NotEmpty(c.txnFileChunks) | ||
| require.Equal(uint64(2), c.txnSize) | ||
| if c.isRetry && c.regionErr { | ||
| regionErrRetries++ | ||
| } | ||
| if c.isRetry && !c.regionErr { | ||
| successfulRetryPrewrites++ | ||
| } | ||
| } | ||
| require.GreaterOrEqual(regionErrRetries, 1, "expected a retry-marked txn-file prewrite to hit a region error after the split") | ||
| require.GreaterOrEqual(successfulRetryPrewrites, 2, "expected regrouped retry prewrites to reach both post-split regions") | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should validate the new txn-file config values before accepting them, especially
TxnChunkMaxSize > 0? If this is set to0, the txn-file path can divide by zero when calculating chunk counts or parallelism, so rejecting or normalizing it inValid()would make the failure mode clearer.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 06541e7.