🤔 구현 배경

zylo 프로젝트에서 드라이브 기능 구현이 거의 다 끝났을 때 쯤 MongoDB에 저장되는 메타 데이터의 구조를 재설계 해야 할 필요성을 느꼈다. 왜냐하면 파일 간의 관계가 표현되어 있지 않았기 때문이다.

그리고 자바 코드도 수정해야 했다. MongoDB의 문서 구조가 변경되면, 관련된 자바 코드를 수정하는 것은 자연스럽게 따라오는 과정이다. 뿐만 아니라 디렉터리 간 파일이 이동하는 기능과 디렉터리 내부에 또다른 디렉터리와 파일을 생성하는 등의 기능도 구현해야 했다.

그래서 일부 클래스를 제외하고 Document 클래스와 서비스 클래스 및 repository 클래스를 리팩터링 하기로 마음먹었다.

그 뿐만 아니라 자바 코드를 리팩터링 할 때, 추후의 확장성과 유지/보수/운영성 측면을 고려하여 파일 시스템을 모델링 하는 가상 파일 시스템 인터페이스와 그 구현체를 만들고, 가상 파일 시스템 객체를 캐싱하는 방식으로 확장성과 유지/보수/운영성, DB I/O 감소를 달성하였다.

📑 MongoDB 문서 구조

1. 첫 번째 시도

가장 먼저 MongoDB에 저장되는 문서의 구조를 재설계 하였다. 처음에 사용했던 문서 구조는 아래와 같다.

{
  "_id": {
    "$oid": "684b9f2c37fecdb0358b4e4a"
  },
  "uid": "abc1234",
  "role": "free",
  "files": [
    {
      "uploadPath": "drive/abc1234/images/favicon.png",
      "filename": "favicon.png",
      "size": {
        "$numberLong": "201976"
      },
      "mimeType": "image/png",
      "upload_date": {
        "$date": "2025-06-13T06:04:17.971Z"
      }
    }
  ],
  "currentCapacity": {
    "$numberLong": "201976"
  },
  "capacity": {
    "$numberLong": "1073741824"
  }
}

이 문서 구조에서는 디렉터리와 그 하위 파일들 간의 관계를 파악하기 힘들다. 파일 간의 관계를 나타내는 필드가 없으므로, uploadPath 필드의 문자열 값을 직접 파싱하여 관계를 파악해야 했기 때문이다.

2. 두 번째 시도

두 번째로 사용한 문서 구조에는 tree 필드 내부에 JSON 객체를 중첩시키는 방식을 사용했다.

{
  "_id": {
    "$oid": "68537511272c6ec4a27dcde5"
  },
  "onwer": "abc123",
  "tier": "free",
  "currentSize": 0,
  "maxSize": 1024,
  "tree": {
    "filename": "/drive/abc123",
    "depth": 0,
    "isDir": true,
    "createdAt": "2025-04-22",
    "resources": [
      {
        "filename": "test.json",
        "depth": 1,
        "isDir": false,
      	"createdAt": "2025-04-24"
      },
      {
        "filename": "sub1",
        "depth": 1,
        "isDir": true,
        "createdAt": "2025-04-24",
        "resources": []
      }
    ] 
  }
}

하지만 여기에도 문제가 있었는데, 파일과 디렉터리가 추가되고, 특정 디렉터리 내부에 다른 파일과 디렉터리가 추가될수록 구조가 복잡해진다는 것이었다. 이는 확장성을 저해할 뿐만 아니라 개별 문서의 용량도 증가하는 문제를 야기한다.

3. 마지막 시도

마지막이자 최종적으로 선택한 대안은 디렉터리와 파일을 별도의 문서로 관리하는 것이었다.

/*root 디렉터리 문서*/
{
  "_id": {
    "$oid": "68537511272c6ec4a27dcde5"
  },
  "nodeId": "abc123-root",
  "onwer": "abc123",
  "filename": "abc123",
  "isDir": true,
  "depth": 0,
  "parentId": null,
  "path": "/drive/abc123",
  "lastModified": "2025-04-22"
}

/*root 디렉터리 하위의 파일 문서*/
{
  "_id": {
    "$oid": "68537826272c6ec4a27dcde6"
  },
  "nodeId": "abc123-e7da618e-9c6c-437f-8bfb-cbbc897bafe3",
  "owner": "abc123",
  "filename": "test.json",
  "isDir": false,
  "depth": 1,
  "parentId": "abc123-root",
  "path": "/drive/abc123/test.json",
  "size": 12345,
  "mimeType": "application/json",
  "lastModified": "2025-04-24"
}

이 구조는 parentId를 이용해서 파일 간의 관계를 파악할 수 있다. 문서를 쉽게 구분하기 위해 nodeId 필드를 정의하고 그 곳에 <userId> + UUID 값을 할당하는 방식을 사용하였다. 뿐만 아니라 isDir 필드를 사용하여 특정 문서가 파일인지, 아니면 디렉터리인지 쉽게 구분할 수 있도록 하였으며, depth 필드를 선언해 포함 관계를 정의하였다.

📂 가상 파일 시스템(Virtual File System, VFS)

문서 구조를 재설계 한 뒤, 드라이브 서비스 코드를 리팩터링 해야 했다. 가장 먼저, 변경된 문서 구조에 부합하는 Document 클래스를 만들고 기존에 만들어 둔 Document 클래스를 사용하는 Service 및 Repository 클래스 등의 메서드 시그니쳐를 수정했다.