どのように復元しますか
まず、何が起こったのかを把握する必要があります。なぜCockroachDBが落ちたのですか? 理由は異なりますが、いずれにしても、サーバーは起動しなくなったり、リクエストに応答しなくなります。 私の場合、短いグーグルの後、rocksdbのベースは破られました:
E171219 15:50:36.541517 25 util/log/crash_reporting.go:82 a panic has occurred! E171219 15:50:36.734485 74 util/log/crash_reporting.go:82 a panic has occurred! E171219 15:50:37.241298 25 util/log/crash_reporting.go:174 Reported as error 20a3dd770da3404fa573411e2b2ffe09 panic: Corruption: block checksum mismatch [recovered] panic: Corruption: block checksum mismatch goroutine 25 [running]: github.com/cockroachdb/cockroach/pkg/util/stop.(*Stopper).Recover(0xc4206c8500, 0x7fb299f4b180, 0xc4209de120) /go/src/github.com/cockroachdb/cockroach/pkg/util/stop/stopper.go:200 +0xb1 panic(0x1957a00, 0xc4240398a0) /usr/local/go/src/runtime/panic.go:489 +0x2cf github.com/cockroachdb/cockroach/pkg/storage.(*Store).processReady(0xc420223000, 0x103) /go/src/github.com/cockroachdb/cockroach/pkg/storage/store.go:3411 +0x427
RocksDBストレージを復元する
rocksdbデータベースビートがある場合、必要なコマンドはゴキブリバージョン1.1に既に組み込まれており、それを復元します。
$ cockroach debug rocksdb repair
パスがカスタムの場合、データディレクトリへのパスを指定する必要がある場合もあります。
回復後、cockroachdbの再起動を試みることができます。 私の場合、これは役に立ちませんでしたが、エラーは異なっていました:
E171219 13:12:47.618517 1 cli/error.go:68 cockroach server exited with error: cannot verify empty engine for bootstrap: unable to read store ident: store has not been bootstrapped Error: cockroach server exited with error: cannot verify empty engine for bootstrap: unable to read store ident: store has not been bootstrapped
明らかに、設定のどこかで壊れているものがありますが、cockroachdbのストレージ形式については、まだ不足していることを理解できるほど理解していません。 したがって、我々は別の方向に進みます:開発者がブログでこれについて( こことここで )語ったように、この中にKey-Valueストレージがあることを知っており、さらに何を探す必要があるかを大まかに知っています。
RocksDBから直接データを「引き裂く」
キーとレコードの形式はおおよそわかっているので、「壊れた」インスタンスからデータを含むディレクトリを取得し、すべてのキーを調べてそこから直接データを取得しようとします。 このオプションについては1つのホストについて説明しますが、ホストが多数あり、クラスター全体が停止した場合、重複を削除してキーの最新バージョンを判別する方法を学習する必要があります。
もちろん、すべてを外出先で書きます。 最初にgithub.com/tecbot/gorocksdbライブラリを取得しようと決めたところ、起動しましたが、
cockroach_comparator
コンパレーターを知らないというエラーが発生しました。 ゴキブリ自体のソースコードから正しいコンパレータを取得しましたが、何も変わっていません。
私は問題がわからないため、他の方法で行くことにし、cockroachdbのソースから既製のパッケージを取り出しました。パッケージgithub.com/cockroachdb/cockroach/pkg/storage/engineには必要なものがすべて揃っていますKVベースで正しく動作するため。
したがって、データベースを開き、反復処理を開始し、キーの名前を検索しようとします。キーの値には、データベースにあるものを正確に知っている行がいくつかあります。
package main import "github.com/cockroachdb/cockroach/pkg/storage/engine" func main() { db, err := engine.NewRocksDB(engine.RocksDBConfig{ Dir: "/Users/yuriy/tmp/vbambuke", MustExist: true, }, engine.NewRocksDBCache(1000000)) if err != nil { log.Fatalf("Could not open cockroach rocksdb: %v", err.Error()) } db.Iterate( engine.MVCCKey{Timestamp: hlc.MinTimestamp}, engine.MVCCKeyMax, func(kv engine.MVCCKeyValue) (bool, error) { if bytes.Contains([]byte(kv.Value), []byte("safari@apple.com")) { log.Printf("Email key: %s", kv.Key) } return false, nil }, ) }
私はこのようなものを得ました:
Email key: /Table/54/1/158473728194052097/0/1503250869.243064075,0
このキーにはかなりの数のコンポーネントがありますが、私が理解したものは次のとおりです。
0.テーブルは「テーブル」を意味します:)
1.テーブル番号(テーブルは作成順になければなりません)
2.キーのタイプ。 1は通常のレコード、2はインデックスを意味します
3.主キーの値(1,2,3、...)
4.どうやらバージョンがわかりませんか?
5.タイムスタンプ
つまり、すべてのテーブルのすべてのキーとその値を別のファイルに表示し、テーブル番号で分割できます。 その後、テーブルがどのように呼び出されるか(そして、まだあるのでその構造は何ですか?)
レコード形式の解析
cockroachdbバージョン1.0.4からデータを復元したため、以降のバージョンでは詳細が異なる場合があります。 しかし、ここに私が理解できたものがあります:
1.値の最初の6バイトは無視できます。 どうやら、これはデータと他のいくつかのメタ情報のチェックサム、たとえば、null許容フィールドに関するビットです
2.次にデータ自体が来ます。最初の列を除く各列の前には、その型の別のバイトがあります
メッセージテーブルの例(odを使用してバイナリデータの読み取り可能なビューを取得しました):
メッセージテーブルの構造は次のとおりです。
CREATE TABLE messages ( id SERIAL PRIMARY KEY, user_id BIGINT, user_id_to BIGINT, is_out BOOL, message TEXT, ts BIGINT );
$ head -n 2 messages | od -c 0000000 1 / 1 / 0 / 1 5 0 3 2 5 0 8 6 8 0000020 . 7 2 7 5 5 4 8 2 8 , 0 0000040 = 241 E 270 276 \n # 202 200 230 316 0000060 316 ˁ ** 263 004 023 202 200 204 231 374 235 222 264 004 032 0000100 026 031 N ow I usereal 0000120 P ostgre SQL 023 230 277 256 217 0000140 240 320 375 342 ( \n 0000146
このデータを順番に分析してみましょう。
1.ファイルの最初にキーの名前を書きました-フラグメントに
0000000 1 / 1 / 0 / 1 5 0 3 2 5 0 8 6 8 0000020 . 7 2 7 5 5 4 8 2 8 , 0 0000040 =
これはすべて主キーの値を取得する必要があるキーの一部です(キーの形式は上記で説明されています)
2.タイトル。 キーの後の行0000040では、6バイトのヘッダーです。
241 E 270 276 \n #
常に異なりますが、すべてのテーブルで最初の6バイトを単純にスキップする必要がありました。
3.最初のフィールド、user_id。 cockroachdbで出会った数字は、常に標準ライブラリからエンコードされたvarintです。 最初の列はbinary.Varintを使用して読み取ることができます。 次の記事を読む必要があります。
0000040 = 241 E 270 276 \n # ---> 202 200 230 316 0000060 316 ˁ ** 263 004 <---- 023 202 200 204 231 374 235 222 264 004 032
4. 2番目のフィールド、user_id_to。 フィールドの先頭はそのタイプであり、023は数値を意味し、varintとまったく同じように読み取ることができることが判明しました。 このような列をバイト配列から読み取るための適切な関数を作成できます。
func readVarIntFirst(v []byte) ([]byte, int64) { res, ln := binary.Varint(v) if ln <= 0 { panic("could not read varint") } return v[ln:], res } func readVarInt(v []byte) ([]byte, int64) { if v[0] != '\023' { panic("invalid varint prefix") } return readVarIntFirst(v[1:]) }
5.次はブールフィールドです。 少しいじる必要がありましたが、
github.com/cockroachdb/cockroach/pkg/util/encoding
パッケージのすぐに使用できるエンコード機能と呼ばれる機能を使用できることがわかりました
github.com/cockroachdb/cockroach/pkg/util/encoding
この機能は上記のように機能し、パニックではなくエラーを返すだけです。 。 利便性のためにパニックを使用します-ワンタイムユーティリティでエラーを非常にインテリジェントに処理する必要はありません。
6.次はメッセージのテキストです。 テキストフィールドの前にバイト026、次に長さ、次にコンテンツが来ます。 次のようになります。
0000100 026 031 N ow I usereal 0000120 P ostgre SQL 023 230 277 256 217 0000140 240 320 375 342 ( \n
最初のバイトが長さであると考えると、テキスト自体がさらに先に進みます。 値が小さい場合(条件付きで最大100バイト)、これでも機能します。 しかし実際には、長さは別の方法でエンコードされ、長さはエンコードパッケージの関数を使用して読み取ることもできます。
func readStringFirst(v []byte) ([]byte, string) { v, _, ln, err := encoding.DecodeNonsortingUvarint(v) if err != nil { panic("could not decode string length") } return v[ln:], string(v[0:ln]) } func readString(v []byte) ([]byte, string) { if v[0] != '\026' { panic("invalid string prefix") } return readStringFirst(v[1:]) }
7.最後に、通常の番号であるreadVarInt関数を使用して読み取ります。
DATE型の列の読み取り
エンコードパッケージで適切な関数がすぐに見つからなかったため、DATE型の列で苦しみました:)。 即興演奏しなければなりませんでした。 長い間苦しむことはありません。DATE形式は通常の数字(列タイプ023ヒント)であり、次のように表示されます... UNIX TIME形式の秒数を86400(日数の秒数)で割った値。 つまり、日付を読み取るには、読み取り番号に86400を掛けて、これをUNIX時間として扱う必要があります。
v, birthdate := readVarInt(v) ts := time.Unix(birthdate*86400, 0) formatted := fmt.Sprintf("%04d-%02d-%02d", ts.Year(), ts.Month(), ts.Day())
ベースに挿入して戻す
データベースにデータを挿入するために、文字列をエスケープするための単純な関数を個人的に作成しました。
func escape(q string) string { var b bytes.Buffer for _, c := range q { b.WriteRune(c) if c == '\'' { b.WriteRune(c) } } return b.String() }
そして、それを使用してSQLクエリを手動で作成しました。
fmt.Printf( "INSERT INTO messages2(id, user_id, user_id_to, is_out, message, ts) VALUES(%s, %d, %d, %v, '%s', %d);\n", pk, userID, userIDTo, isOut, escape(message), ts, )
ただし、CSVを作成したり、ベースにモデルを使用したり、準備された式を使用したりできます。 -あなたが望むように。 CockroachDBでバイナリデータストレージ形式を解析した後は難しくありません:)。
リンク、結論
最後までスクロールしてくれてありがとう:)。 バックアップを行うことをお勧めします。私のように振る舞わないでください。 しかし、突然CockroachDBからデータを取得する必要がある場合は、この記事を少しお役立てください。 データを失わないでください!
ゴキブリ
私の面白いソーシャルネットワーク
データ回復ユーティリティのソース
YouTubeでの処理(2/3ビデオ)