[{"data":1,"prerenderedAt":1082},["ShallowReactive",2],{"content:\u002Fja\u002Fnotes\u002F65840\u002Fraftlab2a":3,"series:content_ja":364},{"id":4,"title":5,"authorship":6,"body":7,"categories":335,"date":337,"description":338,"draft":339,"extension":340,"image":341,"meta":342,"navigation":344,"path":345,"permalink":346,"published":346,"readingTime":347,"recommend":346,"references":346,"seo":352,"seoDescription":353,"seoTitle":346,"sitemap":354,"stem":355,"tags":356,"type":362,"__hash__":363},"content_ja\u002Fposts\u002Fnotes\u002F65840\u002Fraftlab2a.md","6.5840 Lab 2A：リーダー選出","human-only",{"type":8,"value":9,"toc":323},"minimark",[10,14,18,21,24,27,43,53,56,59,70,73,80,91,94,98,101,111,114,125,128,134,137,143,149,155,159,162,168,171,184,187,193,196,199,205,208,212,219,225,228,234,237,243,250,256,259,265,268,271,274,278,281,287,293,296,302,305,311,317],[11,12,13],"h3",{"id":13},"はじめに",[15,16,17],"p",{},"6.824のLab 2は、Raftアルゴリズムを実装する課題です。後の課題で作る分散KVストアは、このRaftを合意モジュールとして使うので、とても重要な土台になります。",[15,19,20],{},"Lab 2ではRaft全体を4段階に分け、それぞれを小課題として実装します。Lab 2Aで扱うのは基本的なリーダー選出とハートビートだけで、切断などの厳しい状況でも任期の交代と選挙が動くようにします。",[15,22,23],{},"もちろん、4つの小課題の出発点なので、リーダー選出だけでなく全体の処理基盤も作る必要があります。今回も明示的なロックを使わない版を実装します。Raft構造体のmu変数、消してしまいましょう！ ちょっとおかしなテンションになってきました。",[11,25,26],{"id":26},"課題を読む",[15,28,29,30,37,38,42],{},"説明書は",[31,32,36],"a",{"href":33,"rel":34},"http:\u002F\u002Fnil.csail.mit.edu\u002F6.824\u002F2022\u002Flabs\u002Flab-raft.html",[35],"nofollow","MIT 6.824 2022 年春学期 Lab 2A","にあります。Lab 1と違い、今回は参考になる実装がほとんどありません。実装するのは",[39,40,41],"code",{"code":41},"src\u002Fraft\u002Fraft.go","で、Raft構造体もごく基本的な骨組みだけです。",[44,45,51],"pre",{"className":46,"code":48,"language":49,"meta":50},[47],"language-go","type Raft struct {\n    peers []*labrpc.ClientEnd \u002F\u002F RPC end points of all peers\n    persister *Persister \u002F\u002F Object to hold this peer's persisted state\n    me int \u002F\u002F this peer's index into peers[]\n    dead int32 \u002F\u002F set by Kill()\n}\n","go","",[39,52,48],{"__ignoreMap":50},[15,54,55],{},"Raft構造体1つがクラスタ内のサーバー1台を表し、そのサーバーに必要な情報をすべて保持します。",[15,57,58],{},"peersは現在のクラスタ構成に含まれる全サーバーです。ClientEndのCallを呼べば、直接RPCを送れます。meはクラスタ内での自分の一意なインデックスで、ほかのマシンもこの値で識別します。",[15,60,61,62,65,66,69],{},"Lab 2Aの入口は",[39,63,64],{"code":64},"Make()","です。構造体を初期化した後、",[39,67,68],{"code":68},"rf.ticker()","をgoroutineとして起動します。この中で無限ループを回します。厳密には終了フラグを見るループですが、シャットダウン後のことはここでは気にしないので、無限ループと考えて構いません。これをメインのgoroutineとします。",[15,71,72],{},"Lab 2が難しいのは、フレームワーク側の実装が少なく、Raft全体をほぼゼロから作る必要があることです。幸い、論文のFigure 2に全体の実装方針がほぼ示されています。",[15,74,75,76,79],{},"テストは同じディレクトリの",[39,77,78],{"code":78},"test_test.go","にあります。通らない場合はテストの実装を読み、そのシナリオを手がかりにデバッグできます。",[15,81,82,83,86,87,90],{},"Lab 2Dのテストコマンドは",[39,84,85],{"code":85},"go test -run 2A","です。データ競合も同時に検出できる",[39,88,89],{"code":89},"go test -race -run 2A","をおすすめします。",[11,92,93],{"id":93},"実装の考え方",[95,96,97],"h4",{"id":97},"全体の流れ",[15,99,100],{},"明示的なロックを使わないため、最初に処理の流れとgoroutine間の通信を丁寧に設計します。シリーズ最初の課題なので、ここで基盤を整えておくと後の課題にも役立ちます。",[15,102,103,104,106,107,110],{},"まず、",[39,105,68],{"code":68},"をメインのgoroutineと決めます。Raft構造体のフィールドを書き換えられるのはこのgoroutineだけとし、ほかからの変更は禁止します。これでデータ競合を避けられます。したがって",[39,108,109],{"code":109},"ticker()","は、複数のchannelからのメッセージを無限ループで待ち受ける形になります。",[15,112,113],{},"では、どのgoroutineと通信し、どんなchannelが必要でしょうか。選挙で使うRPCはAppendEntriesとRequestVoteの2種類です。受信側では、最初にRPCを受け取るのはメイン以外のgoroutineなので、メインへ渡すchannelが2つ必要です。送信側も、RPCの応答待ちでメインを止めるわけにはいかないため、別のgoroutineで送ります。この2種類のRPCの応答をメインへ返すために、さらに2つのchannelが要ります。",[15,115,116,117,120,121,124],{},"加えて、選挙タイムアウト用とハートビート用の2つのタイマーが必要です。説明書では、",[39,118,119],{"code":119},"time.Sleep()","で一定時間待つ方法が勧められています。ただ、この方法ではカウントダウンを中断できません。そこで、説明書では非推奨の",[39,122,123],{"code":123},"time.Timer","を、反抗して使うことにしました。中断とリセットをしたかったからです。とはいえ、Timerを正しく使うのは本当に難しいです。",[15,126,127],{},"まず、サーバーの役割を示す状態を定義します。",[44,129,132],{"className":130,"code":131,"language":49,"meta":50},[47],"type ServerStatus uint8\n \nconst (\n    Follower  ServerStatus = 0\n    Candidate ServerStatus = 1\n    Leader    ServerStatus = 2\n)\n",[39,133,131],{"__ignoreMap":50},[15,135,136],{},"Raft論文のFigure 2に合わせて基本フィールドを定義し、先ほどのchannelとタイマーも追加します。",[44,138,141],{"className":139,"code":140,"language":49,"meta":50},[47],"type Raft struct {\n    ...\n\n    \u002F\u002F Status\n    Status ServerStatus\n    \u002F\u002F 已提交日志，外部获取管道\n    ApplyCh chan ApplyMsg\n\n    \u002F***** 所有 Server 都包含的持久状态 *****\u002F\n    \u002F\u002F CurrentTerm 机器遇到的最大的任期，启动时初始化为 0，单调递增\n    CurrentTerm int\n    \u002F\u002F VotedFor 当前任期内投票的 Candidate ID，未投票则为 -1\n    VotedFor int\n    \u002F\u002F Logs 日志条目，每个条目都包含了一条状态机指令和 Leader 接收该条目时的任期，index 从 1 开始\n    Logs []*LogEntry\n\n    \u002F***** 所有 Server 都包含的可变状态 *****\u002F\n    \u002F\u002F CommitIndex 已知的最大的即将提交的日志索引，启动时初始化为 0，单调递增\n    CommitIndex uint64\n    \u002F\u002F LastApplied 最大的已提交的日志索引，启动时初始化为 0，单调递增\n    LastApplied uint64\n\n    \u002F******* Leader 包含的可变状态，选举后初始化 *******\u002F\n    \u002F\u002F NextIndex 每台机器下一个要发送的日志条目的索引，初始化为 Leader 最后一个日志索引 +1\n    NextIndex []uint64\n    \u002F\u002F MatchIndex 每台机器已知复制的最高的日志条目，初始化为 0，单调递增\n    MatchIndex []uint64\n\n    \u002F\u002F 定时器\n    electionTimer  *time.Timer\n    heartbeatTimer *time.Timer\n\n    \u002F\u002F 处理 rpc 请求的管道\n    requestVoteChan   chan RequestVoteMsg\n    appendEntriesChan chan AppendEntriesMsg\n}\n",[39,142,140],{"__ignoreMap":50},[15,144,145,146,148],{},"channelとタイマーは、すべて",[39,147,64],{"code":64},"で初期化する必要があります。nilのchannelは読み書きがブロックされるので注意してください。関数が戻る前にメインのgoroutineを起動し、channelのメッセージを待ち受けます。",[44,150,153],{"className":151,"code":152,"language":49,"meta":50},[47],"func (rf *Raft) ticker() {\n    for !rf.killed() {\n        select {\n        case \u003C-rf.electionTimer.C:\n            rf.startElection()\n            resetTimer(rf.electionTimer, RandomizedElectionTimeout())\n        case \u003C-rf.heartbeatTimer.C:\n            rf.broadcastHeartbeat()\n            resetTimer(rf.heartbeatTimer, FixedHeartbeatTimeout())\n        case msg := \u003C-rf.requestVoteChan:\n            rf.handleRequestVote(msg)\n        case msg := \u003C-rf.appendEntriesChan:\n            rf.handleAppendEntries(msg)\n        case msg := \u003C-rf.requestVoteResChan:\n            rf.handleRequestVoteRes(msg)\n        case msg := \u003C-rf.appendEntriesResChan:\n            rf.handleAppendEntriesRes(msg)\n        }\n    }\n}\n",[39,154,152],{"__ignoreMap":50},[95,156,158],{"id":157},"_2つのタイマー","2つのタイマー",[15,160,161],{},"electionTimerは選挙タイムアウト用です。クラスタ全体が同時にタイムアウトしないよう、毎回ランダムな時間で初期化します。ここでは300〜450msにしています。heartbeatTimerはハートビート用で、固定の100msです。",[44,163,166],{"className":164,"code":165,"language":49,"meta":50},[47],"func Make(peers []*labrpc.ClientEnd, me int,\n    persister *Persister, applyCh chan ApplyMsg) *Raft {\n    ...\n    rf.electionTimer = time.NewTimer(RandomizedElectionTimeout())\n    rf.heartbeatTimer = time.NewTimer(FixedHeartbeatTimeout())\n    ...\n}\n\nfunc RandomizedElectionTimeout() time.Duration {\n    rand.Seed(time.Now().UnixNano())\n    return time.Duration(rand.Intn(150)+300) * time.Millisecond\n}\n\nfunc FixedHeartbeatTimeout() time.Duration {\n    return time.Millisecond * 100\n}\n",[39,167,165],{"__ignoreMap":50},[15,169,170],{},"選挙タイマーは主にリーダー以外が使います。リーダーからハートビートを受け取るたびにリセットし、一定時間何も届かなければ選挙を始めます。手順は次のとおりです。",[172,173,174,178,181],"ol",{},[175,176,177],"li",{},"現在の任期を1増やす。",[175,179,180],{},"候補者になり、自分に投票する。",[175,182,183],{},"ほかのマシンにRequestVoteを送る。",[15,185,186],{},"実装はこちらです。",[44,188,191],{"className":189,"code":190,"language":49,"meta":50},[47],"func (rf *Raft) startElection() {\n    if rf.Status == Leader {\n        \u002F\u002F leader 无需发起新选举\n        return\n    }\n    rf.CurrentTerm += 1\n    \u002F\u002F fmt.Printf(\"server %d start election for term %d\\n\", rf.me, rf.CurrentTerm)\n    rf.Status = Candidate\n    rf.VotedFor = rf.me\n    args := RequestVoteArgs{\n        Term:         rf.CurrentTerm,\n        CandidateId:  rf.me,\n        LastLogIndex: len(rf.Logs) - 1,\n    }\n    if len(rf.Logs) != 0 {\n        args.LastLogTerm = rf.Logs[len(rf.Logs)-1].Term\n    }\n    meta := ElectionMeta{\n        term: rf.CurrentTerm,\n        yeas: 1,\n        nays: 0,\n    }\n    for peer := range rf.peers {\n        if peer == rf.me {\n            continue\n        }\n        go rf.sendRequestVoteRoutine(peer, args, &meta)\n    }\n}\n",[39,192,190],{"__ignoreMap":50},[15,194,195],{},"ElectionMetaには、この選挙の任期と賛成・反対票の数を保存します。メインのgoroutineで全マシンの投票を待つことはできないので、マシンごとにgoroutineを立ててRequestVoteを管理します。応答を受け取ったらchannelでメインへ知らせます。また、選挙を始めた後は選挙タイマーをリセットします。",[15,197,198],{},"ハートビートタイマーは主にリーダーが使い、自身の役割を維持します。タイマーが切れるたびにクラスタへハートビートを送り、新たな選挙が始まらないようにします。送信後はこちらのタイマーもリセットします。",[44,200,203],{"className":201,"code":202,"language":49,"meta":50},[47],"func (rf *Raft) broadcastHeartbeat() {\n    if rf.Status != Leader {\n        return\n    }\n    \u002F\u002F fmt.Printf(\"server %d broadcast heartbeat\\n\", rf.me)\n    args := AppendEntriesArgs{\n        Term:     rf.CurrentTerm,\n        LeaderID: rf.me,\n    }\n    for peer := range rf.peers {\n        if peer == rf.me {\n            continue\n        }\n        go rf.sendAppendEntriesRoutine(peer, args)\n    }\n}\n",[39,204,202],{"__ignoreMap":50},[15,206,207],{},"ハートビートにはAppendEntries RPCを流用します。ほかのマシンとのRPC通信は、同様に個別のgoroutineで処理します。",[95,209,211],{"id":210},"requestvoteの処理","RequestVoteの処理",[15,213,214,215,218],{},"選挙タイマーが切れると、リーダー以外のマシンは新たな選挙を始めます。前述の",[39,216,217],{"code":217},"StartElection()","では、各マシンへのRequestVoteを管理するgoroutineを起動しました。その送信関数はこちらです。",[44,220,223],{"className":221,"code":222,"language":49,"meta":50},[47],"\u002F\u002F 发送拉票请求的协程\nfunc (rf *Raft) sendRequestVoteRoutine(peer int, args RequestVoteArgs, electionMeta *ElectionMeta) {\n    reply := RequestVoteReply{}\n    ok := rf.sendRequestVote(peer, &args, &reply)\n    if !ok {\n        return\n    }\n    msg := RequestVoteResMsg{\n        resp: &reply,\n        meta: electionMeta,\n    }\n    rf.requestVoteResChan \u003C- msg\n}\n",[39,224,222],{"__ignoreMap":50},[15,226,227],{},"特別な処理はありません。RPCを送り、結果を包んでchannel経由でメインへ渡すだけです。リクエストと応答の構造体を定義します。",[44,229,232],{"className":230,"code":231,"language":49,"meta":50},[47],"\u002F\u002F 拉票 RPC 请求\ntype RequestVoteArgs struct {\n    \u002F\u002F Term Candidate 的任期\n    Term int\n    \u002F\u002F CandidateId 拉票的 Candidate 的 ID\n    CandidateId int\n    \u002F\u002F LastLogIndex Candidate 最后一条日志序列的索引\n    LastLogIndex int\n    \u002F\u002F LastLogTerm Candidate 最后一条日志序列的任期\n    LastLogTerm int64\n}\n\n\u002F\u002F 拉票 RPC 响应\ntype RequestVoteReply struct {\n    \u002F\u002F Term 当前任期\n    Term int\n    \u002F\u002F VoteGranted true 则拉票成功\n    VoteGranted bool\n}\n\n\u002F\u002F 拉票请求 RPC 发送入口\nfunc (rf *Raft) sendRequestVote(server int, args *RequestVoteArgs, reply *RequestVoteReply) bool {\n    ok := rf.peers[server].Call(\"Raft.RequestVote\", args, reply)\n    return ok\n}\n",[39,233,231],{"__ignoreMap":50},[15,235,236],{},"受信側の入口はRequestVoteメソッドです。RPCを受け取るgoroutineはメインではないので、ここでもchannelを使って投票要求をメインへ渡します。",[44,238,241],{"className":239,"code":240,"language":49,"meta":50},[47],"\u002F********* 拉票请求接收端相关方法 *********\u002F\n\u002F\u002F 拉票请求 RPC 接收入口\nfunc (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {\n    msg := RequestVoteMsg{\n        req: args,\n        ok:  make(chan RequestVoteReply),\n    }\n    rf.requestVoteChan \u003C- msg\n    resp := \u003C-msg.ok\n    *reply = resp\n}\n\n\u002F\u002F 主协程处理拉票请求\nfunc (rf *Raft) handleRequestVote(msg RequestVoteMsg) {\n    req := msg.req\n    if req.Term \u003C rf.CurrentTerm ||\n        (req.Term == rf.CurrentTerm && rf.VotedFor != -1 && rf.VotedFor != req.CandidateId) {\n        msg.ok \u003C- RequestVoteReply{\n            Term:        rf.CurrentTerm,\n            VoteGranted: false,\n        }\n        return\n    }\n    rf.rpcTermCheck(req.Term)\n    rf.VotedFor = req.CandidateId\n    resetTimer(rf.electionTimer, RandomizedElectionTimeout())\n    \u002F\u002F fmt.Printf(\"server %d vote for server %d for term %d\\n\", rf.me, msg.req.CandidateId, req.Term)\n    msg.ok \u003C- RequestVoteReply{\n        Term:        rf.CurrentTerm,\n        VoteGranted: true,\n    }\n}\n",[39,242,240],{"__ignoreMap":50},[15,244,245,246,249],{},"賛成票を投じた場合は、選挙タイマーもリセットします。",[39,247,248],{"code":248},"rpcTermCheck()","は、RPCのリクエストや応答に含まれる任期が自身より大きいかを調べる共通処理です。大きければ任期を更新し、フォロワーになります。",[44,251,254],{"className":252,"code":253,"language":49,"meta":50},[47],"\u002F\u002F 检查 rpc 请求响应中的 term，如果大于自己的则需要更新任期并成为 Follower\nfunc (rf *Raft) rpcTermCheck(msgTerm int) {\n    if rf.CurrentTerm \u003C msgTerm {\n        rf.CurrentTerm = msgTerm\n        rf.Status = Follower\n        rf.VotedFor = -1\n    }\n}\n",[39,255,253],{"__ignoreMap":50},[15,257,258],{},"投票要求を送ったgoroutineは、応答を受け取ると結果をメインへ渡します。メイン側で票を数え、選挙結果を判断します。",[44,260,263],{"className":261,"code":262,"language":49,"meta":50},[47],"\u002F\u002F 主协程处理拉票请求返回结果\nfunc (rf *Raft) handleRequestVoteRes(msg RequestVoteResMsg) {\n    meta := msg.meta\n    if rf.Status != Candidate {\n        return\n    }\n    if rf.CurrentTerm != meta.term {\n        return\n    }\n    if msg.resp.VoteGranted {\n        meta.yeas++\n        if meta.yeas > len(rf.peers)\u002F2 {\n            \u002F\u002F fmt.Printf(\"server %d become leader for term %d\\n\", rf.me, rf.CurrentTerm)\n            rf.Status = Leader\n            resetTimer(rf.heartbeatTimer, FixedHeartbeatTimeout())\n            rf.broadcastHeartbeat()\n        }\n    } else {\n        meta.nays++\n        rf.rpcTermCheck(msg.resp.Term)\n        if meta.nays > len(rf.peers)\u002F2 {\n            \u002F\u002F 反对票超过一半，则该任期选举失败；可以给其他机器投票\n            rf.VotedFor = -1\n        }\n    }\n}\n",[39,264,262],{"__ignoreMap":50},[15,266,267],{},"最初に2つ確認します。現在のサーバーが候補者ではない、または現在の任期と投票の任期が一致しないなら、過去の選挙に対する応答なので、そのまま戻ります。",[15,269,270],{},"賛成票なら、賛成の数が過半数に達したかを確認します。達していれば当選です。候補者はリーダーになり、ハートビートタイマーをリセットして、全マシンへハートビートを送り、リーダーになったことを知らせます。",[15,272,273],{},"反対票の場合には、ちょっとした最適化を入れています。反対が過半数を超えたら、この選挙は失敗と判断できます。この実装では、その任期の投票先を-1に戻し、ほかの候補者へ投票できるようにして、リーダー選出を早めています。",[95,275,277],{"id":276},"appendentriesの処理","AppendEntriesの処理",[15,279,280],{},"この課題では、AppendEntriesはハートビートだけ処理できれば十分です。実際のログ追加はLab 2Bで実装します。",[15,282,283,286],{},[39,284,285],{"code":285},"broadcastHeartbeat()","では、AppendEntriesを使ってクラスタ全体へハートビートを送ります。各マシンへのリクエストは、それぞれのgoroutineで管理します。",[44,288,291],{"className":289,"code":290,"language":49,"meta":50},[47],"\u002F\u002F 发送追加请求的协程\nfunc (rf *Raft) sendAppendEntriesRoutine(peer int, args AppendEntriesArgs) {\n    reply := AppendEntriesReply{}\n    ok := rf.sendAppendEntries(peer, &args, &reply)\n    if !ok {\n        return\n    }\n    rf.appendEntriesResChan \u003C- AppendEntriesResMsg{\n        resp: &reply,\n    }\n}\n",[39,292,290],{"__ignoreMap":50},[15,294,295],{},"RequestVoteと同じく、RPCを送って応答を待ち、channel経由でメインへ渡します。AppendEntriesのリクエストと応答は次の定義です。",[44,297,300],{"className":298,"code":299,"language":49,"meta":50},[47],"\u002F\u002F 追加 RPC 请求\ntype AppendEntriesArgs struct {\n    \u002F\u002F Term Leader 的任期\n    Term int\n    \u002F\u002F LeaderID Follower 可以将客户端请求重定向到 Leader\n    LeaderID int\n    \u002F\u002F PrevLogIndex 新日志条目前一个日志条目的日志索引\n    PrevLogIndex int\n    \u002F\u002F PrevLogTerm 前一个日志条目的任期\n    PrevLogTerm int\n    \u002F\u002F Entries 需要保存的日志条目，心跳包为空\n    Entries []*LogEntry\n    \u002F\u002F LeaderCommit Leader 的 CommitIndex\n    LeaderCommit int\n}\n\n\u002F\u002F 追加 RPC 响应\ntype AppendEntriesReply struct {\n    \u002F\u002F Term Follower 当前任期\n    Term int\n    \u002F\u002F Success Follower 包含 PrevLogIndex 和 PrevLogTerm 的日志条目为 true\n    Success bool\n}\n\n\u002F\u002F 追加请求 RPC 发送入口\nfunc (rf *Raft) sendAppendEntries(server int, args *AppendEntriesArgs, reply *AppendEntriesReply) bool {\n    ok := rf.peers[server].Call(\"Raft.AppendEntries\", args, reply)\n    return ok\n}\n",[39,301,299],{"__ignoreMap":50},[15,303,304],{},"受信側は、AppendEntriesをメインへ渡して処理します。現時点ではハートビートだけなので、フォロワーへの移行、選挙タイマーのリセット、必要に応じた任期の更新を行えば十分です。",[44,306,309],{"className":307,"code":308,"language":49,"meta":50},[47],"\u002F********* 追加请求接收端相关方法 *********\u002F\n\u002F\u002F 追加请求 RPC 接收入口\nfunc (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {\n    msg := AppendEntriesMsg{\n        req: args,\n        ok:  make(chan AppendEntriesReply),\n    }\n    rf.appendEntriesChan \u003C- msg\n    resp := \u003C-msg.ok\n    *reply = resp\n}\n\n\u002F\u002F 主协程处理追加请求\nfunc (rf *Raft) handleAppendEntries(msg AppendEntriesMsg) {\n    rf.Status = Follower\n    resetTimer(rf.electionTimer, RandomizedElectionTimeout())\n    rf.rpcTermCheck(msg.req.Term)\n    msg.ok \u003C- AppendEntriesReply{\n        Term: rf.CurrentTerm,\n    }\n}\n",[39,310,308],{"__ignoreMap":50},[15,312,313,314,316],{},"最後に送信側の応答処理です。任期を確認するだけなので、ここでも",[39,315,248],{"code":248},"を使えます。",[44,318,321],{"className":319,"code":320,"language":49,"meta":50},[47],"\u002F\u002F 主协程处理追加请求返回结果\nfunc (rf *Raft) handleAppendEntriesRes(msg AppendEntriesResMsg) {\n    resp := msg.resp\n    rf.rpcTermCheck(resp.Term)\n}\n",[39,322,320],{"__ignoreMap":50},{"title":50,"searchDepth":324,"depth":324,"links":325},4,[326,328,329],{"id":13,"depth":327,"text":13},3,{"id":26,"depth":327,"text":26},{"id":93,"depth":327,"text":93,"children":330},[331,332,333,334],{"id":97,"depth":324,"text":97},{"id":157,"depth":324,"text":158},{"id":210,"depth":324,"text":211},{"id":276,"depth":324,"text":277},[336],"notes","2022-12-16 02:06:10","Lab 2Aでは、Raftのリーダー選出とハートビートを実装し、さまざまな厳しい条件でも任期の交代や選挙が正常に行われるようにします。実習全体は4段階からなり、この後に実装する分散KVストアの土台になります。明示的なロックを使わない設計によって、Raft構造体を簡潔にします。実習の手引きには必要な背景知識が載っていますが、前の実習と比べると参考資料に頼れる部分はほとんどなく、自力で実装することが重視されます。",false,"md","https:\u002F\u002Fblog-img.774352199.xyz\u002Fc11Uk4.webp",{"slots":343},{},true,"\u002Fnotes\u002F65840\u002Fraftlab2a",null,{"text":348,"minutes":349,"time":350,"words":351},"18 min read",17.515,1050900,3503,{"title":5,"description":338},"MIT 6.5840 Lab 2AでRaftのリーダー選出とハートビートを実装。channelで状態更新を集約し、ランダムなタイマーとRequestVoteなどのRPCを処理します。",{"loc":345},"posts\u002Fnotes\u002F65840\u002Fraftlab2a",[357,358,359,360,361],"MIT 6.5840","Raft","Go","リーダー選出","分散合意","tech","YsznNdZsRDgeqmZqSa4yMKAtq-6WPrrXSsd0EQYemD0",[365,383,400,418,435,454,472,490,508,523,540,556,571,587,604,622,637,654,668,685,703,720,735,752,768,787,804,821,839,853,871,887,903,907,921,938,953,968,983,997,1011,1025,1040,1054,1068],{"categories":366,"date":368,"description":369,"image":370,"path":371,"readingTime":372,"recommend":346,"tags":377,"title":382,"type":362},[367],"daily","2024-09-01 22:03:10","子どもの頃からの胃腸の不調が大学時代には不思議と消えたものの、就職後の夜更かしと飲酒で再発。慢性胃炎との付き合いは一筋縄ではいきませんでした。吐き気や胃のむかつきが日常になり、何度か検査を受けて生活を振り返るうちに、ようやく腰を据えて治療に向き合うようになりました。","https:\u002F\u002Fblog-img.774352199.xyz\u002F0Hr9l5.webp","\u002Fdaily\u002Fanti-chronic-gastritis",{"text":373,"minutes":374,"time":375,"words":376},"7 min read",6.485,389100,1297,[378,379,380,381],"慢性胃炎","受診記録","胃内視鏡検査","健康記録","慢性胃炎の治療記録",{"categories":384,"date":385,"description":386,"image":387,"path":388,"readingTime":389,"recommend":346,"tags":394,"title":399,"type":362},[367],"2022-04-11 00:13:13","『三体』では、劉慈欣が壮大な歴史を通して個人と集団の関係を描いています。西側の民主主義への疑念と、大多数の人々に運命を委ねることへの悲観が、三部作を貫いています。英雄たちが力を尽くしても、その努力は歴史の流れに押し流される。文化大革命から始まる物語に、少数の傑出した人物と平凡な大衆の対立を読み、人間性と社会について考えました。","https:\u002F\u002Fblog-img.774352199.xyz\u002FLTzbFP.webp","\u002Fdaily\u002Fpeople-in-three-body",{"text":390,"minutes":391,"time":392,"words":393},"19 min read",18.495,1109700,3699,[395,396,397,398],"三体","劉慈欣","SF文学","読書ノート","『三体』における「大多数の人々」",{"categories":401,"date":402,"description":403,"image":404,"path":405,"readingTime":406,"recommend":346,"tags":411,"title":417,"type":362},[367],"2024-01-04 02:06:51","2024年の学習目標は、日本語をN2レベルに引き上げること、SICPとTAPLを最後までやり切ること、POSIXを完全に実装したカーネルを作ること、そしてブログのテーマを更新すること。どれも簡単ではありませんが、今年取り組む方向は決まりました。","https:\u002F\u002Fblog-img.774352199.xyz\u002FV3rSQC.webp","\u002Fdaily\u002Fplan2024",{"text":407,"minutes":408,"time":409,"words":410},"1 min read",0.345,20700,69,[412,413,414,415,416],"年間計画","日本語学習","SICP","TAPL","オペレーティングシステム","2024年の学習計画",{"categories":419,"date":420,"description":421,"image":422,"path":423,"readingTime":424,"recommend":346,"tags":429,"title":434,"type":362},[367],"2024-09-28 16:26:00","ByteDanceで働いて3年。変化の速い環境で、時間まで加速したように感じながらも、どうにか心のバランスを保ってきました。杭州のオフィスのベランダから遠くの山を眺めていた入社当初を振り返り、仕事の壁や心境の変化、忙しさの中で自分のペースを探してきた日々を記します。","https:\u002F\u002Fblog-img.774352199.xyz\u002FhnCaht.webp","\u002Fdaily\u002Fwork-for-3-years",{"text":425,"minutes":426,"time":427,"words":428},"20 min read",19.73,1183800,3946,[430,431,432,433],"ByteDance","エンジニアのキャリア","仕事の振り返り","メンタルヘルス","ByteDanceで3年働きながら、ある程度の正気を保つことも不可能ではない",{"categories":436,"date":438,"description":439,"image":440,"path":441,"readingTime":442,"recommend":346,"tags":447,"title":453,"type":362},[437],"fiddling","2025-05-28 22:09:00","ブログをAstroに移行したところ、従来のGoogle Analytics導入方法では性能面が気になるようになりました。headタグにJavaScriptを直接追加してイベントを送ることはできますが、ページの性能に影響します。そこでpartytownでスクリプトをメインスレッドから切り離し、読み込みを妨げないようにしました。サンプルコードに手を加えることでGoogle Analyticsの導入に成功し、性能とアクセス解析を両立できました。","https:\u002F\u002Fblog-img.774352199.xyz\u002FQ0w4RN.webp","\u002Ffiddling\u002Fastro-google-tag-manager",{"text":443,"minutes":444,"time":445,"words":446},"4 min read",3.455,207300,691,[448,449,450,451,452],"Astro","Google Analytics","Google Tag Manager","Partytown","アクセス解析","AstroにGoogle Analyticsを導入する（Tag Manager編）",{"categories":455,"date":456,"description":457,"image":458,"path":459,"readingTime":460,"recommend":465,"tags":466,"title":471,"type":362},[437],"2023-04-08 13:16:36","新しいプログラミング言語の設計は、難しくも面白い作業です。複雑なコンパイラ理論や機能の実装詳細をいったん脇に置き、コードがコンピューターのどこで動くかに注目すると、言語の作り方が見えてきます。RISC-VI命令セットを出発点に、基盤となる構造、階層化されたシステム、仮想マシンのモデルをたどり、言語の本質を考えます。","https:\u002F\u002Fblog-img.774352199.xyz\u002FuO420B.webp","\u002Ffiddling\u002Fchitchat-about-programming-language",{"text":461,"minutes":462,"time":463,"words":464},"28 min read",27.03,1621800,5406,2,[467,468,469,470],"プログラミング言語設計","型システム","コンパイラ設計","RISC-V","プログラミング言語についての雑談",{"categories":473,"date":474,"description":475,"image":476,"path":477,"readingTime":478,"recommend":324,"tags":483,"title":489,"type":362},[437],"2025-04-18 16:43:12","彼女が仕事で北京から上海に引っ越したので、回線の手配も手伝いました。ところが上海電信の500Mは杭州の1000Mより高く、なんとも納得がいきません。そこで上海での透過プロキシ、特定の通信の杭州経由での外部接続、両拠点のLAN相互接続を目指してネットワークを組むことにしました。杭州側はソフトウェアルーターと無線APのシンプルな構成で、普段から自宅経由で通信できる環境があり、次の挑戦に向けた準備はできています。","https:\u002F\u002Fblog-img.774352199.xyz\u002FO6cAGh.webp","\u002Ffiddling\u002Fcross-city-network-setup",{"text":479,"minutes":480,"time":481,"words":482},"11 min read",10.14,608400,2028,[484,485,486,487,488],"Tailscale","mihomo","拠点間ネットワーク","透過プロキシ","ホームネットワーク","上海と杭州の自宅ネットワークをつなぐ",{"categories":491,"date":492,"description":493,"image":494,"path":495,"readingTime":496,"recommend":346,"tags":501,"title":507,"type":362},[437],"2021-12-27 00:09:00","CSAPPの学習では実験が欠かせませんが、Linux環境の構築が壁になりがちです。とくに仮想マシンでは、インストール失敗、互換性、ネットワーク接続などの問題に悩まされます。Windows 10バージョン2004以降なら、WSL（Windows Subsystem for Linux）が有力な選択肢です。従来の仮想マシンの複雑さや性能面の負担を避けながら、Linux環境を手軽に利用できます。","https:\u002F\u002Fblog-img.774352199.xyz\u002FvqOC7N.webp","\u002Ffiddling\u002Fcsapplab0",{"text":497,"minutes":498,"time":499,"words":500},"10 min read",9.585,575100,1917,[502,503,504,505,506],"CSAPP","WSL2","Linux","GDB","実験環境","CSAPP Labの環境構築",{"categories":509,"date":510,"description":511,"image":512,"path":513,"readingTime":514,"recommend":346,"tags":518,"title":522,"type":362},[437],"2024-07-13 17:49:00","Debianをサイドルーターに使うと、OpenWrtやLuCIに依存せず、より安定した柔軟な構成を作れます。Debianを直接設定することでシステムを細かく制御でき、GUIの制約や不安定さを避けられます。一般的なサイドルーター構成と比べて透過プロキシを安定して運用しやすく、性能や効率を重視する場合の選択肢になります。","https:\u002F\u002Fblog-img.774352199.xyz\u002FpPRU5x.webp","\u002Ffiddling\u002Fdebian-as-bypass-router",{"text":348,"minutes":515,"time":516,"words":517},17.525,1051500,3505,[519,520,485,521,487],"Debian","サイドルーター","AdGuard Home","Debianをサイドルーターにする",{"categories":524,"date":525,"description":526,"image":527,"path":528,"readingTime":529,"recommend":346,"tags":534,"title":539,"type":362},[437],"2024-08-16 23:53:00","FakeIPを使った透過プロキシ構成で、従来のサイドルーターの単一障害点、性能不足、複雑なポート転送を改善します。プロキシコアをsing-boxに変えることで、転送性能を高めながら設定も簡単にできます。対応プロトコルが豊富で、従来のClashより最適化も進んでいます。同じ構成はClashでも実現できますが、sing-boxは柔軟な選択肢になります。","https:\u002F\u002Fblog-img.774352199.xyz\u002FS2HHD5.webp","\u002Ffiddling\u002Ffake-ip-based-transparent-proxy",{"text":530,"minutes":531,"time":532,"words":533},"14 min read",13.1,786000,2620,[535,536,487,537,538],"FakeIP","sing-box","ポリシーベースルーティング","トラフィック振り分け","FakeIPを使った透過プロキシの振り分け",{"categories":541,"date":542,"description":543,"image":544,"path":545,"readingTime":546,"recommend":346,"tags":551,"title":555,"type":362},[437],"2024-08-15 23:50:00","サイドルーターを導入すると、メインルーターのポート転送が効かなくなることがあります。ゲートウェイをサイドルーターに変えることで通信経路が変わり、メインルーターを前提とした転送が正常に動かなくなるためです。ゲートウェイはアドレスを変換して内部ネットワークの通信を外部へ転送し、各端末は外部との通信にこれを必要とします。この仕組みを理解すると、ポート転送の問題を解決しやすくなります。","https:\u002F\u002Fblog-img.774352199.xyz\u002FmRqws9.webp","\u002Ffiddling\u002Ffix-port-forward-in-bypass-router",{"text":547,"minutes":548,"time":549,"words":550},"6 min read",5.155,309300,1031,[520,552,553,554],"ポート転送","NAT","ネットワークのトラブルシューティング","サイドルーター環境でポート転送が効かない問題を解決する",{"categories":557,"date":558,"description":559,"image":560,"path":561,"readingTime":562,"recommend":346,"tags":567,"title":570,"type":362},[437],"2023-02-02 23:24:55","春節の帰省列車で、Goをベアメタルで動かす記事を読み、低レベルのシステムインターフェースに興味が湧きました。実際に動く実装を見て、高級言語でOSを作る可能性に胸が躍ります。関連研究を調べると先行例も見つかり、積もった熱意は期待に満ちたプロジェクトへと変わりました。しかし、最後は思いどおりにはいきませんでした。","https:\u002F\u002Fblog-img.774352199.xyz\u002FxB1Ni5.webp","\u002Ffiddling\u002Fgo-os",{"text":563,"minutes":564,"time":565,"words":566},"12 min read",11.81,708600,2362,[359,470,416,568,569],"ベアメタルプログラミング","ランタイム","失敗に終わったプロジェクト：楽しくなかった春節の7日間",{"categories":572,"date":573,"description":574,"image":575,"path":576,"readingTime":577,"recommend":346,"tags":582,"title":586,"type":362},[437],"2022-08-15 01:05:01","システムのリファクタリングで、階層ごとのエンティティ変換に苦労しました。ビュー層の商品VO、ドメイン層のentity、永続化層のPOは似ていても、型のわずかな違いで直接変換できず、ディープコピーが面倒になります。そこでリフレクションを使った汎用的な変換処理を実装し、大量のassemblerメソッドを減らして、保守性と柔軟性を高めることにしました。","https:\u002F\u002Fblog-img.774352199.xyz\u002FBlhm0I.webp","\u002Ffiddling\u002Fgolang-deepcopy-between-different-type",{"text":578,"minutes":579,"time":580,"words":581},"5 min read",4.14,248400,828,[359,583,584,585],"リフレクション","ディープコピー","構造体変換","Goで異なる型の構造体をディープコピーする",{"categories":588,"date":589,"description":590,"image":591,"path":592,"readingTime":593,"recommend":346,"tags":597,"title":603,"type":362},[437],"2025-03-31 23:51:00","Appleヘルスケアの心拍数を定期的にサーバーへ同期し、Grafanaで可視化して直感的に確認できるようにしました。Health Auto ExportのREST API機能でHTTPエンドポイントへデータを送り、InfluxDBに保存します。Grafanaのダッシュボードから、自分の心拍数の変化を追ったり分析したりできます。","https:\u002F\u002Fblog-img.774352199.xyz\u002FF4qD2T.webp","\u002Ffiddling\u002Fheart-rate-to-grafana",{"text":443,"minutes":594,"time":595,"words":596},3.76,225600,752,[598,599,600,601,602],"Apple Watch","Health Auto Export","InfluxDB","Grafana","心拍数モニタリング","My Heart Beats for U：心拍数をGrafanaで可視化する",{"categories":605,"date":606,"description":607,"image":608,"path":609,"readingTime":610,"recommend":614,"tags":615,"title":621,"type":362},[437],"2025-06-10 20:18:00","macOSとiPadOS 26では、新しいデザイン言語Liquid Glassが導入され、アイコンやウインドウの印象が一新されました。とくにiPadのアプリをウインドウで扱えるようになったことは、生産性向上のための端末へ進む大きな一歩です。一方、透明なコントロールセンターやLaunchpadの統合には賛否があり、使い勝手には改善の余地もあります。課題はありつつも、今後につながる更新だと感じました。","https:\u002F\u002Fblog-img.774352199.xyz\u002FTPSaLE.webp","\u002Ffiddling\u002Fmacos-26-trial",{"text":373,"minutes":611,"time":612,"words":613},6.94,416400,1388,5,[616,617,618,619,620],"macOS 26","iPadOS 26","Liquid Glass","Apple Intelligence","OS使用感","macOS 26とiPadOS 26を試してみた",{"categories":623,"date":624,"description":625,"image":626,"path":627,"readingTime":628,"recommend":346,"tags":632,"title":636,"type":362},[437],"2026-06-11 10:00:00","今年もWWDCでmacOS 27の新しいApple Intelligenceが登場しました。macOS 26時代の回避方法はもう使えず、引き続きAppleとの知恵比べです。","https:\u002F\u002Fblog-img.774352199.xyz\u002FfOFucm.webp","\u002Ffiddling\u002Fmacos-27-apple-intelligence-chatgpt",{"text":497,"minutes":629,"time":630,"words":631},9.74,584400,1948,[633,619,634,635],"macOS 27","ChatGPT","中国本土向けMac","中国本土向けMacでApple Intelligenceを使う：地域変更からChatGPTの取り出しまで",{"categories":638,"date":639,"description":640,"image":641,"path":642,"readingTime":643,"recommend":346,"tags":647,"title":653,"type":362},[437],"2025-07-20 23:32:00","MoonTVはNext.jsとReactで作った動画集約プラットフォームで、連続ドラマなどを手軽に楽しむためのものです。LibreTVを改善しようとしたことが始まりで、数か月の開発を経て多くの関心と利用を集めています。Cursorのおかげで効率よく開発できた一方、複数環境への対応や複雑なデータ依存関係には苦労しました。ユーザーが増える中、フィードバックを受けて改善を続けています。","https:\u002F\u002Fblog-img.774352199.xyz\u002FnIeONi.webp","\u002Ffiddling\u002Fmoontv-vibe-coding",{"text":479,"minutes":644,"time":645,"words":646},10.98,658800,2196,[648,649,650,651,652],"MoonTV","LunaTV","Vibe Coding","Cursor","AI支援開発","MoonTV：Vibe Codingを試してみた",{"categories":655,"date":656,"description":657,"image":658,"path":659,"readingTime":660,"recommend":346,"tags":664,"title":667,"type":362},[437],"2024-10-07 16:51:00","BGPをもとに中国国内・国外のIPを振り分けることで、透過プロキシの効率と精度を高めます。国外の宛先をFakeIPで識別すれば、メインルーターでより適切に通信を振り分けられます。sing-boxのDNS設定も見直し、DNSクエリを柔軟かつ効率的に処理して、ネットワーク全体の使い勝手を改善します。","https:\u002F\u002Fblog-img.774352199.xyz\u002FMOmM1s.webp","\u002Ffiddling\u002Fmore-accurate-chnroute",{"text":547,"minutes":661,"time":662,"words":663},5.75,345000,1150,[665,666,537,538],"BGP","IPアドレスデータベース","BGPで中国国内・国外IPの振り分けをより正確にする",{"categories":669,"date":670,"description":671,"image":672,"path":673,"readingTime":674,"recommend":346,"tags":679,"title":684,"type":362},[437],"2025-12-14 14:31:00","友人の一言からシミュレーターに興味が湧き、Microsoft Flight Simulatorを購入。地図やモデルはリアルタイム配信、アカウントは再ログインが必要、チュートリアルは見つけにくく、キーボード操作も難しい。結局フライトスティックを買い、Pico VRまで設定してようやく一通り体験できました。","https:\u002F\u002Fblog-img.774352199.xyz\u002FlfhEuE.webp","\u002Ffiddling\u002Fmsfs2024-joystick-and-pico",{"text":675,"minutes":676,"time":677,"words":678},"9 min read",8.96,537600,1792,[680,681,682,683],"Microsoft Flight Simulator 2024","Thrustmaster TCA","PICO VR","フライトシミュレーション","Microsoft Flight Simulator 2024をあれこれ試す",{"categories":686,"date":687,"description":688,"image":689,"path":690,"readingTime":691,"recommend":346,"tags":696,"title":702,"type":362},[437],"2025-06-05 23:26:00","OnePlusからiPhoneへと何度も機種変更するうち、いじる楽しさからエコシステムへの依存へと関心が変わりました。最近、彼女の一言をきっかけに写真を改善しようとOPPO Find X8 Ultraを購入。Apple環境からの移行では、Androidアプリの質のばらつきと代替探しの難しさを改めて感じました。1か月をかけて、異なるプラットフォームの摩擦に折り合いをつけた記録です。","https:\u002F\u002Fblog-img.774352199.xyz\u002F19NIhZ.webp","\u002Ffiddling\u002Fone-month-using-android",{"text":692,"minutes":693,"time":694,"words":695},"15 min read",14.05,843000,2810,[697,698,699,700,701],"Android","OPPO","iOS","スマートフォン使用感","エコシステム移行","Androidに乗り換えて1か月",{"categories":704,"date":705,"description":706,"image":707,"path":708,"readingTime":709,"recommend":346,"tags":713,"title":719,"type":362},[437],"2026-05-11 22:43:00","GitHub Trendingで旅行計画ツールを見つけ、自分でも動かしたくなりました。どうせ新しいVPSを借りるなら、ついでにもう少し、いや、だいぶ色々入れてしまおうと。","https:\u002F\u002Fblog-img.774352199.xyz\u002FBBdDWW.webp","\u002Ffiddling\u002Fone-trek-twenty-stacks",{"text":479,"minutes":710,"time":711,"words":712},10.34,620400,2068,[714,715,716,717,718],"TREK","Docker Compose","Dockge","VPS","セルフホスティング","TREKひとつのつもりが、20個のスタックに",{"categories":721,"date":722,"description":723,"image":724,"path":725,"readingTime":726,"recommend":346,"tags":730,"title":734,"type":362},[437],"2025-01-16 23:09:00","OPNsenseは、美しい画面と充実した機能を備えるオープンソースのファイアウォール・ルーターです。いくつかの構成を試した末に、透過プロキシやトラフィック振り分けでの可能性に気づきました。BGPベースの振り分けと組み合わせることで安全性と安定性を高められ、IPリストの自動更新で管理もしやすくなります。","https:\u002F\u002Fblog-img.774352199.xyz\u002FxA8C1E.webp","\u002Ffiddling\u002Fopnsense-transparent-proxy",{"text":563,"minutes":727,"time":728,"words":729},11.38,682800,2276,[731,732,485,733,487,538],"OPNsense","FreeBSD","tun2socks","OPNsenseで透過プロキシとトラフィック振り分けを実現する",{"categories":736,"date":737,"description":738,"image":739,"path":740,"readingTime":741,"recommend":327,"tags":745,"title":751,"type":362},[437],"2025-03-15 20:35:00","構文解析では、ユーザー定義型名と通常の変数名を見分けることが課題になります。たとえば`a*b;`は算術式とも宣言とも解釈できます。とくに型指定子に関する文法規則によって変数名が型名と解釈され、正確さや読みやすさに影響します。初期化を伴わない変数宣言は多いため、この曖昧さはソースコードの随所に現れます。","https:\u002F\u002Fblog-img.774352199.xyz\u002F2VKHK9.webp","\u002Ffiddling\u002Fparser-type-variable-ambiguity",{"text":530,"minutes":742,"time":743,"words":744},13.58,814800,2716,[746,747,748,749,750],"構文解析","GLR","シンボルテーブル","スコープ","曖昧性解消","構文解析で型名と変数名の曖昧さを解消する",{"categories":753,"date":754,"description":755,"image":756,"path":757,"readingTime":758,"recommend":346,"tags":762,"title":767,"type":362},[437],"2023-05-24 17:51:09","RISC-Vツールチェーンをインストールするには、まずriscv-gnu-toolchainのソースを取得します。clone時に`--depth=1`を付けるとダウンロード量を減らせます。READMEのPrerequisitesを確認し、必要な依存関係をそろえてから進めます。Debianではコマンドで依存パッケージをまとめて導入できます。","https:\u002F\u002Fblog-img.774352199.xyz\u002FrWNOKx.webp","\u002Ffiddling\u002Fspike-install",{"text":443,"minutes":759,"time":760,"words":761},3.89,233400,778,[470,763,764,765,766],"Spike","riscv-pk","クロスコンパイル","ツールチェーン","RISC-Vツールチェーンとエミュレーターのインストール",{"categories":769,"date":770,"description":771,"image":772,"path":773,"readingTime":774,"recommend":778,"tags":779,"title":786,"type":362},[437],"2026-09-09 23:59:00","これも一種のNTRですよね","https:\u002F\u002Fblog-img.774352199.xyz\u002FNDPUwc.webp","\u002Ffiddling\u002Fsteamdeck-switch-60fps",{"text":530,"minutes":775,"time":776,"words":777},13.905,834300,2781,7,[780,781,782,783,784,785],"Steam Deck","Nintendo Switch","EmuDeck","Eden","Lossless Scaling","フレーム生成","Steam Deckの第二の人生：Switchエミュレーター導入からフレーム補間で60FPSまで",{"categories":788,"date":789,"description":790,"image":791,"path":792,"readingTime":793,"recommend":346,"tags":798,"title":803,"type":362},[437],"2024-06-23 15:31:32","GFWは単に出口のゲートウェイを監視するのではなく、通信経路の脇から国際通信を傍受します。出入国するIPパケットをクラスタへ複製し、詳しく分析・フィルタリングする仕組みです。どこで何をしているかを理解することは、検閲回避の検討に欠かせません。ネットワーク構成を調べることで、遮断の経路や技術、対処方法が見えてきます。","https:\u002F\u002Fblog-img.774352199.xyz\u002FsOpJuL.webp","\u002Ffiddling\u002Ftech-about-gfw",{"text":794,"minutes":795,"time":796,"words":797},"25 min read",24.975,1498500,4995,[799,800,801,802],"GFW","DNSポイズニング","TCP","インターネット検閲","GFWの仕組みを探る",{"categories":805,"date":806,"description":807,"image":808,"path":809,"readingTime":810,"recommend":346,"tags":814,"title":820,"type":362},[437],"2022-04-16 00:01:28","Javaでは`this`を使うことで、コンパイラの定数最適化が効かなくなる場合があります。このコード例の`ab1`と`ab2`は同じstatic final変数`s`を参照しているように見えますが、比較結果は異なります。静的変数を直接参照して文字列を連結する`ab1`に対し、`this`経由の`ab2`では同じ定数伝播の最適化が行われません。わずかな構文の違いが、コンパイル結果を変える例です。","https:\u002F\u002Fblog-img.774352199.xyz\u002FgKtkYe.webp","\u002Ffiddling\u002Fthis-in-javac-string-concat",{"text":443,"minutes":811,"time":812,"words":813},3.88,232800,776,[815,816,817,818,819],"Java","javac","文字列連結","定数伝播","バイトコード","Javaのthisキーワードでコンパイル時の定数伝播が効かなくなる問題",{"categories":822,"date":823,"description":824,"image":825,"path":826,"readingTime":827,"recommend":346,"tags":832,"title":838,"type":362},[437],"2025-01-29 21:58:00","ひとこと投稿機能があれば、静的ブログの記事公開に伴う手間や心理的な負担を減らし、短い思いつきをいつでも共有できます。Cloudflare Workersでバックエンドを実装し、KVで投稿を保存・管理します。フロントエンドはVitePressにVueコンポーネントを埋め込み、投稿を手軽に表示してブログに動きと交流の場を加えます。","https:\u002F\u002Fblog-img.774352199.xyz\u002FhZX6hr.webp","\u002Ffiddling\u002Fvitepress-memos-component",{"text":828,"minutes":829,"time":830,"words":831},"24 min read",23.345,1400700,4669,[833,834,835,836,837],"VitePress","Vue","Cloudflare Workers","Cloudflare KV","マイクロブログ","VitePressにひとこと投稿機能を実装する",{"categories":840,"date":841,"description":842,"image":843,"path":844,"readingTime":845,"recommend":346,"tags":849,"title":852,"type":362},[437],"2025-03-15 16:24:00","ある平凡な午後、Telegramの通知で魅力的なVPSプランを見つけました。中国電信CN2直結、帯域2.5Gという好条件です。IPv6付きで動画配信サービスの地域制限解除にも便利ですが、すべての通信をWARP経由にする必要はありません。以前のスクリプトは手軽な一方、速度と振り分けに不満があり、もっと柔軟な方法を探すことにしました。","https:\u002F\u002Fblog-img.774352199.xyz\u002FMcjrrF.webp","\u002Ffiddling\u002Fvps-warp-ipv6",{"text":578,"minutes":846,"time":847,"words":848},4.09,245400,818,[850,851,717,537,538],"Cloudflare WARP","IPv6","VPSの特定の通信をWARP経由のIPv6で外に出す",{"categories":854,"date":855,"description":856,"image":857,"path":858,"readingTime":859,"recommend":863,"tags":864,"title":870,"type":362},[437],"2026-08-17 23:59:21","いじればいじるほど、もっといじりたくなります。年を取ってもまだまだ元気、まだまだ元気です。","https:\u002F\u002Fblog-img.774352199.xyz\u002FqfxB0h.webp","\u002Ffiddling\u002Fxiaomi17-root-and-hide-root",{"text":675,"minutes":860,"time":861,"words":862},8.83,529800,1766,6,[865,866,867,868,869],"Xiaomi 17","Bootloader","Android Root","ファームウェア書き換え","Rootの隠蔽","Xiaomi 17を普段使いに：ロック解除、ROM書き込み、root化とroot隠し",{"categories":872,"date":873,"description":874,"image":875,"path":876,"readingTime":877,"recommend":346,"tags":882,"title":886,"type":362},[336],"2022-01-20 22:29:00","Lab 1では、masterとworkerという2つの主要な部分からなるMapReduceシステムを実装します。GoのRPCと並行プログラミングを十分に使いこなし、MapReduceの処理の流れも深く理解する必要があります。実装は2つの版を作り、mutexでロックする方式から、よりすっきりしたchannelによる明示的なロックを使わない方式へと改めました。後者のほうが設計もシンプルで分かりやすくなっています。実習を理解する鍵は、関連資料、特にフローチャートとその説明をしっかり読むことです。","https:\u002F\u002Fblog-img.774352199.xyz\u002FibVwPJ.webp","\u002Fnotes\u002F65840\u002Fmapreducelab",{"text":878,"minutes":879,"time":880,"words":881},"13 min read",12.39,743400,2478,[357,883,359,884,885],"MapReduce","RPC","並行プログラミング","6.5840 Lab 1：MapReduce",{"categories":888,"date":889,"description":890,"image":891,"path":892,"readingTime":893,"recommend":346,"tags":897,"title":902,"type":362},[336],"2022-01-16 17:32:00","MapReduceは、大規模なデータセットの処理を簡単にするための効率的な並列計算モデルです。利用者はMapとReduceという2つの主要な関数を定義することで、複雑なタスクを簡単な操作に分解できます。データの分配やタスクの割り当てはフレームワークが自動で管理するため、開発者は低レベルの細部に気を取られず、アルゴリズムそのものに集中できます。分散システムで広く使われていることからも、その柔軟性と実用性が分かります。","https:\u002F\u002Fblog-img.774352199.xyz\u002FApIDdC.webp","\u002Fnotes\u002F65840\u002Fmapreducepaper",{"text":675,"minutes":894,"time":895,"words":896},8.085,485100,1617,[883,898,899,900,901],"分散システム","論文ノート","並列計算","耐障害性","MapReduce論文を読む",{"categories":904,"date":337,"description":338,"image":341,"path":345,"readingTime":905,"recommend":346,"tags":906,"title":5,"type":362},[336],{"text":348,"minutes":349,"time":350,"words":351},[357,358,359,360,361],{"categories":908,"date":909,"description":910,"image":911,"path":912,"readingTime":913,"recommend":346,"tags":918,"title":920,"type":362},[336],"2022-12-03 21:40:09","Raftは、ログレプリケーションの効率を高めるために設計された合意アルゴリズムです。複数のマシンからなるクラスタに適しており、一部のマシンが故障してもサービスを継続できます。複製状態機械モデルを使い、ログに命令の順序を記録することで、クラスタ内の各マシンが同じ状態に到達できるようにします。論文『In Search of an Understandable Consensus Algorithm』は、Raftの設計思想とPaxosとの比較を詳しく論じ、その理解しやすさを示すとともに、信頼できる大規模なソフトウェアシステムを構築するための土台を提供しています。この記事は、その中心的な概念と応用を理解するための読書メモです。","https:\u002F\u002Fblog-img.774352199.xyz\u002F7mmvIZ.webp","\u002Fnotes\u002F65840\u002Freftextendedpaper",{"text":914,"minutes":915,"time":916,"words":917},"30 min read",29.245,1754700,5849,[358,361,919,899],"ログ複製","Raft論文を読む",{"categories":922,"date":924,"description":925,"image":926,"path":927,"readingTime":928,"recommend":932,"tags":933,"title":937,"type":362},[923],"projects","2021-11-27 14:43:00","MYDBは、データベースの基本原理を学び、実装してみるための個人プロジェクトです。空き時間を使い、半月あまりかけて作りました。大学のデータベースシステムの授業で基礎には触れたものの、インターン中は授業を口実にサボってばかりでした。面接で正直に答えても大きな問題にはなりませんでしたが、それをきっかけに自分の知識を見直し、実際に手を動かして学ぼうと考えるようになりました。そうして生まれたのがこのプロジェクトです。","https:\u002F\u002Fblog-img.774352199.xyz\u002Fxfci2J.webp","\u002Fprojects\u002Fmydb\u002Fmydb0",{"text":675,"minutes":929,"time":930,"words":931},8.03,481800,1606,1,[934,815,935,936],"MYDB","データベース実装","データベースアーキテクチャ","MYDB 0. プロジェクト構成と、先に伝えておきたいこと",{"categories":939,"date":940,"description":941,"image":942,"path":943,"readingTime":944,"recommend":346,"tags":949,"title":952,"type":362},[923],"2021-11-28 16:10:00","MYDBではXIDファイルでトランザクションを管理します。各トランザクションには1から増える一意のXIDがあり、XID 0は常にコミット済みのスーパートランザクションです。TransactionManagerがこのファイルを管理し、実行中・コミット済み・中止済みという3つの状態を記録します。この仕組みにより状態を正確に照会・管理でき、システムの安定性と信頼性を支えます。","https:\u002F\u002Fblog-img.774352199.xyz\u002FH4zZAK.webp","\u002Fprojects\u002Fmydb\u002Fmydb1",{"text":945,"minutes":946,"time":947,"words":948},"8 min read",7.42,445200,1484,[934,815,950,951],"トランザクション管理","XID","MYDB 1. まずはシンプルなTMから",{"categories":954,"date":955,"description":956,"image":957,"path":958,"readingTime":959,"recommend":346,"tags":963,"title":967,"type":362},[923],"2021-12-25 18:26:00","MYDBはMySQLに似たC\u002FS構成を採用し、複数のクライアントがソケットでサーバーへ接続してSQLを実行し、結果を受け取れます。通信には専用のバイナリー形式を使いますが、実装を簡単にするならプレーンテキストも選択肢です。サーバーとクライアントの基本的な転送構造によって、データのやり取りと処理を行います。","https:\u002F\u002Fblog-img.774352199.xyz\u002FPAHrUZ.webp","\u002Fprojects\u002Fmydb\u002Fmydb10",{"text":547,"minutes":960,"time":961,"words":962},5.76,345600,1152,[934,815,964,965,966],"Socket","クライアントサーバー構成","通信プロトコル","MYDB 10. サーバー・クライアントの実装と通信プロトコル",{"categories":969,"date":970,"description":971,"image":972,"path":973,"readingTime":974,"recommend":346,"tags":978,"title":982,"type":362},[923],"2021-11-30 23:18:00","データマネージャー（DM）は上位モジュールとファイルシステムをつなぎ、ページとキャッシュを管理しながら、データの安全性と復旧を支えます。キャッシュには従来のLRUではなく参照カウント方式を採用し、汎用性と効率を高めて、この先のデータ操作の土台を作ります。","https:\u002F\u002Fblog-img.774352199.xyz\u002FWdIGoG.webp","\u002Fprojects\u002Fmydb\u002Fmydb2",{"text":479,"minutes":975,"time":976,"words":977},10.395,623700,2079,[934,815,979,980,981],"参照カウント","キャッシュ設計","共有メモリ","MYDB 2. 参照カウント方式のキャッシュ基盤と共有バイト配列",{"categories":984,"date":985,"description":986,"image":987,"path":988,"readingTime":989,"recommend":346,"tags":993,"title":996,"type":362},[923],"2021-12-05 15:28:00","DMはファイルシステムをページとして抽象化し、ページ単位で読み書きとキャッシュを行います。データページのデフォルトサイズは8Kで、大量の書き込みではサイズの調整によって性能を高められます。実装済みの汎用キャッシュ基盤を使い、ここではページ構造を具体的に定義して、効率よくページを管理します。","https:\u002F\u002Fblog-img.774352199.xyz\u002FjlFC4E.webp","\u002Fprojects\u002Fmydb\u002Fmydb3",{"text":945,"minutes":990,"time":991,"words":992},7.14,428400,1428,[934,815,994,995],"データページ","キャッシュ管理","MYDB 3. データページのキャッシュと管理",{"categories":998,"date":999,"description":1000,"image":1001,"path":1002,"readingTime":1003,"recommend":346,"tags":1007,"title":1010,"type":362},[923],"2021-12-08 22:55:00","MYDBでは、クラッシュ後にデータを復旧できるよう、ログファイルが重要な役割を担います。DMは下位のデータを操作するたびにログを生成・記録し、連続したログ列を作ります。チェックサムと各操作の記録を含む所定のバイナリー形式で保存することで、再起動時にデータの状態を正確に再構築し、整合性と完全性を保ちます。","https:\u002F\u002Fblog-img.774352199.xyz\u002FTRcbsj.webp","\u002Fprojects\u002Fmydb\u002Fmydb4",{"text":878,"minutes":1004,"time":1005,"words":1006},12.685,761100,2537,[934,815,1008,1009],"データベースログ","クラッシュリカバリ","MYDB 4. ログファイルと復旧戦略",{"categories":1012,"date":1013,"description":1014,"image":1015,"path":1016,"readingTime":1017,"recommend":346,"tags":1021,"title":1024,"type":362},[923],"2021-12-11 15:16:00","ページインデックスはDM層の重要な構成要素です。各ページの空き容量をキャッシュすることで挿入処理を効率化し、上位モジュールが長い探索をせずに適切なページを素早く見つけられるようにします。実装ではデータ項目（DataItem）の抽象化とも密接に連携し、データベースの効率的な動作を支えます。","https:\u002F\u002Fblog-img.774352199.xyz\u002F22PSG1.webp","\u002Fprojects\u002Fmydb\u002Fmydb5",{"text":497,"minutes":1018,"time":1019,"words":1020},9.86,591600,1972,[934,815,1022,1023],"ページインデックス","データ管理","MYDB 5. ページインデックスとDMの実装",{"categories":1026,"date":1027,"description":1028,"image":1029,"path":1030,"readingTime":1031,"recommend":346,"tags":1035,"title":1039,"type":362},[923],"2021-12-18 14:58:00","VMは二相ロックでスケジュールの直列化可能性を保証し、多版型同時実行制御（MVCC）を導入して読み取りと書き込みの相互ブロックを解消します。また、データベース操作の競合を定義し、特に更新と読み取りの関係を整理することで、トランザクションの分離レベルを理解する土台を作ります。","https:\u002F\u002Fblog-img.774352199.xyz\u002F8YzotA.webp","\u002Fprojects\u002Fmydb\u002Fmydb6",{"text":692,"minutes":1032,"time":1033,"words":1034},14.665,879900,2933,[934,815,1036,1037,1038],"MVCC","トランザクション分離","二相ロック","MYDB 6. レコードのバージョンとトランザクション分離",{"categories":1041,"date":1042,"description":1043,"image":1044,"path":1045,"readingTime":1046,"recommend":346,"tags":1050,"title":1053,"type":362},[923],"2021-12-23 21:20:00","VMでは、MVCCに伴うバージョンの飛び越しとデッドロックに対処する必要があります。MYDBはトランザクションに印を付けるだけで取り消しやロールバックを行い、abortedなトランザクションのデータがほかへ影響しないようにします。この設計により並行処理の効率と信頼性を高め、従来の2PLで起こりがちなデッドロックを避けて、システム全体の安定性と性能を向上させます。","https:\u002F\u002Fblog-img.774352199.xyz\u002FBF3yDW.webp","\u002Fprojects\u002Fmydb\u002Fmydb7",{"text":563,"minutes":1047,"time":1048,"words":1049},11.32,679200,2264,[934,815,1051,1052],"デッドロック検出","バージョン管理","MYDB 7. デッドロック検出とVMの実装",{"categories":1055,"date":1056,"description":1057,"image":1058,"path":1059,"readingTime":1060,"recommend":346,"tags":1064,"title":1067,"type":362},[923],"2021-12-24 21:01:00","MYDBはB+木によるクラスタ化インデックスを実装します。IMはバージョンマネージャー（VM）を経由せず、データマネージャー（DM）と直接やり取りし、インデックスデータをデータベースファイルへ直接書き込みます。この章では二分木インデックスの構造を詳しく説明し、葉フラグ、キー数、兄弟ノードの識別子など、インデックス検索の基盤となる要素を扱います。","https:\u002F\u002Fblog-img.774352199.xyz\u002Ff92X4o.webp","\u002Fprojects\u002Fmydb\u002Fmydb8",{"text":373,"minutes":1061,"time":1062,"words":1063},6.53,391800,1306,[934,815,1065,1066],"B+木","データベースインデックス","MYDB 8. インデックス管理",{"categories":1069,"date":1070,"description":1071,"image":1072,"path":1073,"readingTime":1074,"recommend":346,"tags":1078,"title":1081,"type":362},[923],"2021-12-25 15:44:00","テーブルマネージャー（TBM）は、フィールドとテーブルの構造を管理します。ParserはSQL風の文を構造化して解析し、文に含まれる情報を対応するクラスへまとめることで、その後の操作を簡単にします。この章ではMYDBのSQL構文も紹介し、管理処理全体を理解する土台を作ります。","https:\u002F\u002Fblog-img.774352199.xyz\u002FzOMyv5.webp","\u002Fprojects\u002Fmydb\u002Fmydb9",{"text":945,"minutes":1075,"time":1076,"words":1077},7.99,479400,1598,[934,815,1079,1080],"SQL解析","テーブル管理","MYDB 9. フィールドとテーブルの管理",1789914059312]