728x90
친구 관계 설정 API, Query logging, jwt 로그아웃
친구 관계 설정 API
- 이 부분을 하면서 뭔가 다양한 것을 찾아본 것 같다. 쿼리 로깅을 달고 쿼리 횟수를 보면서 쿼리 횟수를 최대한 줄이기 위해서 orm 로딩 전략도 찾아보고 많은 것을 공부해볼 수 있었다.
- api 작성 자체는 어렵지 않았다. 하지만 쿼리를 어떻게 날릴 건지에 대한 많은 고민을 했다.
- 친구 상세 조회
- 현재 로그인 된 유저와 친구 관계인지 먼저 확인하는 것이 필요했다. 그렇다면 현재 로그인 된 유저의 친구 목록을 모두 순회를 하면서 찾는 것은 비효율적이다. 그래서 조인으로 뭔가 해보려고 했다.
- 중간 관계 테이블과 유저 테이블을 조인해서 로그인 유저-목적 유저가 존재할 때만 목적 유저의 객체만 가져오도록 했다.
- 친구 목록 조회
- 현재 로그인된 유저 객체를 찾고 해당 유저.friends를 하니 쿼리가 2번 이상 날라갔다. 이게 그 N+1 문제였던가...
- 그래서 어차피 모든 친구들의 객체를 가져와야했기에 joinedload를 사용하여 쿼리 1번으로 끝낼 수 있었다.
- 친구 목록 추가/삭제
- 이 부분은 위 부분을 구현해놓으니 상대적으로 쉬웠다. 똑같은 방법으로 구현했고, orm으로 친구 관계를 넣어줘야 되어서 로그인 유저, 목적 유저를 조회하여 1개 객체를 꺼내서 서로의 친구 관계에 넣어주었다.
- 하지만 삭제에서는 좀 생각해볼 것이 남겨져 있었다. 지금은 friends 배열에서 remove 함수로 제거하고 있는데, 만약 친구 목록이 많아지면 비효율적이기 때문에... 이건 어떻게 해야될지...
- 친구 상세 조회
Query logging
- 회사에서 다른 팀 장고 프로젝트 하는 것을 보며, 단위 테스트를 돌리면서 쿼리 찍히는 것을 보며 쿼리 최적화를 진행하는 것을 보았다. 나는 여태까지 직접 쿼리를 보며 분석하지는 않았기에, 진짜 쿼리를 최적화 하려면 orm이 어떻게 실행되는 지 봐야겠다고 생각했다.
- 로깅 자체는 어렵지 않았다. 그냥 파이썬 내장 로깅 가져와서 sqlalchemy 엔진을 찍어주면 되었다.
- 파일이나 다른 방법으로 저장하는 것은 프로젝트가 좀 커지면 생각해봐야겠다.
jwt 로그아웃
사용자 로그아웃은 어떻게 처리해야되는지 궁금했다. jwt가 아닐땐 세션에서 제거해주고 했어야 했는데, 현재는 세션을 안쓰기 때문이었다. 그래서 찾아보았더니 단순히 클라이언트에서 해당 토큰을 브라우저에서 지워주면 된다고 한다.
그런데 만약에 어떠한 이유로 access 토큰이 남아있어 사용이 된다면 문제가 된다. 그래서 로그아웃 할 때 redis에 username 값으로 토큰을 저장해주고 해당 토큰이 조회되는지 확인해주었다. → 마치 블랙리스트 느낌으로, 존재한다면 그건 이미 destory 된 토큰이니깐 인증을 안해주면 된다.
근데 refresh 토큰도 해줘야하나....?
발생한 문제
- request.user를 가져와서 해당 친구 관계 가져올 시 lazy loading 관련 오류
Parent object <User> is not bound to a Session; lazy load operation of attribute cannot proceed
가requets.user.friends
로 접근할 때 문제가 발생했다.- Auth 미들웨어에서 db 세션을 엶 → 유저 객체를 꺼냄 → 해당 세션을 종료 → API 실제 처리 하는 곳에서 꺼내 씀 (여기서 db 세션을 새로 만듦) → 오류 발생
- 리서치 해 본 결과, 위의 과정에서 db 세션이 다르기에 기본적으로 lazy loading 전략으로 User 객체를 가져와서 세션이 끊겨버리므로 더이상 해당 객체에서 chaining(?)을 할 수 없어서 발생하는 오류
- 그럼 lazy loading 말고 join이나 다른 전략을 사용하면...? → 이건 매번 요청이 들어올때마다 유저에 연결된 모든 것을 가져오므로 효율적이지 않을 것 같음
- 그렇다면 미들웨어에서 사용하는 db 세션을 계속 쓰면 되겠다 라고 생각했다. → 다만 redis에 더 이상 user를 저장하지 못한다.
- 찾아본 결과 FastAPI에서 사용하는
starlette.request
에서request.state.some_value
와 같이 원하는 값을 저장할 수 있었다. 그래서request.state.db
에 넣어서 본 API 처리 함수에서 꺼내서 썼다. - 해당 문제는 해결
- Authentication 미들웨어 효율성에 관한 의문
- 위의 문제를 해결했더니 또 다른 문제가 발생했다. 친구 목록을 가져오는데 시간이 꽤 오래걸렸다.
- 혹시나 해서 쿼리 로그 기록을 봐보았다.
BEGIN (implicit)
SELECT users.id AS users_id, users.login_id AS users_login_id, users.username AS users_username, users.password AS users_password, users.nickname AS users_nickname, users.create_dt AS users_create_dt, users.update_dt AS users_update_dt
FROM users
WHERE users.id = %(pk_1)s
[generated in 0.00014s] {'pk_1': 1}
BEGIN (implicit)
SELECT users.id AS users_id, users.login_id AS users_login_id, users.username AS users_username, users.password AS users_password, users.nickname AS users_nickname, users.create_dt AS users_create_dt, users.update_dt AS users_update_dt
FROM users
WHERE users.id = %(pk_1)s
[cached since 1.909s ago] {'pk_1': 1}
SELECT users.id AS users_id, users.login_id AS users_login_id, users.username AS users_username, users.password AS users_password, users.nickname AS users_nickname, users.create_dt AS users_create_dt, users.update_dt AS users_update_dt
FROM users, friendships
WHERE %(param_1)s = friendships.user_id AND users.id = friendships.friend_id
[generated in 0.00011s] {'param_1': 1}
- 쿼리 자체는 2번 보낸 것 같다. 두번째꺼는 캐시된 것을 가져온 것 같다. → 캐시된 걸 가져오는데 꽤 오래걸린다... postman에서 2.72s가 떴다.
- 그럼 미들웨어를 안쓰고 그냥 API 함수에서 직접 꺼내서 쓰면 어떻게 되나 궁금했다.
BEGIN (implicit)
SELECT users.id AS users_id, users.login_id AS users_login_id, users.username AS users_username, users.password AS users_password, users.nickname AS users_nickname, users.create_dt AS users_create_dt, users.update_dt AS users_update_dt
FROM users
WHERE users.id = %(pk_1)s
[generated in 0.00016s] {'pk_1': 1}
SELECT users.id AS users_id, users.login_id AS users_login_id, users.username AS users_username, users.password AS users_password, users.nickname AS users_nickname, users.create_dt AS users_create_dt, users.update_dt AS users_update_dt
FROM users, friendships
WHERE %(param_1)s = friendships.user_id AND users.id = friendships.friend_id
[generated in 0.00018s] {'param_1': 1}
- 우선 쿼리는 똑같이 2번인데 캐시된걸 가져오는 것이 빠졌다 (당연히 바로 사용하니깐) → postman에서 0.611s 걸렸다.
- 위에서 캐시된 것을 가져오는 것은 API함수에서 `request.user`를 사용할 때 발생한다. 해당 쿼리를 사용하지 않게 하는 방법은 아직까지 못찾았다.
- 이번에 미들웨어를 써보는 이유가 장고처럼 편하게 현재 로그인 된 유저를 가져오고 싶어서 그랬는데, 이렇게까지 느리면 사용하지 않고, 그냥 필요할때마다 직접 해당 함수에서 사용하는 것이 나을 것 같다.
728x90
'FastAPI 채팅 개발 일지' 카테고리의 다른 글
21.04.28 친구 관계 설정, EC2, RDS, nginx, gunicorn 설정 (0) | 2021.05.01 |
---|---|
21.04.23 - 소켓 통신 테스트 및 소켓 인증 방식 리서치, Authentication / TrustedHost Middleware 추가 (0) | 2021.04.25 |
21.04.21 - 메시지 조회 및 생성 기능, 다른 api 버그 수정 (0) | 2021.04.25 |
21.04.20 - 채팅방 생성 및 조회 기능, Class Based View (0) | 2021.04.25 |
21.04.16 - 채팅에 관한 간단한 리서치, JWT 인증 구현 (0) | 2021.04.25 |