아마존엔 없는 것 빼고 다 있다.

2024년 1월 3일 수요일

Today I Learned

날짜

2024년 1월 3일 수요일

내용

SQLAlchemy

매주 새로 생성된 리뷰들에 관한 정보를 담은 메일을 보내는 Task를 얼추 완성했다. CSS에 대해 추가적인 피드백이 필요하긴 하지만 우선 로직과 코드 자체에 대해 확인이 필요했다.

  1. 명명

    이번엔, 변수나 함수 이름, 엔드포인트에 대해서 조금 더 신경썼다. 맨 처음 코드를 작성할 떈 아무래도 머릿속에 생각나는대로 적거나 GPT가 써준대로 적게 된다. Task를 진행하다보면 어느새 이 이름에 익숙해져 그대로 PR을 올리는 경우가 많았다.

    다른사람이 내 코드를 볼 때, 이름의 의미를 쉽게 파악할 수 있다면 코드를 읽기 훨씬 수월해지기 떄문에 PR올리기 전 코드를 정리하는 시간을 따로 가졌다. 요청을 주고 받을 때 양쪽의 변수 이름을 동일하게 해서 함수의 흐름을 따라가는 사람이 헷갈리지 않도록 했다. 함수가 결국 반환하는 데이터가 무엇인지를 이름을 통해 알 수 있게 했다. 예를 들어, 내 Task가 결국 email을 보내야 하는 거지만 내가 구현할 함수 중에는 그저 Email을 위해 필요한 데이터만 조회하는 함수도 존재한다. 이 함수의 이름에는 email이 들어갈 필요가 전혀 없다. 나야 Task를 진행하면서 자연스레 넘어가게 되지만, 배경을 모르는 사람이 보면 코드를 볼 때 존재하지도 않는 메일 전송과 관련된 로직을 신경써야 하니까.

    클린코드에선 애초에 지을때 잘지으라고 했으니, 다음부턴 한번에 기가막히게 짓고, 마지막엔 검토만 해볼 것.

  2. SQL Query

    DB에서 필요한 데이터를 조회할 때 db.query() 의 형태를 사용했다. 이번 Task는 cron을 만들어야 했기 때문에 다른 파일들이 그렇게 되어있어 별 생각없이 따라했기 때문이다. 하지만 이 쿼리문은 SQLAlchemy 1.4 이전에서 사용되던 형태다. 우리 서비스에서 사용해야할 2.0 에선 작성한 쿼리를 db.execute() 내부에 삽입하는 모양새다. 이미 기존에 API를 만들면서 여러번 사용해왔지만, 코드를 별 생각없이 작성해서 발생한 문제다.

    이 기회에 SQLAlchemy ORM과 SQLAlchemy Core의 차이도 알게 되었는데, 추상화 레벨의 차이다. 쿼리를 유연하게 다룰 수있고 성능이 상대적으로 좋은건 SQLAlchemy에서 기본적으로 제공하는 Core다. 이 Core를 기반으로 유저 친화적으로 만든 것이 ORM이고, 특히 객체와 데이터를 Mapping하기 훨씬 쉽다. (참고 링크)

    내가 필요한 정보들은 Review 테이블의 칼럼들에서 얻을 수 있다. 조건에 맞는 칼럼의 갯수, 평균 점수, imported 여부가 필요했다. 기존에는 조건에 맞는 Review들을 모두 가져오고 서버에서 해당 데이터들을 계산해서 원하는 값을 얻었다. 따라서 불필요한 필드들도 가져오는 것, 서버에서 데이터의 갯수를 세는 len 내장함수를 사용하는 것으로 인해 비효율적이었다. 개선을 위해 데이터베이스에서 처리 후에 필요한 값만 가져오도록 수정했다. 필요한 값들이 1개의 테이블에 있었기 떄문에 1번의 접근으로 3개의 값들을 가져와서 계산할 수 있도록 func.count 를 사용했다.

    추가적인 궁금증이 생겼다. SQL 서버는 review 테이블에서 조건에 맞는 칼럼들 모두 수집한 후에 필요한 필드값들을 찾아나가는지, 매 칼럼에서 필요한 필드들만 꺼낼지 궁금했는데 ChatGPT 가 말하길 후자라고 한다. 생각해보면 전자는 너무 비효율적이다.

404

어제 확인한 404에 관해서, S3와 연관되있을 수 있다고 들어 확인했다. 이참에 우리 서비스가 어떻게 구동되고 있는지 알게 될 수 있어서 약간 신났는데 그리 오래가진 않았다.

클라이언트가 서버에게 요청을 보내는 그 사이에 많은 과정이 포함될 수 있다. 우리 서비스에선 Nginx, Route53, CloudFront, S3 정도가 있다. 간단하게 정의해보자.

  1. Nginx : 프록시나 리버스 프록시서버로 작동하면서 로드밸런싱, 트래픽 조절 등을 통해 성능에 영향을 끼치는게 Nginx다.
  2. AWS Route53 : DNS 라우팅, 도메인 등록, 상태 확인등을 해주는 서비스. DNS는 Domain Name System의 약자인데, 호스트의 도메인 이름을 호스트의 네트워크 주소로 바꾸거나 그 반대의 변환을 수행할 수 있도록 하는것이다. 사람이 읽을 수 있는 도메인 이름(링크)를 IP 주소(숫자)로 변환. 읽었을 때, 그럼 ‘가비아랑 뭐가 다른건가?’ 라는 생각이 들긴 했는데 가비아에선 ‘사람이 읽을 수 있는 도메인 이름’을 선택해서 구매할 수 있고 Route53은 그 도메인을 IP 주소와 연결해준다고 이해했다.
  3. CloudFront : CDN을 제공하는 서비스. 정적 및 동적 웹 컨텐츠를 사용자에게 더 빨리 배포하도록 지원해주는 서비스다. 엣지 로케이션이라는 곳에서 콘텐츠를 제공하고, 만약 여기에 없다면 어디서 줄 지 지정할 수 있다. 뒤에 후술할 S3라는 저장소에서 제공하도록 설정할 수도 있다. CDN(Content Delivery Network)은 웹 컨텐츠의 복사본을 사용자에 가까운 곳에 두어 웹 성능 및 속도를 향상시켜준다. 이건 내 블로그에 이미지를 업로드할 떄 많이 사용하다보니 알게됐다. 미국에서 우리 서비스를 접속한 고객의 화면에 띄울 이미지는 적어도 북아메리카에 있는 서버에서 보내는게 빠르다. 아무리 통신이 빨라도 희망봉까지 해저케이블 타고 갔다오는것과는 차이가 있을 수밖에.
  4. S3: 객체 스토리지 서비스. 그냥 저장소다. 단 버킷이라는 개념으로, 일관성을 유치한채 접근하고 읽거나 쓸 수 있다. Docker의 컨테이너 느낌.

서론이 길었는데, 결국 클라이언트는 요청 ⇒ Nginx ⇒ Route53 ⇒ CloudFront 순서로 오게 된다. 만약 CDN으로 웹 컨텐츠를 제공할 수 있으면 하고, 없으면 지정해놓은 S3의 버킷에서 가져오도록 설정되어 있다. 문제는 여기서 발생했는데, 이 지정된 버킷에는 우리 서비스의 관한 파일들(객체)이 들어있다. Typescript는 컴파일 될 떄 Javascript로 변환되어 dist 폴더에서 작동되는데, 이 변환된 파일이 담겨있다. 근데 접근을 못한다. 지정된 엔드포인트로 보내면 버킷안에 들어있는 index.html을 반환해야하는데 반환하질 않는다. 설정에는 에러가 발생할 시 띄울 것도 지정할 수 있는데 여기에도 index.html으로 설정되어있다. 따라서 클라이언트는 그냥 초기화면을 보게 되지만, 실제로는 예외처리로 인해 뜬 화면인 것. 그 이후 동작은 문제가 없다. 왜 안되나 로그를 확인해보니 403(Access Denied)가 뜬다. 보통은 버킷의 접근 권한 설정이 잘못되어 있는 경우 발생하는 오류인데, 여기에는 문제가 없다. 버킷 내의 다른 객체들(이미지 파일이나 js 파일 등)의 엔드포인트로 접속해보면 접근이 잘 되는데 index.html만 안된다. 로컬로 받아보니 안에 작성된 내용은 이상 없다. 왜 index.html에서 내부 이미지를 불러올 때 안되는걸까. 하..

회고

브라질이나 미국이나 아마존엔 모든게 다있구나. 왜 아마존이 돈을 잘버는지 개발자가 되서야 알았다. 온갖 서비스가 다있네. 그냥 럭키 교보문고인줄 알았다.