├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── db.json
├── init_zsh.sh
├── rid
└── webQuery.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | cache
3 | .idea
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "server"]
2 | path = server
3 | url = https://github.com/JerryK026/random-interview-defense-server.git
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 SeokyungKim
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🏰 랜덤 면접 디펜스 🏰
2 | 랜면디는 터미널을 열 때마다 랜덤한 웹 개발 기술면접 질문을 노출하는 서비스입니다
3 |
4 | 
5 |
6 | ## 도움말
7 | [랜면디 위키](https://github.com/JerryK026/random-interview-defense/wiki)
8 |
9 | ## 웹 버전 사용법 - 권장!
10 | 해당 레포가 업데이트 될 때마다 pull할 필요가 없습니다
11 | 받은 질문을 서버가 기억하고 알아서 답변합니다
12 |
13 | 🚨 베타버전이기 때문에 도메인 주소가 변경될 수도 있습니다
14 |
15 | 1. 해당 레포의 webQuery.sh 파일을 `~/rid/` 위치에 추가하고 실행 권한을 줍니다
16 | - `chmod +x webQuery.sh`
17 |
18 | 2. 터미널 설정 파일에 원하는 퀴즈를 등록합니다
19 | - mac : `vi ~/.zshrc`로 접속 후 마지막 줄에 다음 문장을 추가한 후 저장합니다
20 |
21 | ```bash
22 | alias rid='~/rid/webQuery.sh'
23 | echo "정답 찾는 법 : rid -r"
24 | rid
25 | ```
26 |
27 | curl에 들어갈 문장을 수정해 원하는 문제 유형을 선택할 수도 있습니다
28 |
29 | - backend + frontend + tip 질문을 받고싶은 경우 (권장)
30 | `rid`
31 | - backend 질문만 받고싶은 경우
32 | `rid -b`
33 | - frontend 질문만 받고싶은 경우
34 | `rid -f`
35 |
36 | 위 명령어를 직접 입력해서 원할 때 질문을 받을 수도 있습니다
37 |
38 | 3. alias를 등록합니다
39 | ```bash
40 | source ~/.zshrc
41 | ```
42 |
43 | ## 로컬 버전 사용법
44 | **1. 레포를 클론합니다**
45 | ```
46 | git clone https://github.com/JerryK026/Random-Interview-Defense.git ~/rid
47 | ```
48 |
49 | **2. 초기화 파일을 실행 후 터미널을 재시작합니다.**
50 | ```
51 | sh ./init_zsh.sh
52 | ```
53 |
54 | 💡 `~.zshrc`에 설정 정보를 등록하니 추후에 문제 발생시 해당 부분을 지워주세요.
55 |
56 | 💡 Powerlevel10k 사용 시 instant prompt 옵션을 종료해야 콘솔 출력 경고가 발생하지 않습니다 [참고](https://github.com/JerryK026/random-interview-defense/issues/7)
57 |
58 | ## 나만의 질문 업로드 가이드
59 | `db.json` 파일에 질문 / 답변이 등록되어 있습니다. 양식에 맞게 커스텀 질문 / 답변을 추가할 수 있습니다.
60 |
61 |
62 | ## 질문 리스트
63 | 질문 리스트는 [위키](https://github.com/JerryK026/random-interview-defense/wiki)에서 확인할 수 있습니다!
64 |
65 | 외에도 원하는 자료나 검수할 내용이 있다면 [이슈](https://github.com/JerryK026/random-interview-defense/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) 탭에서 자유롭게 신청해 주세요
66 |
67 | ## 기여 가이드
68 | 면접 질문 / 팁 / 답안에 대한 컨텐츠 추가 혹은 피드백 모두 적극 환영합니다!
69 | 기여 시, 해당 자료에 기여해 주신 분들의 github id를 추가해 드립니다 :D
70 | - Issue로 기여
71 | 1. 새로운 이슈를 발행하고 `🏆 새 자료`, `🚨 검수가 필요해요` 등 상황에 맞는 라벨을 부착합니다
72 | 2. 수정 / 피드백의 경우 이슈 코멘트에 컨텐츠 내용 변경에 대한 이유를 추가적으로 기술합니다
73 | - Pull request로 기여
74 | 1. 레포를 추가한 뒤 `나만의 질문 업로드 가이드` 양식에 맞추어 자료를 추가한 뒤, 문제 혹은 팁 마지막 줄에 출처와 자신의 github id를 적습니다
75 | - ex) 출제자 : JerryK026 | 출처 : xxx
76 | 2. 커밋 메세지 양식은 다음과 같습니다
77 | - 새 자료 : `new-content : [backend / frontend / tip] [자료 번호] 자료 설명`
78 | - 자료 수정 : `fix-content : [backend / frontend / tip] [자료 번호] 자료 설명`
79 | 3. PR 요청을 보내면서 수정 / 피드백의 경우 PR 코멘트에 컨텐츠 내용 변경에 대한 이유를 추가적으로 기술합니다
80 |
--------------------------------------------------------------------------------
/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "backend": {
3 | "1": {
4 | "quiz": "네트워크 소켓이란 무엇인가요?",
5 | "answer": "네트워크 소켓이란 응용 프로그램이 전송 계층에 접근할 수 있도록 파일 형식으로 추상화한 인터페이스입니다. 네트워크로 연결된 컴퓨터(호스트) 간 통신하기 위해 필요합니다.",
6 | "contributor": [],
7 | "category": ["네트워크"]
8 | },
9 | "2": {
10 | "quiz": "웹 서버 환경에서 보편적으로 CPU 부하 분산보다 I/O 부하 분산처리가 더 어려운 이유는 무엇인가요?",
11 | "answer": "CPU 부하의 경우 서버를 추가하고 로드 밸런서로 부하를 균등하게 분산해 비교적 간단하게 해결할 수 있습니다.\n\n그러나 I/O 부하의 경우 입출력 요청을 여러 기기에서 사용하기 때문에 동시성에 대한 고려가 추가적으로 필요합니다.\n",
12 | "contributor": [],
13 | "category": ["시스템 디자인"]
14 | },
15 | "3": {
16 | "quiz": "A 레코드와 CNAME 레코드, MX 레코드에 대해 들어보신 적 있으신가요?",
17 | "answer": "언급하신 키워드는 모두 DNS 레코드 종류들입니다\nDNS 레코드들은 DNS 서버가 해당 패킷을 받았을 때 도메인 이름을 IP로 매핑하기 위해 처리할 방법에 대해 지침합니다\n\"A 레코드\"는 IPv4 주소를 가진 호스트를 찾기 위해 사용됩니다\n\"CNAME 레코드\"는 한 도메인에 alias를 부여해 다양한 도메인으로 접근할 수 있도록 할 때 사용됩니다\n\"MX 레코드\"는 수신할 메일의 메일 서버를 찾기 위해 사용됩니다\n언급하신 키워드 외에도 AAAA레코드, NS 레코드, SOA 레코드, PTR 레코드, TXT 레코드, SRV 레코드 등이 있습니다\n",
18 | "contributor": [],
19 | "category": ["네트워크"]
20 | },
21 | "4": {
22 | "quiz": "인덱스를 구성할 때 이진트리가 아닌 B+트리를 사용했을 때의 장점은 무엇이 있을까요?",
23 | "answer": "1. B+트리는 갱신작업 후에 트리 구조가 스스로 변하기 때문에 O(logN)의 검색 속도를 보장합니다. 이진트리의 경우 노드 값에 의한 재정렬을 하지 않기 때문에 최악의 경우 선형 탐색 O(N)의 성능을 가집니다.\n\n2. B+트리는 노드당 데이터 수를 변경할 수 있어, 노드의 크기를 조절할 수 있습니다. 따라서, 노드 크기를 4KB 혹은 8KB인 페이지 크기로 만들어 디스크 IO를 최소화할 수 있습니다.\n\n3. B+트리는 리프 노드가 링크드 리스트로 연결되어 있어 인접한 노드들에 대해 순차적으로 접근할 수 있습니다. 따라서 디스크 랜덤 읽기가 아닌 순차 읽기가 가능합니다. 이로 인해 디스크 I/O가 줄어들어 빠른 속도를 보장합니다.\n",
24 | "contributor": [],
25 | "category": ["데이터베이스"]
26 | },
27 | "5": {
28 | "quiz": "DB가 JOIN을 수행하는 방법에는 어떠한 것들이 있을까요?",
29 | "answer": "NL(Nested Loop), Sort-Merge, Hash 방식이 있습니다\nNL은 드라이빙 테이블의 조건 컬럼에 해당하는 레코드 1건마다 드리븐 테이블 조건 컬럼을 각각 탐색해 반복문 형태를 취하는 조인 형태입니다. 가장 많이 쓰이는 형태의 조인이며, 드라이빙 테이블에서 드리븐 테이블의 조건 레코드 수 만큼 검색하므로 주로 드라이빙 테이블보다 드리븐 테이블의 레코드 수가 많을 때 성능이 좋습니다\n\nSort-Merge는 조건에 맞는 행을 찾아 정렬한 뒤 비교하는 방식입니다\n\nHash은 조인 대상 테이블 중 레코드 수가 적은 테이블을 해시 테이블로 만들어 조인을 수행합니다\n\n주로 응답 속도가 중요한 OLTP 연산에선 NL, 처리량이 많이 필요한 방식에선 Hash가 유용합니다",
30 | "contributor": [],
31 | "category": ["데이터베이스"]
32 | },
33 | "6": {
34 | "quiz": "컴퓨터를 막 부팅했을 때, 컴퓨터 성능이 느린 이유는 무엇일까요?",
35 | "answer": "프로세스는 기본적으로 메모리에 올라와 있는 정보만 이용할 수 있습니다\n\n그러나 메모리는 휘발성 영역이기 때문에 컴퓨터 전원을 껐다가 켰을 경우 캐싱되어 있는 데이터가 없기 때문에 디스크로부터 필요한 읽기 작업을 수행한 후에 일정한 성능을 얻을 수 있습니다.",
36 | "contributor": [],
37 | "category": ["운영체제"]
38 | },
39 | "7": {
40 | "quiz": "트랜잭션 격리 수준에서 Read Committed와 Repeatable Read 수준의 차이점은 무엇일까요?",
41 | "answer": "Read Committed 수준은 Oracle에서 기본으로 사용되며, 아직 커밋되지 않은 트랜잭션에서 수정한 데이터를 읽을 수 있는 \"Dirty Read\" 문제를 해결합니다. 그러나 다른 트랜잭션에서 수정하고 커밋 완료한 경우 현재 트랜잭션에서 다시 읽은 값이 달라질 수 있는 \"Non-Repeatable Read\"라는 문제가 발생합니다\n\nRepeatable Read 격리 수준은 MySQL의 InnoDB에서 기본으로 사용되며 트랜잭션 마다 버전을 기입해서 현재 트랜잭션보다 낮은 버전의 데이터만 보게 해 Non-Repeatable Read 문제를 해결합니다. 그러나 쓰기 잠금을 거는 경우 다른 트랜잭션에 의해 수정된 레코드가 보이기도 하는 \"Phantom Read\"라는 현상을 겪을 수도 있습니다\n",
42 | "contributor": [],
43 | "category": ["데이터베이스"]
44 | },
45 | "8": {
46 | "quiz": "InnoDB의 경우 Repeatable Read 수준에서도 Phantom Read 현상이 발생하지 않는 이유가 무엇인가요?",
47 | "answer": "InnoDB는 레코드 단위의 인덱스 레코드 락을 제공하는데 이때 갭 락을 사용합니다. 갭 락은 목적 레코드 사이에 모두 읽기 잠금을 걸기 때문에 쓰기 작업을 수행할 수 없어 Phantom Read 현상이 발생하지 않습니다\n\n별개로, 갭 락은 데드락 현상의 주원인이기도 하므로 가능한 줄이는 것이 좋습니다",
48 | "contributor": [],
49 | "category": ["데이터베이스"]
50 | },
51 | "9": {
52 | "quiz": "네트워크를 통해 인터넷으로 데이터를 전송할 때의 흐름을 프로세스부터 설명해 보시겠어요?",
53 | "answer": "애플리케이션 프로세스에서 Socket에 \"Stream\" 데이터를 쓰기 작업해 전송 계층으로 보냅니다.\n\n전송 계층에서는 Stream 데이터를 MSS(Maximum Segment Size)를 기준으로 잘라 \"Segment\"로 만드는 \"Segmentation\" 후 Network 계층으로 보냅니다.\n\nNetwork 계층에서 Segment를 인터넷으로 전송 가능한 형태의 default 1500 byte인 \"Packet\"으로 만들어 데이터 링크 계층으로 보냅니다.\n\n데이터 링크 계층에서는 패킷을 \"Frame\"으로 \"Encapsulation\"해 인터넷으로 전송합니다.\n\n참고 : https://www.youtube.com/watch?v=p6ASAAMwgd8",
54 | "contributor": [],
55 | "category": ["네트워크"]
56 | },
57 | "10": {
58 | "quiz": "HTTP 프로토콜이란 무엇인가요?",
59 | "answer": "HTTP란 Hyper Text Transfer Protocol의 약자입니다. 이는 HTML파일을 주고받기 위한 약속이며, HTML을 요청하는 주체를 클라이언트라고 부르고 제공하는 주체를 웹 서버라고 부릅니다.\n\n클라이언트는 보편적으로 브라우저 형태를 띄며 추가적으로 동적으로 변화하는 데이터에 대한 정보를 제공하는 서버를 WAS라고 부릅니다.\n\nHTTP는 보편적으로 TCP/IP 기반이고 버전 별로 0.9 / 1.0 / 1.1 / 2.0 / 3.0이 있으며 공식 표준 버전은 1.1입니다. \n\nHTTP 프토콜은 클라이언트가 요청하면 서버가 응답하는 형태를 띄며, 패킷에는 원하는 동작인 \"메서드\", 서버의 위치인 \"주소\", \"HTTP 버전\", 요청에 대한 추가 정보인 \"헤더\", 본문인 \"body\"가 들어있습니다.\n\n응답 리퀘스트의 경우 요청 결과를 나타내는 \"상태 코드\"가 추가적으로 들어갑니다",
60 | "contributor": [],
61 | "category": ["네트워크"]
62 | },
63 | "11": {
64 | "quiz": "N+1 문제란 무엇이고 왜 발생하며 JPA에서는 어떻게 해결할 수 있을까요?",
65 | "answer": "N+1 문제란 연관 관계를 가진 엔티티를 조회할 때 발생하는 문제입니다.\n\n해당 엔티티를 조회(1)했을 때, 해당 엔티티의 레코드 수 만큼 추가로 쿼리(N)를 날려야 하기 때문에 발생합니다. 이는 N+1번의 쿼리를 요청하게 되고 이는 심각한 성능 이슈를 유발합니다\n\n이를 해결하기 위해서는 기본적으로 모든 연관관계를 Lazy Loading으로 설정하고 연관 테이블이 필요할 때는 fetch join하는 방법으로 해결할 수 있습니다. fetch join은 관련된 레코드들을 한 번에 읽어오기 때문입니다. 혹은 EntityGraph기능을 사용해 해결할 수도 있습니다\n\nx대다 관계에서는 데이터 수가 불어나는 현상을 가지기 때문에 BatchSize를 추가로 적용해야 합니다\n\n출처 : 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화",
66 | "contributor": [],
67 | "category": ["Java", "데이터베이스"]
68 | }
69 | },
70 | "frontend": {
71 | "1": {
72 | "quiz": "실행 컨텍스트에 대해 설명해 주세요",
73 | "answer": "실행할 코드에 제공할 환경 정보(context)들을 모아놓은 객체를 말합니다.\n\nJS 엔진은 해당 컨텍스트와 관련된 코드 실행에 필요한 환경 정보들을 수집해 실행 컨텍스트에 저장합니다.\n\n실행 컨텍스트는 다음 3가지 프로퍼티를 가집니다.\n\nVariable Object : 선언된 변수, 함수들이 저장되는 객체입니다.\n\nScope Chain : 전역 객체부터 이어진 각 함수의 스코프 참조를 차례로 저장해 해당 함수가 참조할 수 있는 변수, 함수 선언 등의 정보를 담은 리스트입니다. 엔진은 스코프 체인을 통해 렉시컬 스코프를 파악합니다.\n\nthis value : 함수 호출 패턴에 의해 결정된 this 값이 할당됩니다.",
74 | "contributor": ["aeong98"],
75 | "category": ["JS"]
76 | },
77 | "2": {
78 | "quiz": "호이스팅에 대해 설명해 주세요",
79 | "answer": "인터프리터가 변수와 함수의 메모리 공간을 선언 전 미리 할당해 선언부를 코드의 최상단으로 끌어올리는 것(hoisting)을 의미합니다.\n\n이 때문에 선언하기 전의 변수임에도 불구하고 참조할 수 있는 현상이 발생합니다.",
80 | "contributor": ["aeong98"],
81 | "category": ["JS"]
82 | },
83 | "3": {
84 | "quiz": "JS에서 this에 대해 설명해 주세요",
85 | "answer": "this는 함수를 호출하는 객체에 대한 참조를 말하며, 어떻게 호출되었느냐에 따라 결정됩니다.\n\n전역 함수에서 호출되었다면 window 객체, 일반 / 내부 함수에서는 window 객체(strict mode라면 undefined), 객체 메서드에서는 해당 객체, 프로토 타입에서는 호출한 객체, 생성자 함수에서는 새로 생성된 객체를 참조합니다.\n\ncall, apply, bind 함수를 통해 this를 원하는 객체에 연결할 수 있습니다.",
86 | "contributor": ["aeong98"],
87 | "category": ["JS"]
88 | },
89 | "4": {
90 | "quiz": "CSR과 SSR의 차이는 무엇인가요?",
91 | "answer": "SSR(Server Side Rendering)은 서버가 클라이언트에게 서버 단에서 완전히 구성된 HTML 파일을 만들어 static한 웹 페이지를 제공하는 방법입니다\n장점 : SEO 최적화에 유리하며, 초기 로딩이 빠릅니다\n단점 : 매 페이지 요청마다 새로고침되기 떄문에 사용자 경험이 다소 떨어집니다\n\nCSR(Client Side Rendering)은 클라이언트에서 Ajax 등을 활용해 json과 같은 데이터를 fetching해 동적으로 HTML 문서를 만드는 방법입니다\n장점 : 클라이언트 액션이나 상황에 따라 동적 페이지 구현에 유리하며, 서버 부담이 적고 초기 로딩 이후 이동 / 상호작용이 빠릅니다\n단점 : SEO가 불리하며, 초기 로딩 속도가 느립니다\n",
92 | "contributor": ["aeong98"],
93 | "category": ["시스템 디자인"]
94 | },
95 | "5": {
96 | "quiz": "이벤트 루프와 태스크 큐에 대해 설명해 주세요",
97 | "answer": "V8과 같은 JS 엔진은 단일 콜스택을 사용하며 요청이 들어올 때마다 순차적으로 스택에 담아 처리합니다. 브라우저 혹은 Node.js는 태스크 큐를 사용해 이벤트를 핸들링하고, 이벤트 루프는 태스크 큐에 대기중인 함수를 콜스택으로 이동시킵니다.\n\n추가로 JS가 싱글 스레드라고 불리우는 이유는 콜스택과 이벤트 루프 동작 특성 상 하나의 코드가 실행되는 동안 다른 코드를 실행할 수 없다는 특징을 말하는 것이지, 스레드 하나에서 모든 작업을 수행하는 것은 아닙니다.",
98 | "contributor": ["aeong98"],
99 | "category": ["JS"]
100 | },
101 | "6": {
102 | "quiz": "CORS가 무엇인지 말씀해 주세요",
103 | "answer": "CORS(Cross-Origin Resource Sharing)는 교차 출처 리소스 공유라고 불리웁니다.\n\n브라우저는 \"동일 출처 원칙\"에 의거하여 API 요청 시 브라우저의 현재 주소와 API 주소의 도메인이 일치(Scheme, Host, Port)해야만 데이터로 접근할 수 있습니다. 만약 다른 도메인에서 API를 요청해 사용할 수 있게 하려면 서버에서 Access-Control-Allow-Origin 헤더에 대한 설정이 필요합니다.",
104 | "contributor": ["aeong98"],
105 | "category": ["네트워크"]
106 | },
107 | "7": {
108 | "quiz": "브라우저 렌더링 과정에 대해 설명해 주세요",
109 | "answer": "1. 브라우저는 HTML, CSS, JS와 같은 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받습니다.\n\n2. 브라우저 렌더링 엔진은 HTML, CSS를 파싱해 자료구조인 DOM, CSSDOM을 생성하고 이를 결합한 렌더 트리를 생성합니다.\n\n3. JS 엔진이 JS를 파싱해 AST를 생성하고 바이트 코드로 변환해 실행합니다. 이때 DOM API를 통해 DOM, CSSDOM 변경이 가능하며 변경된 자료구조는 렌더 트리로 결합됩니다.\n\n4. 렌더 트리를 기반으로 HTML 요소 레이아웃(위치와 크기)를 계산하고 브라우저 화면에 HTML 요소를 페인팅합니다.",
110 | "contributor": ["aeong98"],
111 | "category": ["네트워크"]
112 | }
113 | },
114 | "tip": {
115 | "1": {
116 | "contents": "메모리는 디스크보다 읽기 성능이 10^5 ~ 10^6배 빠릅니다.",
117 | "contributor": [],
118 | "category": ["운영체제"]
119 | },
120 | "2": {
121 | "contents": "java에서 배열을 복사하는 방법은 Object.clone()과 System.arraycopy 2가지 방법이 있습니다.\n\nclone()은 deep copy이나, 성능이 느립니다.\n\narraycopy()는 shallow copy이나 성능이 빠릅니다.",
122 | "contributor": [],
123 | "category": ["Java"]
124 | },
125 | "3": {
126 | "contents": "Phantom Read란 주로 언두로그가 아닌 레코드를 읽을 때 발생합니다.\n\n레코드는 버전 컨트롤의 대상이 되지 못하기 때문에 다른 트랜잭션에서 언두 로그에 쓰기 잠금을 걸면 현재 트랜잭션은 당장 읽을 수 있는 실제 레코드에서 데이터를 읽어옵니다. 이는 다른 트랜잭션에서 변경했을 가능성이 있습니다.\n\n+) InnoDB의 Repeatable Read에서 Phantom Read가 발생하지 않는 이유는 Gap Lock을 지원하기 때문입니다.\n\nGap Lock은 읽은 레코드들 사이에 읽기 잠금을 걸기 때문에 다른 곳에서 쓰기 잠금을 걸지 못합니다.\n\n따라서 트랜잭션은 해당 레코드에 대해 같은 레코드 읽기를 보장 받습니다.",
127 | "contributor": [],
128 | "category": ["데이터베이스"]
129 | },
130 | "4": {
131 | "contents": "JPA를 사용할 때는 엔티티를 직접 캐싱해선 안 됩니다. 영속성 컨텍스트가 관리하는 걸 캐시가 다시 관리하면 정합성 문제가 발생하기 때문에 DTO로 변환해 캐싱해야 합니다.\n\n참고 : 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화",
132 | "contributor": [],
133 | "category": ["Java"]
134 | },
135 | "5": {
136 | "contents": "JPA에서 OSIV(OpenSessionInView)란, 트랜잭션 밖에서도 영속성 컨텍스트가 해당 엔티티를 관리하기 위해 DB 커넥션을 가지는 기능을 말합니다. OSIV가 켜져 있을 땐 커넥션이 마르기 쉽기 때문에 실시간 서비스에서는 해당 기능을 꺼놓고 프레젠테이션 계층으로 보내기 전 데이터를 모두 조회 후 내보내는 것이 좋습니다\n\n참고 : 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화",
137 | "contributor": [],
138 | "category": ["Java"]
139 | },
140 | "6": {
141 | "contents": "SQL에서 AND로 연결된 경우에는 두 조건 중 하나라도 인덱스를 사용할 수 있으면 인덱스 스캔을 사용합니다. 그러나, OR의 경우 둘 중 하나라도 인덱스를 사용하지 못하면 풀 테이블 스캔합니다.",
142 | "contributor": [],
143 | "category": ["데이터베이스"]
144 | },
145 | "7": {
146 | "contents": "CQS(Command Query Seperation) 패턴이란 모든 객체의 메서드를 쓰기 작업을 수행하는 \"command\"와 읽기 작업을 수행하는 \"query\"로 분리하는 패턴입니다.\n\n하나의 메서드는 command면서 query일 수 없으며, CQS를 지킬 시 부수효과가 일어나는 메서드를 분리할 수 있어 유지보수하기 쉬워지지만 간단한 코드도 복잡해질 수 있다는 단점을 가집니다.",
147 | "contributor": [],
148 | "category": ["시스템 디자인"]
149 | },
150 | "8": {
151 | "contents": "집계함수 / GROUP BY / DISTINCT / SELECT 절 / 사용자 변수 / UNION을 포함하는 절 등에 해당하는 서브쿼리는 가능하면 외부 쿼리로 수동 병합하는 것이 성능 향상에 도움이 됩니다\n\n참고 : Real MySQL 8.0 1권",
152 | "contributor": [],
153 | "category": ["데이터베이스"]
154 | },
155 | "9": {
156 | "contents": "인덱스를 활용한 정렬은 읽는 순간에 이미 정렬되어 있기 때문에 빠른 속도로 정렬할 수 있습니다. 이때 B-Tree 계열의 인덱스를 통해서만 정렬이 가능합니다.",
157 | "contributor": [],
158 | "category": ["데이터베이스"]
159 | },
160 | "10": {
161 | "contents": "MySQL에선 GROUP BY에 사용된 조건은 인덱스 처리할 수 없어, HAVING 절 튜닝을 위한 인덱스를 고려할 필요는 없습니다\n\n참고 : Real MySQL 8.0 1권",
162 | "contributor": [],
163 | "category": ["데이터베이스"]
164 | },
165 | "11": {
166 | "contents": "MySQL에서 정렬 작업을 수행하는 방식은 인덱스 정렬과 Filesort 2가지 방식이 있습니다.\n\n인덱스 정렬의 경우 이미 정렬된 상태로 레코드들을 읽어오기 때문에 성능이 빠릅니다. 이진 트리 검색만 하면 되니 O(logN) 성능을 보입니다.\n\nFilesort는 쿼리 실행 시점에 정렬을 수행해야 합니다. 따라서 정렬 과정인 O(NlogN) 작업이 추가로 필요합니다.\n\n참고 : Real MySQL 8.0 1권",
167 | "contributor": [],
168 | "category": ["데이터베이스"]
169 | },
170 | "12": {
171 | "contents": "SELECT 쿼리를 날릴 때는 *이 아닌 꼭 필요한 칼럼을 직접 명시해 조회하는 것이 좋습니다. *를 사용할 경우 정렬 버퍼를 몇배에서 몇십 배까지 비효율적으로 사용할 가능성이 큽니다.\n\n또한, 임시 테이블이 필요한 쿼리에도 영향을 미칩니다.\n\n참고 : Real MySQL 8.0 1권",
172 | "contributor": [],
173 | "category": ["데이터베이스"]
174 | },
175 | "13": {
176 | "contents": "SQL에서 LIMIT은 레코드를 일부만 가져올 수 있게 만들기 때문에 작업량을 줄여 성능이 향상됩니다.\n\n그러나 ORDER BY, GROUP BY 같은 작업은 조건에 만족하는 레코드 전부를 읽어와 버퍼에서 정렬 / 그루핑 작업을 수행한 후에야 LIMIT 작업을 할 수 있습니다.\n\n따라서 해당 작업들은 성능이 좋지 않을 수 있습니다.\n\n참고 : Real MySQL 8.0 1권",
177 | "contributor": [],
178 | "category": ["데이터베이스"]
179 | }
180 | }
181 | }
--------------------------------------------------------------------------------
/init_zsh.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | file_path="$(pwd)"
4 | file_name="rid"
5 |
6 | chmod +x "${file_path}/${file_name}"
7 |
8 | echo "\n# 랜면디 정보 등록 시작" >> ~/.zshrc
9 | echo "echo '랜면디 명령어 정보 : rid -h'" >> ~/.zshrc
10 | echo "export RID_PATH='${file_path}'" >> ~/.zshrc
11 | echo "alias rid='$RID_PATH/${file_name}'" >> ~/.zshrc
12 | echo "rid" >> ~/.zshrc
13 | echo "# 랜면디 정보 등록 끝\n" >> ~/.zshrc
14 | echo "랜면디 초기화 완료!"
--------------------------------------------------------------------------------
/rid:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import json
4 | import random
5 | import sys
6 | import os
7 |
8 |
9 | # todo: 검색 기능 추가
10 | def load_json(file_path):
11 | try:
12 | with open(file_path, 'r') as f:
13 | return json.load(f)
14 | except FileNotFoundError:
15 | print('db.json 파일이 존재하지 않습니다. https://github.com/JerryK026/random-interview-defense에서 최신의 파일을 설치해 주세요.')
16 | return
17 |
18 |
19 | def parse_cache(line):
20 | split = line.split()
21 | return {'type': split[0], 'id': split[1]}
22 |
23 |
24 | def load_cache(file_path):
25 | try:
26 | with open(file_path, 'r') as f:
27 | return parse_cache(f.readline())
28 | except FileNotFoundError:
29 | return
30 |
31 |
32 | def save_cache(file_path, type, id):
33 | f = open(file_path, 'w')
34 | cache_str = str(type) + " " + str(id)
35 | f.write(cache_str)
36 |
37 |
38 | class Query:
39 | def __init__(self):
40 | self.question_query_type_table = {"-b": "backend", "-f": "frontend", "-t": "tip"}
41 | self.question_type_command = None if len(sys.argv) <= 1 else sys.argv[1]
42 | self.question_id = None if len(sys.argv) <= 2 else sys.argv[2]
43 | self.content = load_json(os.environ['RID_PATH'] + "/db.json")
44 | self.cache = load_cache(os.environ['RID_PATH'] + "/cache")
45 |
46 | def extract_total(self):
47 | b_exclude = 1 + len(self.content['backend'])
48 | f_exclude = len(self.content['frontend']) + b_exclude
49 | t_exclude = len(self.content['tip']) + f_exclude
50 |
51 | target = random.randrange(1, t_exclude)
52 |
53 | if target < b_exclude:
54 | return self.get_quiz("backend", target)
55 |
56 | if target < f_exclude:
57 | return self.get_quiz("frontend", target - b_exclude + 1)
58 |
59 | if target < t_exclude:
60 | return self.get_quiz("tip", target - f_exclude + 1)
61 |
62 | raise Exception("잘못된 수를 추출했습니다.")
63 |
64 | def extract_single(self, question_type):
65 | quiz_id = random.randrange(1, len(self.content) + 1)
66 |
67 | return self.get_quiz(question_type, str(quiz_id))
68 |
69 | def get_quiz(self, question_type, quiz_id):
70 | quiz_id = str(quiz_id)
71 | if question_type != "tip":
72 | save_cache(os.environ['RID_PATH'] + '/cache', question_type, quiz_id)
73 |
74 | return quiz_id, self.content[question_type][quiz_id]
75 |
76 | def dispatcher(self):
77 | question_query_type_table = {"-b": "backend", "-f": "frontend", "-t": "tip"}
78 |
79 | if self.question_type_command is None:
80 | return self.extract_total()
81 |
82 | if self.question_type_command in question_query_type_table:
83 | if self.question_id is None:
84 | return self.extract_single(question_query_type_table[self.question_type_command])
85 |
86 | return self.get_quiz(question_query_type_table[self.question_type_command], self.question_id)
87 |
88 | if self.question_type_command == "-r":
89 | return self.get_quiz(self.cache['type'], self.cache['id'])
90 |
91 | if self.question_type_command == "-c":
92 | return None, None
93 |
94 | print("rid [type]: 해당 타입의 문제에 대한 질문 요청\n-b: 백엔드 질문 -f: 프론트 엔드 질문 -t: 기타 TIL 팁 정보 -h: 명령어 요청 -c: 카테고리별 컨텐츠 검색"
95 | "\nex) "
96 | "rid -b\nrid [type] [number]: 해당 문제에 대한 답변 요청\nex) rid -f 5")
97 | exit()
98 |
99 | def category_target_factory(self, question_type):
100 | if question_type in ["backend", "frontend"]:
101 | return "quiz"
102 |
103 | else:
104 | return "contents"
105 |
106 | # 매번 전역에 카테고리를 올리는 것은 비효율적이므로 필요할 때만 검색하도록 호출하는 방식
107 | def get_categories(self):
108 | keyword = self.question_id
109 | quiz_type_list = ["backend", "frontend", "tip"]
110 | output = ""
111 |
112 | for question_type in quiz_type_list:
113 | category_quiz_store = []
114 | output += question_type + "\n---------------\n"
115 | target = self.category_target_factory(question_type)
116 | for quiz_idx in self.content[question_type]:
117 | if keyword in self.content[question_type][quiz_idx]["category"]:
118 | category_quiz_store.append("Q." + quiz_idx + " " + self.content[question_type][quiz_idx][target] +
119 | "\n")
120 |
121 | if len(category_quiz_store) == 0:
122 | output += "없음"
123 |
124 | else:
125 | for out in category_quiz_store:
126 | output += out
127 |
128 | output += "\n---------------\n"
129 |
130 | return output
131 |
132 | def ask(self):
133 | quiz_id, contents = self.dispatcher()
134 | try :
135 | output_str = self.question_query_type_table[self.question_type_command] + " "
136 | # rid, rid -r, rid -c을 위한 예외처리
137 | except:
138 | output_str = ""
139 |
140 | # tip
141 | if self.question_type_command == '-t':
142 | output_str += 'Q' + quiz_id + '.' + contents['contents']
143 | if contents['contributor']:
144 | output_str += '\ncontributor : ', str(contents['contributor'])
145 |
146 | elif self.question_type_command == '-c':
147 | if self.question_id is None:
148 | output_str += "유효 카테고리 : [네트워크, 데이터베이스, 운영체제, 시스템 디자인, Java, JS]"
149 |
150 | else:
151 | output_str += self.get_categories()
152 |
153 | # quiz
154 | else:
155 | if "quiz" in contents:
156 | output_contents = contents['quiz']
157 | else:
158 | output_contents = contents['contents']
159 | output_str += 'Q' + quiz_id + '.' + output_contents
160 | if self.question_id is not None or self.question_type_command == '-r':
161 | output_str += '\nA. ' + contents['answer']
162 |
163 | if contents['contributor']:
164 | output_str += '\ncontributor : ' + str(contents['contributor'])
165 |
166 | print(output_str)
167 |
168 |
169 | if __name__ == '__main__':
170 | Query().ask()
171 |
--------------------------------------------------------------------------------
/webQuery.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | TARGET="www.rid.r-e.kr"
4 |
5 | if [ -z "$1" ]; then
6 | curl "$TARGET/request"
7 | exit 0
8 | fi
9 |
10 | if [ $1 == "-r" ]; then
11 | TARGET="${TARGET}/response"
12 | curl $TARGET
13 | exit 0
14 | fi
15 |
16 | if [ $1 == "-b" ]; then
17 | TARGET="${TARGET}/request/backend"
18 | elif [ $1 == "-f" ]; then
19 | TARGET="${TARGET}/request/frontend"
20 | fi
21 |
22 | curl $TARGET
23 |
--------------------------------------------------------------------------------