When you run a personal knowledge base, “sync” feels like the most natural answer at first. That was true for me too. My Second Brain stack originally lived on a local machine, while agent sessions coming in through messaging channels were running on a server. The problem was simple: an agent running on the server could not reliably reach the search API that lived on my local machine.
At first, I tried to bridge that gap with Google Drive. The architecture put Drive between local and server, periodically downloaded files, processed an upload queue, and cleaned up missed items at night. The design was possible. But as I refined the implementation, it started to feel wrong. I was no longer building a knowledge base. I was building a sync pipeline.
Eventually I changed direction. Instead of maintaining a transport layer, I moved the Second Brain to a single server-operated model and added a direct upload API.
The real role Drive was playing
At first, I thought of Drive as a simple transport mechanism. It was just a temporary bridge for moving documents from local storage to the server. But before removing it, I audited the system and found something important: Drive was not just the transport layer. It was the only durable copy of the original documents.
I thought I had backups, but in practice they were centered on the search index database. The original document area was not backed up well enough. If I removed Drive in that state, the architecture would become simpler, but I could lose the source documents needed for recovery after a failure.
So I changed the backup model before removing Drive. Instead of keeping only the index, I moved to incremental snapshots that include the original documents as well. Rather than copying everything every time, the system uses hardlink-based incremental backups to save storage while still allowing point-in-time recovery.
Only after that work could I safely remove Drive. It was a reminder that when you remove a dependency, the first question is not “can I delete this?” It is “what responsibility was this dependency quietly carrying for me?”
Uploads became write-only
In the new structure, I consolidated the shared upload logic into one path. It validates files, checks for duplicates, stores them in a safe location, and then hands them off for indexing. On top of that, I added a write-only ingestion API.
The important design choice was separating reads from writes. The search API remains read-only, while the upload API lives behind a separate process boundary. That prevents Planner or Executor-style agent flows from structurally reaching the upload logic.
I did not want to rely only on code review or rules for access control. When possible, I prefer a structure that is hard to touch by accident. That matters even more for a personal knowledge base, where original documents accumulate over time. Keeping the read path and write path separate is simply easier to trust.
What the audit found along the way
The work also surfaced a few important surprises. The backup timer existed as a file, but it was not actually enabled. The previous backup was closer to a one-off manual run. Once snapshots become the core of durability, whether the timer is enabled matters as much as the implementation itself. So this time I verified the scheduled run as part of the work.
Testing had a similar issue. The tests were written in pytest fixture style, but when they were run with a different test runner, zero tests were quietly discovered and the run looked successful. It was green, but it had not verified anything. Only after aligning the environment with pytest did I confirm that the full test suite was actually running and passing.
These issues rarely show up in large design documents. In operational systems, though, small gaps like this are often what eventually become incidents.
The trade-off I accepted
This is not a perfect architecture. Server-local durability is stronger now, but I have not added separate offsite replication yet. Single-server risk still exists. At this stage, though, I decided that single-server operation with a clear backup model was better than continuing to maintain a sync pipeline.
The point of this cleanup was not simply “I removed Drive.” The point was that before removing a transport layer, I checked what responsibility it was actually carrying and moved the responsibility I still needed into a simpler structure.
The most dangerous moment in simplifying a complex system is not when you press delete. It is when you believe deletion is safe. This time, I let audit and tests challenge that belief before I removed the dependency.
한국어 버전
Second Brain을 VPS로 옮기며 Drive를 걷어낸 이유
개인 지식 베이스를 운영하다 보면 처음에는 “동기화”가 가장 자연스러운 해법처럼 보인다. 내 경우도 그랬다. Second Brain 스택은 원래 로컬 머신에 있었고, 메신저를 통해 들어오는 에이전트 세션은 서버에서 돌고 있었다. 문제는 간단했다. 서버에서 실행되는 에이전트가 로컬에 있는 검색 API에 안정적으로 닿기 어려웠다.
처음에는 이 간극을 Google Drive로 메우려고 했다. 로컬과 서버 사이에 Drive를 두고, 주기적으로 내려받고, 업로드 큐를 처리하고, 밤마다 누락분을 정리하는 구조였다. 설계 자체는 가능했다. 하지만 구현을 다듬을수록 찜찜함이 커졌다. 내가 만들고 있는 것은 지식 베이스가 아니라 동기화 파이프라인이었다.
결국 방향을 바꿨다. 전송 계층을 유지하는 대신, Second Brain을 서버 단일 운영으로 옮기고 업로드 API를 직접 두기로 했다.
Drive가 맡고 있던 진짜 역할
처음에는 Drive를 단순한 전송 수단으로 생각했다. 로컬에 있는 문서를 서버로 보내기 위한 임시 다리 정도였다. 그런데 걷어내기 전에 감사를 해보니 중요한 사실이 나왔다. Drive는 전송 계층이 아니라 원본 문서의 유일한 내구성 있는 사본이었다.
백업은 있다고 생각했지만, 실제로는 검색 인덱스 데이터베이스 중심이었다. 원본 문서 영역은 충분히 백업되고 있지 않았다. 이 상태에서 Drive를 제거하면 구조는 단순해지지만, 장애가 났을 때 복구할 원본이 사라질 수 있었다.
그래서 Drive 제거보다 먼저 백업 모델을 바꿨다. 인덱스만 보관하는 방식에서 원본 문서까지 포함하는 증분 스냅샷 구조로 바꿨다. 매번 전체를 복사하는 대신 하드링크 기반 증분 백업을 사용해 저장 공간을 아끼면서도 시점별 복구가 가능하도록 했다.
이 작업을 하고 나서야 Drive를 지울 수 있었다. 의존성을 제거할 때는 “지워도 되는가”보다 “그 의존성이 몰래 대신하던 책임은 무엇인가”를 먼저 봐야 한다는 걸 다시 느꼈다.
업로드는 쓰기 전용으로 분리했다
새 구조에서는 공용 업로드 로직을 하나로 묶었다. 파일을 검증하고, 중복을 확인하고, 안전한 위치에 저장한 뒤, 인덱싱까지 이어지는 흐름이다. 그 위에 쓰기 전용 Ingestion API를 얹었다.
중요하게 본 부분은 읽기와 쓰기의 분리였다. 검색 API는 읽기 전용으로 남기고, 업로드 API는 별도 프로세스 경계 안에 두었다. Planner나 Executor 계열의 에이전트 흐름이 업로드 로직에 구조적으로 접근하지 못하게 만든 것이다.
권한을 코드 리뷰나 규칙에만 기대고 싶지 않았다. 가능하면 “실수로도 닿기 어려운 구조”가 낫다. 특히 개인 지식 베이스처럼 원본 문서가 쌓이는 시스템에서는 읽기 경로와 쓰기 경로를 분리하는 편이 마음이 편했다.
감사하면서 나온 덤
이번 작업에서 의외로 중요한 발견도 있었다. 백업 타이머는 파일로 존재했지만 실제로 활성화되어 있지 않았다. 이전 백업은 수동으로 한 번 실행된 상태에 가까웠다. 스냅샷이 내구성의 핵심이 되는 순간, 타이머가 켜져 있는지는 구현만큼 중요하다. 그래서 이번에 주기 실행까지 확인했다.
테스트에서도 비슷한 문제가 있었다. 테스트는 pytest 픽스처 스타일인데, 다른 테스트 러너로 실행하면 조용히 0개가 발견되고 성공처럼 보였다. 겉으로는 초록색이지만 실제로는 아무것도 검증하지 않은 상태였다. pytest 환경을 맞춘 뒤에야 전체 테스트가 실제로 통과하는 것을 확인할 수 있었다.
이런 문제들은 큰 설계 문서에서는 잘 보이지 않는다. 하지만 운영 시스템에서는 이런 작은 틈이 결국 장애로 이어진다.
수용한 트레이드오프
이번 선택이 완벽한 구조는 아니다. 서버 로컬 내구성은 강화했지만, 별도 오프사이트 복제까지 넣지는 않았다. 단일 서버 리스크는 남아 있다. 다만 지금 단계에서는 동기화 파이프라인을 계속 유지하는 비용보다, 단일 운영과 명확한 백업 구조가 더 낫다고 판단했다.
이번 정리의 핵심은 “Drive를 없앴다”가 아니다. 전송 계층을 없애기 전에 그 계층이 실제로 맡고 있던 책임을 확인했고, 필요한 책임은 더 단순한 구조로 옮겼다는 점이다.
복잡한 시스템을 단순하게 만들 때 가장 위험한 순간은 삭제 버튼을 누를 때가 아니다. 삭제해도 된다고 믿는 순간이다. 이번에는 그 믿음을 감사와 테스트로 한 번 더 확인한 뒤에 지웠다.