Transaction Anomalies

Non-Repeatable Read A non-repeatable read occurs, when during the course of a transaction, a row is retrieved twice and the values within the row differ between reads. Phantom Read A phantom read occurs when, in the course of a transaction, two identical queries are executed, and the collection of rows returned by the second query is different from the first. Read Skew Read skew is that with two different queries, a transaction reads inconsistent data because between the 1st and 2nd queries, other transactions insert, update or delete data and commit. Finally, an inconsistent result is produced by the inconsistent data. ...

March 31, 2024 · Last updated on August 1, 2025 · 2 min · KKKZOZ

Programming Rust Book Note

Chapter 3 Types Arrays 不支持动态开数组: // an array of 100 int32 elements, all set to 1 let mut arr1 = [1;100]; // correct let n = 100; let mur arr2 = [1;c]; // error Vectors let mut arr = vec![0,6,4,8,1]; arr.sort() // increasing order arr.sort_by(|a,b| b.cmp(a)) // decreasing order If you know the number of elements a vector will need in advance, instead of Vec::new you can call Vec::with_capacity to create a vector with a buffer large enough to hold them all, right from the start. ...

February 10, 2024 · Last updated on August 1, 2025 · 46 min · KKKZOZ

A Piece Of: Go Tests

最近在写毕设项目时,总是发现有些单元测试在 VSCode 的 Testing Panel 连续运行测试时无法通过,但是单独运行时能正常通过,困扰了我好长一段时间。 有一次我发现了一个盲点: 在我写的框架中,有一个 config.go 文件: var Config = config{ LeaseTime: 1000 * time.Millisecond, MaxRecordLength: 2, IdGenerator: NewIncrementalGenerator(), Serializer: serializer.NewJSONSerializer(), LogLevel: zapcore.InfoLevel, } 当我从 Testing Panel 连续运行测试时,不同的测试都会复用 IdGenerator。 从网上查了资料后,才知道: The behavior you’re seeing is expected because Config is a global variable and it’s shared across the entire package. This means that state, such as the current ID from your NewIncrementalGenerator (), is preserved and reused across all your tests running within the same package. Go runs test functions (those starting with Test) in parallel by default, but within a single test package, they all share the same memory space. Therefore, global variables will persist their state across individual tests within that package. ...

January 21, 2024 · Last updated on August 1, 2025 · 1 min · KKKZOZ

Talk about Postgres Visibility Check Rules

Background 最近在看分布式事务相关的论文,很多论文设计的系统中都实现的是快照隔离这一层次的机制,其中 Epoxy 最为典型,直接把 Postgres 的快照隔离机制在中间层重新实现了一遍。 之前看关于 Postgres 快照隔离机制的文章,找到了这个:PostgreSQL并发控制,讲得非常好,逻辑非常清晰,理论和实际例子相结合。 这篇文章中关于 Visibility Check Rules 的部分讲的非常详细,但是没啥规律,可归纳性不强,我时不时就会回来看看这一段,但每次看的时候好像都要从头再重新理解一遍,于是最近我整理了一下这十条规则,力求达到清晰有序。 Rules 我先把原文中提到的十条规则列出来,方便下文做参考。 可以把这些规则简单地按照 t_xmin 的状态分为三部分: Status of t_xmin is ABORTED: Rule 1: If Status (t_xmin) = ABORTED ⇒ Invisible Status of t_xmin is IN_PROGRESS: Rule 2: If Status (t_xmin) = IN_PROGRESS ∧ t_xmin = current_txid ∧ t_xmax = INVAILD ⇒ Visible Rule 3: If Status (t_xmin) = IN_PROGRESS ∧ t_xmin = current_txid ∧ t_xmax ≠ INVAILD ⇒ Invisible Rule 4: If Status (t_xmin) = IN_PROGRESS ∧ t_xmin ≠ current_txid ⇒ Invisible Status of t_xmin is COMMITTED: ...

December 4, 2023 · Last updated on August 1, 2025 · 2 min · KKKZOZ

A Piece Of: ThreadLocal

原理 ThreadLocal 是一种线程局部变量,它为每个线程提供了一个独立的变量副本,所以每个线程都可以拥有自己的局部变量,互不影响。 ThreadLocal 可以做到线程隔离的原因在于,每次创建 ThreadLocal 的时候,都会创建一个新的线程局部存储区,这个存储区只存在于当前线程中,其他线程无法访问到。这样就实现了线程之间的隔离,每个线程都可以在自己的线程局部存储区中保存自己的数据,互不影响。 使用方法 管理 Connection ThreadLocal 的相关知识我查过多次,一直不理解为什么使用 ThreadLocal 可以起到“管理 Connection”的作用,我之前的疑问是这样的: 数据库连接在同一时间只能被一个线程所持有,线程在申请数据库连接时也是线程安全的。Java 多线程访问同一个 java.Sql.Connection 会导致事务错乱。如果 ThreadLocal 的作用是“提供副本”的话,那么多个线程拿到的不就是同一个 Connection 了? 其实是这样的: 如果不使用 ThreadLocal,你当然可以用局部变量的方式来保证线程封闭(Thread Confinement),即在一个函数中先从连接池中获取连接,执行完逻辑后再归还连接。但如果说你必须要使用到一个全局变量的 Connection 呢? 如果不使用 ThreadLocal,就会出现不同的线程使用同一个全局变量的问题,自然不满足“一个数据库连接在同一时间只能被一个线程所持有”的限制。 每当一个线程需要数据库连接时,它就从数据库连接池中取出一个连接,存到 ThreadLocal 中,这样虽然不同线程的数据库连接都叫 dbConn,但都是独立的 Connection。 在 Spring 的 Web 项目中,我们通常会将业务分为 Controller 层,Service 层,Dao 层,我们都知道@Autowired 注解默认使用单例模式,那么不同请求线程进来之后,由于 Dao 层使用单例,那么负责数据库连接的 Connection 也只有一个,如果每个请求线程都去连接数据库,那么就会造成线程不安全的问题,Spring 是如何解决这个问题的呢? 在 Spring 项目中 Dao 层中装配的 Connection 肯定是线程安全的,其解决方案就是采用 ThreadLocal 方法,当每个请求线程使用 Connection 的时候,都会从 ThreadLocal 获取一次,如果为 null,说明没有进行过数据库连接,连接后存入 ThreadLocal 中,如此一来,每一个请求线程都保存有一份自己的 Connection,于是便解决了线程安全问题。 public class DatabaseUtil { private static DataSource dataSource = ...; // 数据库连接池 private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>(); public static Connection getConnection() throws SQLException { Connection conn = connectionHolder.get(); if (conn == null) { conn = dataSource.getConnection(); connectionHolder.set(conn); } return conn; } public static void closeConnection() throws SQLException { Connection conn = connectionHolder.get(); if (conn != null) { conn.close(); connectionHolder.remove(); } } } public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Connection conn = null; try { conn = DatabaseUtil.getConnection(); // do something with the database connection // ... } catch (SQLException e) { // handle exception } finally { if (conn != null) { try { DatabaseUtil.closeConnection(); } catch (SQLException e) { // handle exception } } } } } 在这个示例中,DatabaseUtil 类通过 ThreadLocal 来存储数据库连接。每个请求线程从连接池获取连接时,会先检查 ThreadLocal 中是否已经存在了一个连接,如果没有就创建一个新连接并将其存储到 ThreadLocal 中,否则直接从 ThreadLocal 中获取已有的连接。在请求处理完毕后,关闭连接并从 ThreadLocal 中删除对象引用,以便及时释放资源和避免内存泄漏。 ...

November 29, 2023 · Last updated on August 1, 2025 · 2 min · KKKZOZ

Note: WSL2 Mirrored 网络模式下异常情况总结

Background 前段时间看到了 Windows Subsystem for Linux September 2023 update - Windows Command Line 这篇文章后,发现了 WSL 2 的新网络模式挺有意思的: Networking improvements are a consistent top ask for WSL, and this feature aims to improve the networking experience in WSL! This is a complete overhaul on the traditional NAT networking architecture of WSL, to an entirely new networking mode called “Mirrored”. The goal of this mode is to mirror the network interfaces that you have on Windows into Linux, to add new networking features and improve compatibility. ...

November 25, 2023 · Last updated on August 1, 2025 · 3 min · KKKZOZ

DDIA: Chapter 9 Consistency and Consensus

本章是这本书最酣畅淋漓的一章,涉及到了一致性和共识问题的方方面面,知识点多而不失条理。第一部分先讲了 Linearizability, 为后面的知识点做铺垫。到了 “Ordering Guarantees” 这一小节,从因果关系的带来的 “Happened Before” 的关系开始讲起,讲到了序列号和 Lamport Timestamp,提出来 Lamport Timestamp 的一个缺点:无法在某事件发生时判断是否有冲突,然后引出了全序关系广播,在全序关系广播中又讲到了和 Linearizable 之间的等价关系,最后引出共识算法。太精彩了,值得反复阅读! Consistency Guarantees Most replicated databases provide at least eventual consistency, which means that if you stop writing to the database and wait for some unspecified length of time, then eventually all read requests will return the same value. A better name for eventual consistency may be convergence, as we expect all replicas to eventually converge to the same value. ...

November 13, 2023 · Last updated on August 1, 2025 · 28 min · KKKZOZ

Research: 6.824 Lab2B 中异常情况的分析

写这篇文章的原因是之前在测试 6.824 Lab2B 时总是会出现几个错误,去提了 issue 后也没有得到令人信服的结果,自己有一点头绪但是没验证,这事就这么放着了。 然后最近有个同样做 6.824 的同学给我发了邮件,说他也遇到了同样的问题,重新分析了一下后,本来想简单回复一下的,结果回复的内容越写越多,就干脆直接整理为一篇文章,供大家参考。 异常情况 我之前在对 Lab2B 进行测试时,总是有几个简单的测试点过不了,仿佛代码即使正确,也总是可能出错。我经过分析后发现,都遵循以下这种错误模式: Leader 接收了来自上层的请求,还未提交该日志或者只有他提交了日志(该日志已经被 major 收到)时,就因为收到了其他 peer 的 RequestVote RPC 重新变回了 Follower。 重新选举后再次成为 Leader 后,由于旧任期的 Log 不能被新任期的 Leader 提交,所以之前的日志无法提交。 没有新的请求进来,导致该日志一致无法提交,然后 2 秒后超时,测试无法通过。 错误提示都是 one(xxx) failed to reach agreement。 为什么会出现 在 Lab2B 最开始的几个测试中,测试的编写者为了简化测试,测试代码中提交 command 的操作均为 cfg.one(cmd, servers, false),这个函数的第三个参数名为 retry,控制的是对于一个请求,是否需要在超时后重新提交。 这里 retry 被设置为了 false,也就是说整个执行过程中只会调用 rf.Start() 一次,如果遇见了上文说的异常情况,就会被卡住,最后出现超时报错的情况。 也就是说,所谓的异常情况就是恰好遇见了一个 timing 加上 Lab2B 前面的几个测试有“缺陷”造成的。 no-op 机制 Raft 协议中本身是没有这个问题的,在论文第 13 页中说明了一个节点在当选 Leader 后会发送一个 no-op 的日志,这样新 Leader 就能把 no-op 以及它之前未提交的日志一起提交,就不会卡住了。 ...

November 8, 2023 · Last updated on August 1, 2025 · 5 min · KKKZOZ