{ "version": "1.1", "id": "write-completion-boundaries", "title": "fsync() 의 요구가 물리 storage 까지 내려가는 사슬", "question": "게스트가 fsync() 를 부르면 그 요구는 어느 계층까지, 몇 번 손이 바뀌어 내려가야 하는가", "type": "flow", "direction": "TB", "audience": [ "가상 머신 위에서 데이터베이스를 돌리며 durability 를 따지는 사람", "완료 응답이 무엇을 보장하는지 가르려는 사람" ], "summary": "게스트 PostgreSQL 의 fsync() 요구는 게스트 파일시스템과 게스트 Block Layer 에서 FLUSH 등으로 바뀌고 virtio-blk 와 QEMU / Backend, Host Storage Stack 을 지나 Physical Storage 까지 전달되어야 한다.", "alt": "PostgreSQL 의 fsync() 가 Guest Filesystem, Guest Block Layer, virtio-blk, QEMU / Backend, Host Storage Stack 을 차례로 지나 Physical Storage 에 닿는 위에서 아래로 읽는 흐름도.", "long_description": "위에서 아래로 읽는다. 맨 위 PostgreSQL 이 fsync() 를 부르고 그 요구가 Guest Filesystem 으로 내려간다. Guest Block Layer 에서 FLUSH 등의 요청으로 바뀌고, virtio-blk 를 지나 VM 경계를 넘어 QEMU / Backend 로 간다. 거기서 Host Storage Stack 을 지나 맨 아래 Physical Storage 까지 그 의미가 전달되어야 완료가 성립한다. 여섯 번 손이 바뀌는 동안 한 곳이라도 그 의미를 지키지 않으면 게스트가 받은 완료 응답이 거짓이 된다. write() 완료와 writeback 완료, fsync/flush 완료, 전원 장애에도 안전한 durability 가 서로 다르다는 구분은 본문의 코드블록이 맡는다.", "source_context": { "document": "docs/virtualization/final/document.md", "document_sha256": "181e2b3cc8a45bae81e7e8193d026937c4d586ae4495d3b2c323eb7e6abcfadd", "anchor": { "kind": "heading", "value": "150. `fsync()`가 필요한 이유", "line": 6723 } }, "composition": { "profile": "component-flow", "diagram_only": true, "reference_ids": [ "payment-event-flow" ], "rationale": "이 절이 주장하는 것은 요구 하나가 한 방향으로 여러 계층을 지나야 완료가 된다는 것이다. sequence 는 참여자마다 생명선을 세우고 요청과 응답을 짝지어 세우는데 이 절에는 돌아오는 응답이 적혀 있지 않아 생명선이 전부 빈 세로줄이 된다. comparison 은 write 와 fsync 를 나란히 놓는 그림이라 「어디까지 내려가야 하는가」라는 자리의 문제가 지워진다.", "focus_node": "physical-storage" }, "groups": [], "nodes": [ { "id": "postgresql", "label": "PostgreSQL", "kind": "service", "role": "source", "shape": "box", "details": [ "write()", "fsync()" ], "description": "필요한 시점에 fsync() 를 불러 영속성 경계까지 반영을 요청하는 게스트 프로세스.", "evidence": [ { "start_line": 6742, "end_line": 6742 }, { "start_line": 6726, "end_line": 6734 } ], "assumption": false }, { "id": "guest-filesystem", "label": "Guest Filesystem", "kind": "service", "role": "service", "shape": "box", "description": "fsync() 요구를 처음 받는 게스트 파일시스템.", "evidence": [ { "start_line": 6746, "end_line": 6746 } ], "assumption": false }, { "id": "guest-block-layer", "label": "Guest Block Layer", "kind": "service", "role": "service", "shape": "box", "description": "요구를 FLUSH 등의 block 요청으로 바꾸는 계층.", "evidence": [ { "start_line": 6749, "end_line": 6751 } ], "assumption": false }, { "id": "virtio-blk", "label": "virtio-blk", "kind": "service", "role": "service", "shape": "box", "description": "그 요청을 VM 경계 너머로 옮기는 게스트 driver.", "evidence": [ { "start_line": 6753, "end_line": 6753 } ], "assumption": false }, { "id": "qemu-backend", "label": "QEMU / Backend", "kind": "service", "role": "service", "shape": "box", "description": "요청을 받아 호스트 쪽 파일 I/O 나 block I/O 로 잇는 자리.", "evidence": [ { "start_line": 6756, "end_line": 6756 }, { "start_line": 6686, "end_line": 6686 } ], "assumption": false }, { "id": "host-storage-stack", "label": "Host Storage Stack", "kind": "service", "role": "service", "shape": "box", "description": "호스트 Page Cache 와 파일시스템, Block Layer 가 놓인 구간.", "evidence": [ { "start_line": 6759, "end_line": 6759 }, { "start_line": 6688, "end_line": 6692 } ], "assumption": false }, { "id": "physical-storage", "label": "Physical Storage", "kind": "store", "role": "sink", "shape": "box", "emphasis": "primary", "description": "요구가 여기까지 전달되어야 정전 이후 생존을 말할 수 있다.", "evidence": [ { "start_line": 6762, "end_line": 6765 }, { "start_line": 6729, "end_line": 6729 } ], "assumption": false } ], "edges": [ { "id": "app-to-guest-fs", "from": "postgresql", "to": "guest-filesystem", "label": "fsync()", "kind": "control", "style": "solid", "order": 1, "evidence": [ { "start_line": 6744, "end_line": 6746 } ], "assumption": false }, { "id": "guest-fs-to-block-layer", "from": "guest-filesystem", "to": "guest-block-layer", "label": "", "kind": "control", "style": "solid", "order": 2, "evidence": [ { "start_line": 6746, "end_line": 6749 } ], "assumption": false }, { "id": "block-layer-to-virtio", "from": "guest-block-layer", "to": "virtio-blk", "label": "FLUSH 등", "kind": "control", "style": "solid", "order": 3, "evidence": [ { "start_line": 6751, "end_line": 6753 } ], "assumption": false }, { "id": "virtio-to-qemu", "from": "virtio-blk", "to": "qemu-backend", "label": "", "kind": "control", "style": "solid", "order": 4, "evidence": [ { "start_line": 6753, "end_line": 6756 } ], "assumption": false }, { "id": "qemu-to-host-stack", "from": "qemu-backend", "to": "host-storage-stack", "label": "", "kind": "control", "style": "solid", "order": 5, "evidence": [ { "start_line": 6756, "end_line": 6759 } ], "assumption": false }, { "id": "host-stack-to-physical", "from": "host-storage-stack", "to": "physical-storage", "label": "", "kind": "control", "style": "solid", "order": 6, "evidence": [ { "start_line": 6759, "end_line": 6765 } ], "assumption": false } ], "legend": [], "metadata": { "rationale": "component-flow 를 고른 이유는 이 절이 답하는 물음이 「fsync() 의 요구가 어디까지 내려가야 하는가」이기 때문이다. 앞뒤 절이 대는 사실 가운데 그리지 않은 것이 있다. 「write · writeback · fsync/flush · 전원 장애 생존」 네 완료 경계를 이 사슬 위에 표시하려 했으나 SSOT 는 §148 에서 네 이름을 나열할 뿐 writeback 완료와 fsync/flush 완료가 이 사슬의 어느 칸에서 성립하는지는 정하지 않았고, §148 자체가 이 앵커의 준비된 문맥(§149~§151) 밖이라 인용할 줄도 없다. 게다가 네 이름의 나열은 기록 본문에 이미 코드블록으로 있어 그림으로 다시 그리면 중복이다. 그래서 자리로만 보이는 것 — 요구가 여섯 번 손이 바뀌어 내려간다는 것 — 만 남겼다." } }