Node.js) Storage 에서 사용하지 않는 더미 파일들 정리하기 -- ft.서버...

Node.js) Storage 에서 사용하지 않는 더미 파일들 정리하기 -- ft.서버...

목차

파일 업로드

사용자가 파일을 입력하면 서버는 storage에 그 파일을 정리한다.

대개 AWS라면 S3 서비스, Naver의 object storage, Azure의 bucket 서비스, 아니면 서버 디스크에 바로 저장하는 경우 등 다양한 방법으로 파일을 저장한다. 파일을 저장한 후엔, file.filename 을 통해 db에 파일 명을 저장하는 방식을 많이 사용한다.

각각 작품의 파일명이 db에 저장된 모습

사용자가 파일을 삭제하거나 수정할 경우 db의 파일 명은 새로운 파일 이름으로 업데이트 되고, storage에도 동일하게 파일이 올라간다.

이렇게만 보면 평화로운 서버 나라이지만, 그러나 사용자가 삭제한 파일은 여전히 storage에 저장되어 있다. 따로 storage에 delete요청을 보내지 않았기 때문이다.

더미 파일을 정리하는 방법

이럴 경우 두 가지 방법이 있다.

1. 사용자가 파일을 수정하거나 삭제할 때마다 storage에 삭제 요청을 보낸다.

2. 서버 스케줄링을 작성해 특정 시간에 사용하지 않는 파일들을 한꺼번에 삭제한다.

게임 서버 같이 서버의 리소스 관리를 철저하게 관리해야할 경우 1 방법을 주로 사용한다. 특히 게임은 한 사용자가 여러 계정을 사용하며, 아이템, 길드 등 방대한 양의 데이터들이 연관되어 있기 때문이다. 게다가 실시간으로 멀티 서버를 운영하는 등 서버 비용이 상대적으로 다른 서비스에 비해 매우 크다. 바로 바로 관리하지 않으면 더미 데이터가 겉잡을 수 없게 쌓이기 때문에 바로 바로 정리해준다고 한다.

커뮤니티 등 일반적인 웹 서비스들은 관례적으로(?) 유연하게 리소스를 관리한다고 한다. 2번 방식을 주로 사용하는데, 주기적으로 코드를 돌려주는 서버 스케줄링 기능을 통해 관리한다. 일반적으로 화요일 새벽3시쯤 돌리는 것을 좋아한다고 한다. 제일 애매한 시간대... ㅋㅋㅋㅋ 애매한 시간대에 부하가 큰(?), 매일 돌릴 필요없는 코드를 돌려 안정성을 확보하는 듯 하다.

1번 방식의 문제점

1번 방식 '요청에 따라 그때 그때 리소스를 삭제한다.'

이 방식을 구현하려고 생각하면 생각보다 많은 단계와 시나리오가 예상된다.

A) 사용자가 파일을 수정했다.

→ 수정전 파일명 가져오기, 삭제

B) 사용자가 파일을 삭제했다.

→ 삭제하는 파일명 가져오기, 삭제

C) 작품 정보를 영구 삭제 했다

→ 작품 정보와 연결된 파일명 가져오기, 삭제

파일을 수정하든, 연결된 파일만 삭제하든 이전 정보를 가져와 업데이트 해야하기때문에 이전 정보를 읽어오는 코드를 추가해야하는 번거로움이 있다. 또한 지금 파일을 업로드하는 경우가 하나일 때만 가정한 것인데, 관련하여 썸네일, 이미지 리사이징이 추가된 경우 예상되는 뎁스가 n배가 된다. 때문에 나는 2번 방식을 택했다. (서버 스케줄링을 한번 도전하고 싶기도 했고!)

2번 방식의 문제점

2번 방식, '서버 스케줄링을 작성해 특정 시간에 사용하지 않는 파일들을 한꺼번에 삭제한다.'

이 방식은 스케줄링 코드를 따로 작성해야 한다는 번거로움과, 서버에 저장된 파일이 많을수록 느리다는 단점이 있다. 이외의 단점은 차차 알아보는 걸로 ㅎㅎ

사용하지 않는 파일 리스트 뽑기 (차집합)

2번 방식을 사용하려면 우선 (A)db에 저장된 현재 사용중인 파일명 을 배열로 가져온다. (B) 그후 실제 서버 스토리지에 저장된 모든 파일명을 배열로 가져온다. 그후 B에서 A를 제외한 부분만 삭제하면된다. 정확히 따지면 아래 그림 같은 모양이 되겠지만, 개발 과정에선 위 그림처럼 될 수 있다. (내 경우 개발 테스트는 aws s3에서 하다가, 디스크 스토리지로 넘어온 케이스라...)

자, 아무튼 한번 차집합을 구하는 코드를 작성해보자.

const {Artwork} = require('./models'); const sequelize = require('sequelize'); const fs = require('fs'); const Op = require('sequelize').Op; // usingArtworkFiles 현재 사용중인 모든 작품 파일 가져오기 const getUsingArtworkFiles = async() => { try { let resources = await Artwork.findAll({ attributes: ['ARTWORK_RESOURCE'], where: { ARTWORK_RESOURCE: { [Op.not]: null } } }) // sequlize는 object 형식으로 리턴하기 때문에 배열로 다시 넣어줌 let result = [] resources.forEach( resource => { result.push(resource.ARTWORK_RESOURCE) }) console.log('----> usingArtworkFiles ', result) return result } catch (error) { console.error(error) return [] } } const getDiskArtowkFiles = async() => { let filelist = fs.readdirSync('uploads/artwork') console.log('----> diskArtworkFiles', filelist); return filelist } const startCleanUp = async() => { // diskArtworkFiles 파일 리스트 가져오기 let usingArtworkFiles = await getUsingArtworkFiles(); let diskArtworkFiles = await getDiskArtowkFiles(); // 차집합 구하기 차집합에 든 파일 disk에서 삭제하기 let difference = diskArtworkFiles.filter(filename => !usingArtworkFiles.includes(filename)); difference.forEach(filename => { fs.unlink(`uploads/artwork/${filename}`, (err) => { if (err === undefined || err == null) { console.log(`delete >>>>> uploads/artwork/${filename}`); } else { console.log(err); } }) }) } startCleanUp() // getDiskArtowkFiles() // getUsingArtworkFiles()

* 주의

꼭 비동기로 디스크에 있는 파일 리스트를 읽어와야한다. 안그러면 파일 리스트를 읽어오는게 오래 걸리기 때문에 빈 배열이 리턴된다. fs.readdirSync('uploads/artwork') 꼭 비동기로 읽어오기!

개발용 로그 남기기

아무튼 차집합을 구해 사용하지 않는 파일들을 지우는 코드를 작성했다. 여기서 바로 서버 스케줄링 코드로 넘어갈 수 있지만, 개발용 로그를 남겨보자. 정해진 시간에 파일이 정리가 되었는지, 어떤 파일들이 지워졌는지 혹시나 처리중에 에러가 발생해 서버가 죽을 수 있으니 로그를 꼭 꼭 남겨보자.

서버 스케줄링 코드 작성 (node-schedule)

const schedule = require('node-schedule'); // 초 분 시 일 월 주 // 42초가 될때마다 실행 const job = schedule.scheduleJob('42 * * * * *', function(){ console.log('42초가 되었습니다.'); });

42초가 될 때마다 콘솔에 찍도록 해주었다. 42초때마다 잘 찍히는것 확인 완료!

이를 활용하여 전에 만들어둔 cleanUpResource.js를 실행시키면 끝~~

생각보다 스케줄링이 어렵지 않아서 놀랐다. 서버 운용이라고 하면 넘 어렵게만 느껴졌는데, 역시 하면 된다!

지금 상태는 node 서버가 돌아가야만 스케줄링 코드가 돌아간다.

다음엔 한번 os에서 돌아가는 cron을 도전해봐야지!

from http://potato-hyun.tistory.com/33 by ccl(A) rewrite - 2021-08-22 21:00:41