책 목록을 한 화면에 두 권씩 보여 준다고 하겠습니다. 다음 버튼을 눌렀을 때는 이어지는 두 권이 필요합니다. Pagination은 긴 목록을 여러 화면으로 나누어 읽는 방법입니다. 이번에는 앞에서 몇 행을 건너뛰는 방법과, 마지막으로 읽은 값을 기억하는 방법을 작은 표에서 확인하겠습니다. DB(Database)는 자료를 저장하고 찾는 시스템입니다. 표의 한 줄은 행, 가격이나 번호처럼 같은 종류의 값을 담는 항목은 열이라고 부릅니다. SQL은 DB에 작업을 요청하는 언어입니다. 아래 예제의 이름과 가격은 설명을 위해 만든 자료입니다. 먼저 결과 순서를 정하기 책 번호 가격(원) 1 100 2 200 3 200 4 300 5 400 6 500 가격이 같은 책 2와 3도 차례를 정해야 합니다. 여기서는 가격이 작은 순서로 읽고, 같은 가격 안에서는 책 번호가 작은 순서로 읽겠습니다. 서로 다른 행을 구분하는 번호까지 정렬 기준에 넣으면 이 예제의 차례가 하나로 정해집니다. ORDER BY 는 결과 순서를 지정합니다. LIMIT 는 반환할 행 수를 제한하고, OFFSET 은 결과 앞에서 건너뛸 행 수를 정합니다. 두 번째 화면을 읽으려면 두 행을 건너뛰고 두 행을 받습니다. 결과 순서를 정해 사용하는 이유는 PostgreSQL 18 LIMIT과 OFFSET 설명 에서도 확인할 수 있습니다. PostgreSQL은 DB 관리 프로그램입니다. 마지막 값으로 다음 화면 찾기 Keyset Pagination은 마지막으로 읽은 정렬 기준값을 기억하고, 그 뒤의 행을 찾는 방법입니다. 첫 화면에서 책 1과 2를 읽었다면 마지막 기준은 (가격 200, 번호 2) 입니다. 여기서부터 이어지는 두 행을 요청합니다. (price, id) > (200, 2) 라는 조건은 두 값을 왼쪽부터 비교합니다. 가격이 200을 넘는 행이 이어집니다. 가격이 200으로 같으면 번호가 2를 넘는 행이 이어집니다. 따라서 가격 200인 책 3도 다음 화면에 들어갑니다. SQLite 여러 값 비교와 목록 조회 그림의 세로 구분선은 첫 화면의 마지막 책을 표시합니다. 가격과 번호를 함께 기억하므로 같은 가격의 책도 이어서 읽을 수 있습니다. 그림은 결과의 차례를 보여 줍니다. 실제 저장 공간과 읽은 횟수는 별도 확인이 필요합니다. 같은 표에서 두 요청 실행하기 SQLite도 DB 관리 프로그램입니다. 이 실험에서는 실행 중 자료를 담는 Memory 공간에 새 SQLite DB를 만들었습니다. 다음 SQL을 같은 예제 DB에서 순서대로 실행합니다. CREATE TABLE book ( id INTEGER PRIMARY KEY, price INTEGER NOT NULL ); INSERT INTO book VALUES (1, 100), (2, 200), (3, 200), (4, 300), (5, 400), (6, 500); CREATE INDEX book_price_id_idx ON book(price, id); SELECT id, price FROM book ORDER BY price, id LIMIT 2; SELECT id, price FROM book ORDER BY price, id LIMIT 2 OFFSET 2; SELECT id, price FROM book WHERE (price, id) > (200, 2) ORDER BY price, id LIMIT 2; CREATE TABLE 은 표를 만들고, INSERT 는 행을 넣습니다. INTEGER 는 정수를 담는 열을 선언합니다. PRIMARY KEY 는 각 행을 구분하는 기본 열입니다. NOT NULL 은 값이 없음을 나타내는 NULL 의 입력을 제한합니다. CREATE INDEX 는 원하는 행을 찾는 보조 자료구조인 Index를 만듭니다. 여기서는 가격과 번호의 순서로 여러 열을 묶었습니다. SELECT 는 읽을 열을, FROM 은 읽을 표를 정합니다. WHERE 는 남길 행의 조건입니다. 첫 조회는 (1, 100) , (2, 200) 을 반환합니다. 뒤의 두 조회는 모두 (3, 200) , (4, 300) 을 반환합니다. 자료가 그대로인 이 시점에는 두 방법으로 같은 다음 화면을 얻습니다. 화면 사이에 책이 추가되면 첫 화면을 읽은 뒤, 가격 50원인 책 7이 추가됐다고 하겠습니다. 새 책은 가격순 목록의 맨 앞에 들어갑니다. 두 행을 건너뛰는 기준은 새 목록에 적용됩니다. INSERT INTO book VALUES (7, 50); SELECT id, price FROM book ORDER BY price, id LIMIT 2 OFFSET 2; SELECT id, price FROM book WHERE (price, id) > (200, 2) ORDER BY price, id LIMIT 2; 새 목록의 첫 세 행은 책 7, 1, 2입니다. OFFSET으로 두 행을 건너뛰면 책 2부터 시작합니다. 실제 결과는 (2, 200) , (3, 200) 입니다. 첫 화면에서 읽었던 책 2가 다시 나타납니다. 마지막 값 (200, 2) 를 조건으로 쓴 조회는 (3, 200) , (4, 300) 을 반환합니다. 앞에 추가된 책 7은 마지막 값의 앞쪽에 놓이므로 이 다음 화면에는 들어오지 않습니다. 이 예제는 행 추가 한 번을 다룹니다. 읽는 동안 기존 책의 가격이 바뀌거나 행이 삭제되면 필요한 화면 규칙을 다시 정해야 합니다. 모든 화면에서 같은 시점의 자료가 필요하다면 DB가 자료의 읽는 시점을 유지하는 방법까지 검토합니다. 마지막 값 하나를 기억하는 방법의 범위와, 전체 조회 시점을 유지하는 요구를 구분할 수 있습니다. 다음 화면의 기준 기록하기 Keyset 방식에서는 반환된 마지막 행의 가격과 번호를 함께 기록합니다. 정렬 기준에 쓴 값을 이어지는 요청의 조건에 그대로 사용합니다. 가격이 같은 자료가 있을 때 번호를 빼면 아직 읽지 않은 같은 가격의 행이 빠질 수 있습니다. 실제 요청에서는 정렬 방향, 빈 값의 처리, 사용자에게 보여 줄 범위도 함께 정합니다. 위 예제의 가격과 번호는 모두 값이 있고 작은 순서로 정렬합니다. 다른 조건을 넣을 때는 그 조건에 맞춘 반환 값을 다시 확인해야 합니다. 이번 실험은 결과 행을 확인했으며, 조회 시간은 측정하지 않았습니다. 확인 문제 첫 화면의 마지막 책이 (200, 2) 일 때 가격 200인 책 3은 다음 화면에 들어갈까요? 책 7이 앞에 추가된 뒤 OFFSET 2 조회가 다시 보여 준 책은 무엇일까요? 한 화면을 세 권씩 읽으려면 어떤 값과 기록을 바꿔야 할까요? 답과 해설 들어갑니다. 가격이 같고 번호 3이 마지막 번호 2의 뒤에 놓입니다. 책 2입니다. 새 목록에서 책 7과 1을 건너뛰면 책 2부터 읽습니다. LIMIT를 3으로 바꾸고, 그 결과의 마지막 행에 있는 가격과 번호를 함께 기록합니다. OFFSET 방식은 화면에 맞는 건너뛸 행 수도 정합니다. 오늘은 책 번호 여섯 개를 가격순으로 놓고 첫 화면의 마지막 값을 표시해보겠습니다. 하루 뒤에는 같은 가격의 책을 한 권 더 넣어 다음 화면을 예상해보겠습니다. 일주일 뒤에는 정렬 기준과 Index의 열 순서를 연결해 설명해보면 좋겠습니다. 자료 확인: 2026-10-07, SQLite 공식 문서와 PostgreSQL 18 공식 문서. 예제 검증 환경: Python 3.12.14, SQLite 3.53.1, 독립 :memory: DB. PostgreSQL에서는 문서의 정렬 규칙을 확인했습니다. 위 SQL의 실행 결과는 SQLite 실험의 범위입니다.
오늘이 시험 당일이라니... 과연 이 사람은 붙을 것일까 떨어질 것일까 궁금하시죠 저도 궁금해요 submiter 가 withdraw 거둬들이다 취소하다 user 가 레코드를 submit for approval approver or submitter submitter 가 잠 나지금 거둬들일게 = withdraw 이거는 대부분 ui 에서 recall 버튼 임 누르면 recall actions 발동 ex . 누군가 approval process 요청 제출 - 생각해보니 번호 잘못 적음 아직 매니저가 승인/거부 안한 상태에서 본인이 거둬드림(withdraw) - recall vr에서 error condition formula 가 true 이면 무조건 누구나 블락 된 어드민도 못피함
Thị trường chứng khoán từ lâu đã được ví như một tấm gương phản chiếu thu nhỏ của nền kinh tế vĩ đại, nơi hàng triệu dòng vốn gặp gỡ, đan xen và tranh đấu từng giây từng phút. Đối với những nhà đầu tư mới bước chân vào lĩnh vực tài chính, bảng điện tử chứng khoán (Bảng giá trực tuyến) thường hiện lên như một ma trận phức tạp với chi chít các con số xanh, đỏ, vàng cùng những ký hiệu viết tắt khó hiểu. Việc không biết cách đọc bảng điện tử chẳng khác nào một người mù đi giữa đêm tối, hoàn toàn bị động trước những biến động chớp nhoáng của thị trường và dễ dàng rơi vào trạng thái hoảng loạn hoặc kỳ vọng thái quá. Tuy nhiên, đằng sau sự nhảy múa liên tục của những con số ấy lại là một bức tranh sống động về cung cầu, về cuộc chiến căng thẳng giữa phe mua và phe bán, đồng thời hé lộ những dấu vết rõ nét của dòng tiền thông minh. Việc nắm vững kỹ năng đọc và phân tích bảng điện tử không chỉ là một công cụ kỹ thuật đơn thuần mà còn là nghệ thuật thấu hiểu tâm lý hành vi của đám đông trên thị trường tài chính. Khi bạn đã thuần thục việc giải mã từng bước nhảy của giá, từng khối lượng khớp lệnh bùng nổ hay sự dịch chuyển của các lô mua bán chủ động, bạn sẽ thấy thị trường không hề ngẫu nhiên hay hỗn loạn mà vận hành theo những quy luật tâm lý và cung cầu hết sức chặt chẽ. Bài viết này sẽ dẫn dắt bạn đi qua từng lớp lang của bảng điện tử chứng khoán, từ những khái niệm căn bản nhất cho đến các kỹ năng đọc vị dòng tiền chuyên sâu, giúp bạn tự tin xây dựng góc nhìn độc lập và sắc bén trong mọi quyết định đầu tư của mình mà không cần phải phụ thuộc vào những lời phím hàng hay tin đồn vô căn cứ trên các diễn đàn. Để có thể làm chủ bảng điện tử, bước đầu tiên và quan trọng nhất là bạn cần trang bị cho mình một tài khoản giao dịch tại các công ty chứng khoán uy tín, nơi cung cấp bảng giá trực tuyến mượt mà, tốc độ cập nhật dữ liệu thời gian thực chính xác cùng nhiều tính năng hỗ trợ phân tích kỹ thuật và đặt lệnh chuyên nghiệp. Bạn có thể tham khảo hướng dẫn chi tiết về quy trình và thủ tục để nhanh chóng sở hữu tài khoản giao dịch thông qua bài viết tổng hợp tại Hướng dẫn lập tài khoản chứng khoán với các bước thực hiện đơn giản, bảo mật tuyệt đối và hoàn toàn miễn phí trực tuyến. Bên cạnh đó, nếu bạn đặc biệt quan tâm đến hệ sinh thái giao dịch phái sinh, các chính sách ưu đãi phí giao dịch hấp dẫn cùng dịch vụ hỗ trợ khách hàng tận tâm hàng đầu thị trường, việc lựa chọn mở tài khoản tại các định chế tài chính lớn là một hướng đi vô cùng khôn ngoan; bạn có thể tìm hiểu thêm về cách thức gia nhập hệ thống này qua thông tin tại Hướng dẫn lập tài khoản VPS để không bỏ lỡ bất kỳ cơ hội lướt sóng ngắn hạn nào. Ngoài ra, đối với những nhà đầu tư theo trường lối đầu tư giá trị, ưa chuộng các giải pháp tự động hóa tài sản thông minh, tích hợp quản lý dòng tiền linh hoạt và các sản phẩm trái phiếu, chứng chỉ quỹ ưu việt, việc mở tài khoản tại ngân hàng đầu tư hàng đầu là lựa chọn tối ưu; hãy tham khảo ngay quy trình thiết lập tài khoản chuyên sâu tại Hướng dẫn chi tiết mở tài khoản TCBS để bắt đầu hành trình kiến tạo sự thịnh vượng tài chính một cách bài bản nhất từ hôm nay. Khi đã có trong tay công cụ giao dịch đắc lực, việc tiếp theo là chúng ta cần hiểu rõ cấu trúc cơ bản của một bảng điện tử tiêu chuẩn trên thị trường chứng khoán Việt Nam. Thông thường, bảng giá được chia thành các nhóm chính bao gồm nhóm cổ phiếu VN30 (những doanh nghiệp có vốn hóa và thanh khoản lớn nhất thị trường), nhóm cổ phiếu vừa và nhỏ, nhóm chứng khoán phái sinh, chứng quyền có bảo đảm và trái phiếu doanh nghiệp niêm yết. Mỗi mã chứng khoán sẽ được định danh bằng một mã viết tắt gồm ba chữ cái theo quy định của trung tâm lưu ký chứng khoán. Đi kèm với mã chứng khoán là một loạt các cột thông tin kinh điển như Giá tham chiếu, Giá trần, Giá sàn, bên cạnh đó là các mức giá mua tốt nhất, khối lượng mua tương ứng, mức giá bán tốt nhất cùng khối lượng bán tương ứng. Màu sắc trên bảng điện tử đóng vai trò như một hệ thống tín hiệu giao thông trực quan giúp nhà đầu tư nắm bắt xu hướng ngay lập tức mà không cần phải đọc từng con số chi tiết. Màu vàng biểu thị cho mức giá tham chiếu, tức là mức giá đóng cửa của phiên giao dịch ngày hôm trước, đóng vai trò là mốc chiếu để tính biên độ dao động trong phiên. Màu xanh lá cây thể hiện sự tăng giá so với giá tham chiếu, mang lại niềm vui cho các cổ đông nắm giữ mã cổ phiếu đó. Màu đỏ biểu thị sự sụt giảm giá so với tham chiếu, phản ánh áp lực bán đang áp đảo hoặc dòng tiền rút lui khỏi mã cổ phiếu đó. Đặc biệt, trên thị trường chứng khoán Việt Nam, chúng ta có thêm hai màu sắc vô cùng đặc biệt là màu tím và màu xanh lơ (hoặc màu xanh da trời tùy theo giao diện của từng công ty chứng khoán). Màu tím tượng trưng cho mức giá trần, là mức giá cao nhất mà cổ phiếu được phép giao dịch trong một phiên giao dịch, thể hiện sức mua cực kỳ khủng khiếp, vượt trội hoàn toàn so với lượng hàng bán ra và thường xuất hiện khi có những tin tức cực kỳ vĩ mô hoặc dòng tiền lớn đổ bộ vào một nhóm ngành nào đó. Ngược lại, màu xanh lơ tượng trưng cho mức giá sàn, là mức giá thấp nhất trong phiên, phản ánh lực bán tháo hoảng loạn, khi mà nhà đầu tư tìm mọi cách thoát hàng bằng được dù phải chịu mức giá chiết khấu sâu nhất trong ngày. Việc hiểu rõ ý nghĩa của biên độ dao động +/-7% trên sàn HOSE, +/-10% trên sàn HNX và +/-15% trên sàn UPCoM sẽ giúp bạn định hình được mức độ rủi ro cũng như biên độ lợi nhuận tiềm năng trong mỗi quyết định giao dịch của mình. Biên độ này không chỉ là những con số kỹ thuật khô khan mà nó còn là lồng sắt bảo vệ nhà đầu tư khỏi những cú sốc tâm lý quá lớn trong một phiên giao dịch, đồng thời là thước đo để kiểm tra sức mạnh nội tại của từng cổ phiếu khi đối diện với các biến động của thị trường chung. Đi sâu hơn vào cấu trúc của bảng điện tử, phần cốt lõi thu hút sự chú ý của mọi nhà đầu tư chuyên nghiệp chính là khu vực khớp lệnh, bao gồm mức giá khớp lệnh hiện tại, khối lượng khớp lệnh trong phiên và tổng khối lượng giao dịch tích lũy từ đầu ngày. Giá khớp lệnh là kết quả của cuộc gặp gỡ định mệnh giữa người muốn mua với mức giá cao nhất có thể chấp nhận và người muốn bán với mức giá thấp nhất có thể chấp nhận. Khi một lệnh mua khớp với một lệnh bán, giao dịch chính thức được thiết lập và ghi nhận vào lịch sử giao dịch của thị trường. Việc theo dõi sự biến động của giá khớp lệnh cùng với khối lượng khớp đi kèm trong từng phút sẽ cho chúng ta thấy được nhịp đập của thị trường. Nếu một cổ phiếu tăng giá đi kèm với khối lượng khớp lệnh tăng đột biến vượt xa mức trung bình 20 phiên gần nhất, đó là minh chứng không thể chối cãi cho thấy dòng tiền lớn (smart money) đang ào ạt đổ vào gom hàng. Ngược lại, nếu giá cổ phiếu tăng nhưng khối lượng khớp lệnh lại teo tóp, lèo tèo, đó thường là cái bẫy tăng giá (bull trap) do một nhóm nhỏ nhà đầu tư tạo lập hoặc đầu cơ kéo giá lên trong biên độ hẹp nhằm xả hàng tồn kho ra cho những nhà đầu tư thiếu kinh nghiệm bám đuổi. Khối lượng tích lũy trong phiên cũng là một chỉ số sống còn để đánh giá thanh khoản của doanh nghiệp. Những cổ phiếu có thanh khoản cao luôn là ưu tiên hàng đầu của các nhà đầu tư lớn, các quỹ đầu tư tổ chức bởi vì họ có thể dễ dàng giải ngân hàng triệu đô la mà không làm biến động quá mạnh đến giá vốn, đồng thời cũng có thể nhanh chóng thoát hàng khi thị trường chuyển biến xấu mà không sợ bị kẹt hàng do không có người mua. Ngược lại, những cổ phiếu có thanh khoản thấp, mỗi phiên chỉ khớp vài trăm hoặc vài nghìn cổ phiếu thường tiềm ẩn rủi ro thao túng giá rất cao bởi các nhóm lái cổ phiếu, nơi mà chỉ cần một lượng tiền nhỏ cũng có thể vẽ biểu đồ theo ý muốn, khiến nhà đầu tư cá nhân sập bẫy bất cứ lúc nào. Do đó, việc quan sát kỹ lưỡng cột khối lượng khớp lệnh kết hợp với dư mua và dư bán chính là chiếc kính lúp phóng đại mọi ý đồ của các nhà tạo lập thị trường, giúp bạn tránh xa những cổ phiếu "rác" và tập trung tài sản vào những doanh nghiệp có nền tảng cơ bản vững chắc cùng dòng tiền thực sự hậu thuẫn. Một khía cạnh cực kỳ quan trọng khác trên bảng điện tử mà ít người để ý đến chính là khu vực khớp lệnh thỏa thuận bên cạnh phương thức khớp lệnh định kỳ và liên tục. Phương thức khớp lệnh định kỳ (ATO và ATC) diễn ra vào đầu và cuối phiên giao dịch, nơi toàn bộ các lệnh mua và bán trong phiên được tập hợp lại để tìm ra một mức giá duy nhất có thể khớp được khối lượng giao dịch lớn nhất. Phiên ATO (Opening) mở cửa lúc 9h sáng trên sàn HOSE định hình tâm lý đầu ngày, phản ánh những thông tin vĩ mô hoặc quốc tế diễn ra trong đêm qua tác động trực tiếp lên kỳ vọng của nhà đầu tư. Trong khi đó, phiên ATC (Closing) diễn ra từ 14h45 đến 15h00 lại là chiến trường khốc liệt nhất của các quỹ đầu tư tổ chức, các quỹ ETF thực hiện việc tái cơ cấu danh mục hoặc chốt NAV định kỳ. Việc theo dõi diễn biến giá và khối lượng trong phiên ATC đòi hỏi một cái đầu lạnh và kinh nghiệm cực kỳ dày dặn, bởi vì đây là thời điểm mà các lệnh lớn được ẩn giấu và tung ra vào những giây cuối cùng nhằm tạo ra mức giá đóng cửa như ý muốn của các "tay to". Còn phương thức khớp lệnh thỏa thuận lại là nơi diễn ra các giao dịch có quy mô cực kỳ lớn giữa các cổ đông lớn, các quỹ đầu tư nước ngoài hoặc các lãnh đạo doanh nghiệp mà không làm ảnh hưởng trực tiếp đến biến động giá trên sàn khớp lệnh liên tục. Việc đọc hiểu bảng điện tử không dừng lại ở việc nhìn thấy con số xanh đỏ trước mắt, mà là khả năng kết nối chuỗi các sự kiện diễn ra trong suốt phiên giao dịch để phác thảo nên hành vi của các bên tham gia thị trường. Bạn cần phải tự đặt ra những câu hỏi phản biện liên tục khi nhìn vào bảng giá: Tại sao cổ phiếu này lại có dư mua giá sàn lớn đến vậy mà không ai bán? Tại sao có lệnh mua hàng triệu cổ phiếu kê ở giá tham chiếu ngay trước giờ ATC? Ai đang là người đứng sau những bước giá nhích lên đều đặn trong suốt phiên sáng nhưng lại bị bán úp ngược vào đầu phiên chiều? Chính sự tò mò mang tính phân tích này sẽ mài giũa tư duy của bạn, biến bạn từ một người chơi theo cảm tính trở thành một nhà đầu tư có tư duy chiến lược sắc sảo. Để nâng cao kỹ năng đọc bảng điện tử lên một tầm cao mới, chúng ta
문제 설명 각 산은 밑변이 x축에 놓인 삼각형입니다. 양쪽 빗변은 밑변과 각각 45도를 이루므로, 봉우리 좌표 (x, y) 가 주어지면 산의 모양이 결정됩니다. 베시는 어떤 산의 봉우리가 다른 산의 내부나 경계에 있으면 그 산을 구별할 수 없습니다. 주어진 산들 중에서 다른 산에 가려지지 않는 산의 개수 를 구하면 됩니다. 예를 들어 봉우리 좌표가 다음과 같다면, (4, 6) (7, 2) (2, 5) (7, 2) 의 봉우리는 (4, 6) 인 산에 가려집니다. 나머지 두 산은 구별할 수 있으므로 정답은 2 입니다. 풀이 아이디어 1. 산을 밑변 구간으로 바꾸기 봉우리가 (x, y) 인 산을 생각해보겠습니다. 빗변의 기울기는 각각 1 , -1 이므로, 봉우리에서 x축까지 내려가는 동안 가로로도 y 만큼 이동합니다. 따라서 밑변의 양 끝점은 다음과 같습니다. 왼쪽 끝점: x - y 오른쪽 끝점: x + y 산 하나를 다음 구간으로 표현할 수 있습니다. [x-y, x+y] 모든 산의 빗변 기울기가 같으므로, 봉우리가 다른 산에 가려지는 조건을 밑변 구간이 다른 구간에 포함되는 조건 으로 바꿀 수 있습니다. 다른 산의 시작점 <= 현재 산의 시작점 현재 산의 끝점 <= 다른 산의 끝점 즉, 다른 구간에 포함되지 않는 구간의 개수를 구하면 됩니다. 2. 정렬 후 최대 끝점 확인하기 구간을 시작점 기준 오름차순으로 정렬합니다. 그러면 앞에서 확인한 구간들은 모두 현재 구간의 시작점 이하에서 시작합니다. 이 상태에서는 앞선 구간들의 최대 끝점만 기억하면 됩니다. 현재 끝점 <= 이전 최대 끝점 → 앞선 구간에 포함됨 현재 끝점 > 이전 최대 끝점 → 앞선 구간에 포함되지 않음 시작점이 같다면 끝점이 큰 구간을 먼저 확인합니다. 그래야 같은 위치에서 시작하는 작은 구간을 가려지는 산으로 처리할 수 있습니다. 코드 #include <bits/stdc++.h> using namespace std; bool cmp(pair<int, int> a, pair<int, int> b) { if (a.first == b.first) return a.second > b.second; return a.first < b.first; } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); int N; cin >> N; vector<pair<int, int>> mnt; for (int i=0; i<N; i++) { int x,y; cin >> x >> y; mnt.push_back({x-y, x+y}); } sort(mnt.begin(), mnt.end(), cmp); int max_ed = INT_MIN; int ret=0; for (auto[st, ed] : mnt) { if (ed > max_ed) { ret++; max_ed = ed; } } cout << ret; return 0; } 풀이 흐름 산의 개수 N 을 입력받습니다. 각 봉우리 (x, y) 를 밑변 구간 [x-y, x+y] 로 바꾸어 저장합니다. 시작점 오름차순, 시작점이 같다면 끝점 내림차순으로 정렬합니다. 이전 구간들의 최대 끝점 max_ed 를 초기화합니다. 정렬된 구간을 앞에서부터 확인합니다. 현재 끝점이 max_ed 보다 크면 보이는 산으로 세고, max_ed 를 갱신합니다. 보이는 산의 개수 ret 를 출력합니다. 구현 포인트 1. mnt에 저장하는 값 vector<pair<int, int>> mnt; mnt 에는 봉우리 좌표가 아닌 산의 밑변 구간을 저장합니다. first = 밑변의 왼쪽 끝점 second = 밑변의 오른쪽 끝점 입력 단계에서 바로 좌표를 변환합니다. int x,y; cin >> x >> y; mnt.push_back({x-y, x+y}); 예제의 산들을 구간으로 바꾸면 다음과 같습니다. (4, 6) → [-2, 10] (7, 2) → [5, 9] (2, 5) → [-3, 7] 이렇게 바꾸면 삼각형의 겹침을 직접 계산하지 않고, 두 끝점으로 포함 관계를 확인할 수 있습니다. 2. 봉우리의 포함과 구간의 포함이 같은 이유 다른 산의 봉우리를 (X, Y) 라고 하겠습니다. 현재 봉우리 (x, y) 가 이 산의 내부나 경계에 있으려면 다음 조건을 만족해야 합니다. y + |x-X| <= Y 다른 산은 봉우리에서 가로로 1만큼 멀어질 때마다 높이가 1씩 낮아집니다. 따라서 현재 위치에서 다른 산의 높이는 Y - |x-X| 이며, 현재 봉우리의 높이 y 가 이 값 이하여야 합니다. 이 조건은 다음 두 조건으로 나눌 수 있습니다. y + x - X <= Y y + X - x <= Y 정리하면 다음과 같습니다. x + y <= X + Y X - Y <= x - y 이는 밑변 구간으로 보면 다음 조건입니다. 다른 산의 왼쪽 끝점 <= 현재 산의 왼쪽 끝점 현재 산의 오른쪽 끝점 <= 다른 산의 오른쪽 끝점 따라서 구간이 포함되면 해당 산의 봉우리도 가려집니다. 3. 시작점이 같으면 끝점 내림차순으로 정렬하기 bool cmp(pair<int, int> a, pair<int, int> b) { if (a.first == b.first) return a.second > b.second; return a.first < b.first; } 기본 정렬 기준은 시작점 오름차순입니다. 시작점이 같다면 끝점이 큰 구간을 먼저 배치합니다. 예를 들어 다음 두 구간이 있다고 하겠습니다. [1, 5] [1, 8] [1, 5] 는 [1, 8] 에 포함되므로 가려지는 산입니다. 작은 구간을 먼저 확인하면 아직 큰 구간을 보지 못했기 때문에 작은 구간도 정답에 포함할 수 있습니다. 따라서 다음 순서로 정렬합니다. [1, 8] [1, 5] 큰 구간을 먼저 확인하여 최대 끝점을 8 로 만들면, 작은 구간은 자연스럽게 제외됩니다. 4. max_ed의 의미 int max_ed = INT_MIN; max_ed 는 현재 구간을 확인하기 전에 처리한 모든 구간의 끝점 중 최댓값 입니다. 처음에는 확인한 구간이 없으므로 INT_MIN 으로 초기화합니다. 이를 통해 첫 번째 구간은 정답에 포함됩니다. 정렬 이후 앞선 구간들은 모두 현재 구간보다 왼쪽 또는 같은 위치에서 시작합니다. 따라서 앞선 구간 중 끝점이 현재 끝점 이상인 구간이 있다면, 그 구간은 현재 구간을 포함합니다. 이 조건을 확인하기 위해 앞선 구간들을 다시 순회할 필요는 없습니다. 가장 큰 끝점인 max_ed 만 비교하면 됩니다. 5. 끝점이 더 클 때만 정답에 포함하기 int ret=0; for (auto[st, ed] : mnt) { if (ed > max_ed) { ret++; max_ed = ed; } } st 는 현재 구간의 시작점이고, ed 는 끝점입니다. 시작점 관계는 정렬로 보장되어 있으므로 반복문에서는 끝점만 비교합니다. ed <= max_ed → 앞선 구간에 포함됨 → 정답에 포함하지 않음 현재 끝점이 더 크다면 앞선 어느 구간에도 포함되지 않습니다. ed > max_ed → 보이는 산 → ret 증가 → max_ed 갱신 끝점이 같은 경우에도 현재 구간은 앞선 구간에 포함됩니다. 문제에서는 경계에 있는 봉우리도 보이지 않으므로, 조건은 >= 가 아닌 > 입니다. 조건을 만족하지 않을 때는 현재 끝점이 기존 최댓값 이하이므로 max_ed 를 그대로 유지합니다. 6. 한 번 센 산을 나중에 취소하지 않아도 되는 이유 현재 구간을 정답에 포함했다면 앞선 구간에는 가려지지 않는다는 것을 확인한 상태입니다. 뒤에 나오는 구간의 시작점은 현재 시작점보다 크거나 같습니다. 시작점이 더 크다면 현재 구간 전체를 포함할 수 없습니다. 시작점이 같더라도 끝점 내림차순으로 정렬했으므로, 뒤에 나오는 구간의 끝점은 현재 끝점 이하입니다. 봉우리 위치가 같은 두 산은 없으므로 완전히 같은 구간도 없습니다. 따라서 뒤의 구간이 현재 산을 가릴 수 없으며, 한 번 증가시킨 ret 를 다시 줄일 필요가 없습니다. 7. 예시로 보는 동작 예제의 구간들을 정렬하면 다음과 같습니다. [-3, 7] [-2, 10] [5, 9] 첫 번째 구간을 확인합니다. ed = 7 7 > INT_MIN ret = 1 max_ed = 7 두 번째 구간은 끝점이 기존 최댓값보다 큽니다. ed = 10 10 > 7 ret = 2 max_ed = 10 세 번째 구간은 끝점이 기존 최댓값 이하입니다. ed = 9 9 <= 10 가려지는 산이므로 제외 따라서 최종 정답은 2 가 됩니다. 시간복잡도 각 산을 밑변 구간으로 변환하는 데 O(N) 이 필요합니다. 구간을 정렬하는 데 O(N log N) , 정렬된 구간을 한 번 순회하는 데 O(N) 이 필요합니다. 따라서 전체 시간복잡도는 O(N log N) 입니다. mnt 에 산마다 구간 하나를 저장하므로 공간복잡도는 O(N) 입니다.
이 글은 제가 만든 유료 상품 이야기입니다. 9월 25일부터 Gumroad에서 "AI 크롤러 정책 자동 검사 배지"를 $19에 팔고 있습니다. 무료로 쓸 수 있는 것부터 적고 유료 부분은 뒤에 따로 적겠습니다. 9월 초에 robots.txt가 GPTBot·ClaudeBot을 실제로 막는지 보여주는 무료 점검기를 소개했습니다. 그런데 점검 결과는 탭을 닫으면 사라져서, 정책을 정해 둔 사이트가 그 상태를 남에게 링크로 보여줄 방법이 없었습니다. 그래서 검사 결과를 발급일로부터 365일 동안 이용하는 결과 주소와 작은 SVG 배지로 남기는 발급기를 만들었습니다. 판정 로직은 복제하지 않고 점검기의 공개 API 2개를 그대로 부릅니다. 검사까지는 무료입니다. 발급 화면에서 키 없이 "먼저 무료로 검사"를 누르면 아래 5개 항목의 결과가 나오고, 같은 결과를 /check?domain=example.com 경로에서 JSON으로 받을 수도 있습니다. 로그인은 없습니다. 배지는 5개 항목을 전부 통과해야 나옵니다. robots.txt가 있을 것(404·410이면 미통과) robots.txt가 512K자를 넘어 잘리지 않을 것 AI 크롤러 15종 모두에 적용되는 규칙이 있을 것(전용 그룹이든 User-agent: * 든) /llms.txt 가 HTML이 아닌 텍스트로 응답할 것 llms.txt 형식 오류 0건 세 번째는 막았는지가 아니라 정했는지를 봅니다. 허용과 차단 중 무엇이 옳은지는 판단하지 않습니다. 네 번째에서 HTML 응답을 미통과로 치는 건 없는 경로에 SPA 껍데기 HTML을 200으로 돌려주는 사이트가 많아서입니다. 9월 24일 외부 도메인 6곳을 돌려 보니 cloudflare.com·vercel.com·stripe.com·docs.perplexity.ai는 5/5, llmstxt.org는 4/5, anthropic.com은 3/5였습니다. 배지가 안 나온다고 사이트에 문제가 있다는 뜻은 아닙니다. 가장 오래 붙잡은 건 배지 문구였습니다. 배지는 기관 심사나 법적 판단처럼 읽히는 순간 문제가 됩니다. 그래서 SVG 안의 글자를 도메인과 "YYYY-MM-DD 자동 검사 통과" 두 조각으로 고정했고, 그렇게 읽힐 수 있는 표현 20개를 금지 목록으로 두어 하네스가 배지·결과 페이지·판매 문안을 전수 검사합니다. 결제 확인은 Gumroad 라이선스 키 하나로 합니다. 발급기는 워커 1개이고, 키와 도메인이 오면 Gumroad licenses/verify API로 확인합니다. 키가 없으면 403, 결제된 키가 아니거나 환불·차지백·분쟁 건이면 402, 결제는 됐어도 5개 중 미통과가 있으면 422로 발급하지 않습니다. KV에는 키 원문을 저장하지 않습니다. 배지 id는 도메인과 키의 sha256 해시를 비밀값으로 HMAC한 값의 앞 24자이고, 해시는 id를 만든 뒤 버립니다. 같은 계정의 다른 워커가 KV list 일일 상한 1,000회를 다 쓴 적이 있어 이 워커는 list() 를 부르지 않습니다. 이 배지가 다루지 않는 것도 적어 둡니다. 기관 심사 결과도 법적 판단도 아닙니다. 사이트가 공개한 두 파일만 보므로 크롤러가 그 규칙을 실제로 따르는지는 알 수 없습니다. 한 번 관측한 결과라 이후 파일이 바뀌면 반영되지 않고, 사이트의 품질·보안·개인정보 처리와도 무관합니다. 결과 페이지에 이 한계와 다시 검사하는 주소가 함께 적힙니다. AI 크롤러 접근 점검기(무료) — https://ai-crawler-checker.hdg-os.workers.dev/ 배지 발급 화면(검사는 키 없이 무료) — https://verify-badge.hdg-os.workers.dev/redeem AI 크롤러 정책 자동 검사 배지($19, 도메인 1개·365일) — https://maxhwang.gumroad.com/l/ai-crawler-badge 구매 후 확인 메일의 라이선스 키와 도메인을 발급 화면에 넣으면 배지 삽입 코드가 나옵니다. 언급한 크롤러·서비스 이름과 도메인은 각 소유자의 것이며, 이 글과 도구는 어느 곳과도 제휴 관계가 없습니다.
최댓값 만들기(1) 문제 설명 정수 배열 numbers가 매개변수로 주어집니다. numbers의 원소 중 두 개를 곱해 만들 수 있는 최댓값을 return하도록 solution 함수를 완성해주세요. 주어진 코드 틀 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> // numbers_len은 배열 numbers의 길이입니다. int solution(int numbers[], size_t numbers_len) { int answer = 0; return answer; } 풀이 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> // numbers_len은 배열 numbers의 길이입니다. int solution(int numbers[], size_t numbers_len) { int answer = 0; for(size_t i = 0; i < numbers_len; i++) { for(size_t l = 0; l<numbers_len -i - 1; l++) { if(numbers[l] > numbers[l+1]) { int num = numbers[l+1]; numbers[l+1] = numbers[l]; numbers[l] = num; } } } answer = numbers[numbers_len -1] * numbers[numbers_len -2]; return answer; } 접근 방식 주어진 배열에서 제일 큰 값 2개를 곱하여 리턴할 것. 문제를 보고 max 값 2개를 무작정 뽑기보다 주어진 배열을 오름차순으로 정렬해서 뒤에 값 2개를 곱해주자는 방식으로 문제를 접근했다. 피드백 입력 변경 첫 번째 루프에서 i < numbers_len - 1 시작할 것. 후기 이번 문제에서는 정렬 중에서 버블 정렬을 이용한 문제이다. 여기서 내가 주의할 점이 정렬할 때 앞에 값과 비교하는데 이때 값을 잘 못 설정하면 범위에서 벗어난다. 특히 루프 돌 때 범위를 한 번 더 생각해주자. 버블 정렬: 인접한 두 개의 언소를 비교하여 크기가 순서대로 되어 있지 않으면 자리를 교환하는 방식의 가장 단순한 정렬 알고리즘
FMS 파일 검색을 처음에는 매번 원격으로 했다. 근데 실적이 많아지면 같은 폴더를 계속 다시 탐색하게 돼서 파일 메타데이터를 H2에 따로 저장해두는 방식으로 바꿨다. FMS → 파일 목록 수집 drive_file_index → 검색용 파일 정보 저장 후보 검색 → FMS 직접 조회 X → H2 인덱스 조회 여기서 말하는 인덱스는 DB의 CREATE INDEX 랑은 조금 다르다. 이번에는 원본 데이터를 검색하기 쉽게 애플리케이션 쪽에 따로 만들어둔 검색용 데이터 저장소 에 가깝다. 원본 파일 → FMS에 그대로 존재 검색용 메타데이터 → Biz Assist H2에 저장 대신 실제 파일 선택이나 다운로드할 때는 H2 데이터만 믿지 않고 FMS를 다시 확인한다. 검색 → 인덱스 사용 실제 파일 존재 여부 / 권한 → 원본 FMS 확인 원격 데이터 조회가 무겁고 반복적일 때 검색용 정보를 따로 저장해두는 구조를 생각해볼 수 있다.
책 목록에서 과학책을 골라 가격순으로 보여 주는 화면을 만든다고 하겠습니다. DB(Database)는 자료를 저장하고 찾는 시스템입니다. 표의 한 줄은 행, 분류나 가격처럼 같은 종류의 값을 담는 항목은 열이라고 부릅니다. 앞선 Index 글 에서는 후보 행을 찾는 과정을 살펴봤습니다. Index는 원하는 행을 찾도록 돕는 보조 자료구조입니다. 이번에는 두 열을 묶었을 때 정렬 순서가 조회에 어떻게 연결되는지 확인하겠습니다. 분류를 먼저, 같은 분류에서는 가격을 Composite Index는 여러 열을 정해진 순서로 묶은 Index입니다. 여기서는 (분류, 가격) 순서를 사용합니다. 분류가 같은 항목끼리 모이고, 그 안에서 가격순으로 놓입니다. SQLite라는 DB 관리 프로그램의 여러 열 Index도 이런 순서로 값을 정렬합니다. SQLite 여러 열 Index 설명 예제의 category 는 분류, price 는 가격을 뜻합니다. 과학은 science , 역사는 history 로 저장하겠습니다. 아래 표는 Index의 정렬 순서를 설명하는 모형입니다. 실제 저장 공간의 모양과 읽은 횟수는 환경별로 따로 확인합니다. 먼저 보는 분류 같은 분류 안의 가격 history 9,000 → 12,000 → 20,000 science 10,000 → 15,000 → 18,000 과학이라는 분류를 정하면 과학 묶음에 집중할 수 있습니다. 그 안에서 12,000원 이상을 찾으면 15,000원과 18,000원이 남습니다. 두 값은 이미 가격순으로 놓여 있습니다. 열 순서를 (가격, 분류) 로 바꾸면 가격이 먼저 기준이 됩니다. 같은 가격 안에서 분류를 정리합니다. 준비된 순서가 달라지므로, 자주 찾는 조건과 필요한 정렬 순서를 함께 적고 Index를 설계해야 합니다. 작은 표에서 읽는 계획 확인하기 SQL은 DB에 작업을 요청하는 언어입니다. WHERE 는 찾을 조건, ORDER BY 는 결과의 순서를 지정합니다. SELECT 는 값을 읽고, CREATE TABLE 은 표를 만들며, INSERT 는 행을 넣습니다. CREATE INDEX 는 Index를 만듭니다. 이번 실험은 SQLite의 독립 Memory DB에서 실행했습니다. Memory는 실행 중에 자료를 담는 공간입니다. 이 공간에 새 DB를 만들어 예제만 실행했습니다. EXPLAIN QUERY PLAN 은 조회에 사용할 읽기 계획을 보여 줍니다. 먼저 Index가 없는 계획을 보고, 만든 뒤 같은 조건의 계획과 결과를 확인합니다. CREATE TABLE book ( id INTEGER PRIMARY KEY, category TEXT NOT NULL, price INTEGER NOT NULL ); INSERT INTO book VALUES (1, 'science', 18000), (2, 'history', 12000), (3, 'science', 10000), (4, 'history', 20000), (5, 'science', 15000), (6, 'history', 9000); EXPLAIN QUERY PLAN SELECT category, price FROM book WHERE category = 'science' AND price >= 12000 ORDER BY price; CREATE INDEX book_category_price_idx ON book(category, price); EXPLAIN QUERY PLAN SELECT category, price FROM book WHERE category = 'science' AND price >= 12000 ORDER BY price; SELECT category, price FROM book WHERE category = 'science' AND price >= 12000 ORDER BY price; EXPLAIN QUERY PLAN SELECT category, price FROM book WHERE price >= 12000 ORDER BY price; SELECT category, price FROM book WHERE price >= 12000 ORDER BY price; book 은 책 표의 이름이고, id 는 각 행을 구분하는 번호입니다. INTEGER 는 정수, TEXT 는 글자 값을 담습니다. PRIMARY KEY 는 행을 구분하는 기본 열로 지정하고, NOT NULL 은 값이 없음을 나타내는 NULL 의 입력을 제한합니다. AND 로 연결한 두 조건은 함께 만족해야 합니다. >= 는 기준값을 포함해 큰 값을 찾습니다. ORDER BY price 는 가격이 작은 순서로 결과를 요청합니다. 계획과 결과를 차례로 읽기 첫 계획에는 SCAN book 과 USE TEMP B-TREE FOR ORDER BY 가 나왔습니다. 표를 살펴본 뒤 결과의 가격순 정렬에 임시 구조를 사용하는 계획입니다. B-tree는 정렬된 값을 관리하는 자료구조입니다. Index를 만든 뒤에는 다음 표시가 나왔습니다. SEARCH book USING COVERING INDEX book_category_price_idx (category=? AND price>?) SEARCH 는 조건으로 범위를 좁혀 읽는 경로입니다. 괄호 안에는 분류와 가격 조건이 계획에 쓰였음을 보여 줍니다. 이 표시의 price>? 는 범위 조건의 요약입니다. 실행한 SQL의 기준은 그대로 price >= 12000 입니다. Covering Index는 조회에 필요한 열을 Index가 모두 담고 있는 경우입니다. 이번에는 분류와 가격만 읽으므로, 두 열을 담은 Index에서 결과를 얻습니다. 이 계획에는 추가 가격 정렬 표시가 없습니다. SQLite 실행 계획 표시 설명 반환된 값은 (science, 15000) , (science, 18000) 입니다. Index 생성 전후의 조회 결과도 같았습니다. 확인한 내용은 읽기 계획과 결과입니다. 처리 시간을 재는 시험은 수행하지 않았습니다. 첫 열의 조건을 빼면 무엇이 달라질까? 마지막 조회는 모든 분류에서 12,000원 이상을 찾습니다. 이 실험의 계획은 다시 SCAN book 과 임시 가격 정렬을 선택했습니다. 결과는 history 12000 → science 15000 → science 18000 → history 20000 입니다. (분류, 가격) 으로 준비된 순서에서 분류 묶음을 넘나드는 전체 가격순은 따로 맞춰야 합니다. 실제 선택은 Planner, 즉 읽는 방법을 고르는 기능이 결정합니다. 저장한 값의 분포와 수집한 통계도 판단에 쓰입니다. SQLite의 Skip-scan은 앞 열의 값 묶음을 차례로 옮기며 뒤 열 조건을 찾는 방법입니다. 앞 열에 반복 값이 많고 통계 등 조건이 맞으면 이런 경로도 선택할 수 있습니다. SQLite Skip-scan 조건 PostgreSQL도 DB 관리 프로그램입니다. PostgreSQL 18의 여러 열 B-tree Index 설명에서도 앞 열 조건과 Skip-scan을 다룹니다. 위 실행 결과는 SQLite에서 확인했습니다. PostgreSQL의 선택은 해당 환경에서 실행 계획을 확인해야 합니다. PostgreSQL 18 여러 열 Index 확인 문제 (분류, 가격) 에서 과학 묶음의 가격은 어떤 순서로 놓일까요? 과학이라는 조건을 빼면 이번 실험은 어떤 읽기 계획을 선택했을까요? Index를 만든 뒤 계획이 바뀌었다면, 실제 처리 시간은 어떻게 확인할까요? 답과 해설 과학 묶음 안에서 10,000원, 15,000원, 18,000원 순서입니다. 분류를 고정한 뒤 가격순으로 읽는 흐름과 연결됩니다. 표를 살펴보는 SCAN book 과 임시 가격 정렬을 선택했습니다. 다른 자료와 통계에서는 Planner의 선택이 달라질 수 있습니다. 같은 자료와 요청 조건에서 결과를 확인하고 시간을 따로 재야 합니다. 실행 계획은 어떤 경로를 골랐는지 알려 줍니다. 오늘은 분류와 가격 여섯 쌍을 직접 정렬하고, 과학 조건으로 남는 값을 표시해보겠습니다. 하루 뒤에는 12,000원을 16,000원으로 바꾸고 남는 값을 예상해보겠습니다. 일주일 뒤에는 자주 쓰는 조회 하나에서 조건 열과 정렬 열을 적고 Index 순서를 설명해보면 좋겠습니다. 앞선 JOIN 글 에서는 관련 행을 묶어 읽는 결과를 확인했습니다. JOIN의 결과와 이 글의 읽기 계획을 함께 떠올려보면, 원하는 결과를 정하는 일과 결과를 찾을 경로를 고르는 일을 이어서 설명할 수 있습니다. 자료 확인: 2026-10-07, SQLite 공식 문서와 PostgreSQL 18 공식 문서. 실행 환경: 프로그램을 작성하는 언어인 Python 3.12.14, SQLite 3.53.1, 독립 :memory: DB. 예제의 계획 표시와 결과를 확인했으며, 운영 환경의 성능은 별도 시험이 필요합니다.
NTP 서버 관리와 방화벽 관리 Part 1. 시간과 로그 관리 (NTP) 1. 시간이 중요한 이유 로그는 보안과 관리 양쪽에서 매우 중요하다. 로그를 분석하려면 기록된 시간이 정확해야 한다. 시간이 틀리면 사건의 선후 관계를 파악할 수 없다. 시간과 관련된 서비스가 NTP(Network Time Protocol)이다. 2. 시간대(Time zone) 확인과 변경 timedatectl 로 현재 시간 설정을 확인한다. nobreak@rocky-node2:~$ timedatectl Local time: Tue 2026-10-06 00:22:19 UTC Universal time: Tue 2026-10-06 00:22:19 UTC RTC time: Tue 2026-10-06 00:22:20 Time zone: UTC (UTC, +0000) System clock synchronized: yes NTP service: active RTC in local TZ: yes 시간대가 UTC로 되어 있어서 한국 시간과 9시간 차이가 난다. RTC in local TZ: yes 경고는 RTC(하드웨어 시계)를 로컬 시간대로 읽는 설정이라 시간대 변경이나 일광절약시간 처리에서 문제가 생길 수 있다는 뜻이다. 가능하면 timedatectl set-local-rtc 0 으로 RTC를 UTC로 사용하는 것이 권장된다. 시간대를 서울로 변경한다. nobreak@rocky-node2:~$ sudo timedatectl set-timezone Asia/Seoul nobreak@rocky-node2:~$ timedatectl Local time: Tue 2026-10-06 09:25:32 KST Universal time: Tue 2026-10-06 00:25:32 UTC Time zone: Asia/Seoul (KST, +0900) 사용할 수 있는 모든 시간대 목록은 timedatectl list-timezones 로 확인한다. nobreak@rocky-node2:~$ timedatectl list-timezones Africa/Abidjan Africa/Accra ... 3. 로그의 종류와 위치 리눅스의 로그는 두 가지 형태로 저장된다. 구분 위치 설명 원본(날것) 로그 journalctl systemd 저널에 저장되는 가공되지 않은 로그이다. 부팅 시점의 커널 메시지부터 기록된다. 가공된 로그 /var/log rsyslog가 분류해서 파일별로 저장한 로그이다. nobreak@rocky-node2:~$ journalctl Oct 05 16:52:36 nobreak kernel: Booting Linux on physical CPU 0x0000000000 [0x610f0000] Oct 05 16:52:36 nobreak kernel: Linux version 6.12.0-211.16.1.el10_2.0.1.aarch64 ... ... nobreak@rocky-node2:~$ ls /var/log anaconda audit btmp chrony cron dnf.log firewalld lastlog maillog messages secure wtmp ... 주요 로그 파일은 다음과 같다. 파일 내용 /var/log/secure 인증, 보안 관련 로그(SSH 접속, sudo 사용 등) /var/log/messages 시스템 전반의 일반 메시지 /var/log/cron 크론 작업 로그 /var/log/maillog 메일 관련 로그 /var/log/audit 감사(audit) 로그. SELinux 관련 기록도 여기에 남는다. 4. 로그 설정 파일 : /etc/rsyslog.conf 시스템 어딘가에는 반드시 설정 파일이 존재한다는 점을 기억해야 한다. 로그와 관련된 설정 파일은 /etc/rsyslog.conf 이다. nobreak@rocky-node2:~$ sudo cat /etc/rsyslog.conf ... # The authpriv file has restricted access. authpriv.* action(type="omfile" file="/var/log/secure") # Log all the mail messages in one place. mail.* action(type="omfile" file="/var/log/maillog" sync="on") # Log cron stuff cron.* action(type="omfile" file="/var/log/cron") 설정 형식은 종류(facility).우선순위(priority) 저장 위치 이다. 예를 들어 authpriv.* 는 인증 관련 로그를 모든 우선순위에 대해 /var/log/secure 에 저장한다는 뜻이다. 메타문자 * 자리에는 아래의 우선순위가 들어갈 수 있다. 위에서 아래로 갈수록 덜 심각하다. 우선순위 의미 emerg 시스템 불능 alert 시스템에 치명상 crit 시스템에 영향 err 시스템 에러 (시스템과 관련된 프로그램이 잘못된 경우) warn 시스템 경고 (프로그램이 어찌어찌 구동은 되는 경우) notice 일반 알림 info 단순 정보 debug 디버깅 5. 실시간 로그 확인 : tail -f tail -f 는 파일의 끝부분을 계속 따라가며 새로 추가되는 내용을 실시간으로 보여준다. nobreak@rocky-node2:~$ sudo tail -f /var/log/secure Oct 6 00:20:34 rocky-node2 sshd-session[2510]: Accepted publickey for nobreak from 172.16.72.1 port 64101 ssh2: ED25519 ... Oct 6 00:24:58 rocky-node2 sudo[2590]: nobreak : TTY=pts/0 ; PWD=/home/nobreak ; USER=root ; COMMAND=/bin/timedatectl set-timezone Asia/Seoul Oct 6 00:30:02 rocky-node2 sudo[2631]: nobreak : TTY=pts/0 ; PWD=/home/nobreak ; USER=root ; COMMAND=/bin/cat /etc/rsyslog.conf 누가 어떤 sudo 명령을 실행했는지, 어떤 IP에서 SSH로 접속했는지 모두 기록된다. 로그를 보는 중에 다른 터미널에서 같은 서버로 새로 접속하면 새 로그가 즉시 추가된다. Oct 6 00:37:50 rocky-node2 sshd-session[2666]: Accepted publickey for nobreak from 172.16.72.1 port 64116 ssh2: ED25519 ... Oct 6 00:37:50 rocky-node2 sshd-session[2666]: pam_unix(sshd:session): session opened for user nobreak(uid=1000) by nobreak(uid=0) # 마지막 두 줄이 새 접속으로 생긴 로그이다. 6. 시간 정보를 받아오는 곳 : chrony 서버는 시간을 어디에서 받아오는지는 설정 파일에서 확인한다. Rocky Linux의 NTP 서비스는 chronyd 이고 설정 파일은 /etc/chrony.conf 이다. nobreak@rocky-node2:~$ cat /etc/chrony.conf pool 2.rocky.pool.ntp.org iburst # 이 줄이 핵심이다. sourcedir /run/chrony-dhcp driftfile /var/lib/chrony/drift makestep 1.0 3 rtcsync logdir /var/log/chrony ... 설정 의미 pool 2.rocky.pool.ntp.org 시간을 받아오는 NTP 서버(풀)의 주소이다. iburst 서버와의 시간 동기화를 빠르게 시작하게 해주는 옵션이다. makestep 1.0 3 시계 오차가 1초보다 크면 처음 3번의 업데이트 동안은 시간을 한 번에 맞추도록 허용한다. rtcsync 커널을 통해 RTC(하드웨어 시계)를 동기화한다. logdir chrony 로그 저장 디렉토리이다. 동기화 상태 확인 : chronyc sources nobreak@rocky-node2:~$ chronyc sources MS Name/IP address Stratum Poll Reach LastRx Last sample =============================================================================== ^- 115.15.44.33 4 6 377 40 -853us[ -853us] +/- 87ms ^- 211.222.238.30 2 6 377 42 -116us[ -137us] +/- 11ms ^* 121.134.215.104 2 6 377 41 -1331us[-1352us] +/- 4038us ^? 121.174.142.82 0 9 0 - +0ns[ +0ns] +/- 0ns 가장 중요한 것은 * 표시이다. * 가 붙은 서버가 현재 동기화된 대상이다. -v 옵션을 붙이면 각 기호의 설명이 함께 출력된다. 기호 의미 ^ 서버 모드이다. * 현재 가장 좋은 소스이며 이 서버와 동기화 중이다. + 동기화에 결합되어 사용되는 소스이다. - 결합되지 않은 소스이다. ? 사용할 수 없는 소스이다. x 오류 가능성이 있는 소스이다. ~ 값의 변동이 너무 큰 소스이다. 서비스 재시작 nobreak@rocky-node2:~$ sudo systemctl restart chronyd restart : 프로그램 자체를 완전히 재시작한다. reload : 프로그램은 유지한 채 설정 파일만 다시 읽어 온다. Part 2. 방화벽 관리 (firewalld) 1. 방화벽 개념 방화벽은 네트워크 보안의 기본이다. 허용한 트래픽만 서버 내부로 들여보내고, 허용하지 않은 트래픽은 차단한다. 네트워크 보안에서는 1차 방어선이 가장 중요하며, 방화벽이 그 역할을 한다. AWS에서는 이와 같은 가상 방화벽을 보안 그룹(Security Group) 이라고 부른다. 방화벽을 다루려면 IP 주소와 포트 번호의 개념을 알아야 한다. 사용법은 firewall-cmd --help 로 확인한다. 2. 현재 설정과 영구 설정 firewalld의 설정에는 두 가지가 있다. 구분 설명 현재 설정(runtime) 지금 즉시 적용되지만 reload나 재부팅 시 사라진다. 영구 설정(permanent) 파일에 저장되어 재부팅 후에도 유지되지만 reload 전에는 현재 설정에 반영되지 않는다. # 현재 설정 확인 nobreak@rocky-node2:~$ sudo firewall-cmd --list-all public (default, active) interfaces: enp18s0 enp2s0 services: cockpit dhcpv6-client ssh ports: ... # 영구 설정 확인 (active 표시가 없다) nobreak@rocky-node2:~$ sudo firewall-cmd --list-all --permanent public (default) interfaces: services: cockpit dhcpv6-client ssh ... interfaces : 이 zone이 적용되는 네트워크 인터페이스이다. services : 허용된 서비스이다. ssh 가 허용되어 있으므로 SSH 접속이 가능하다. 영구 설정에는 현재 실제로 어떤 인터페이스가 붙어 있는지는 나오지 않으므로 interfaces 가 비어 있고 active 도 없다. 3. Zone Zone은 용도에 따라 방화벽 정책을 다르게 적용하기 위한 묶음이다. firewall-cmd --list-all-zones 로 전체 zone의 설정을 확인한다. Zone 특징 block 들어오는 연결을 거절(REJECT)한다. drop 들어오는 패킷을 응답 없이 버린다(DROP). dmz 외부에 공개되는 서버용이다. ssh만 허용한다. external 외부 네트워크용이다. 마스커레이딩(NAT)이 켜져 있다. public 기본 zone이다. cockpit, dhcpv6-client, ssh를 허용한다. work 업무 환경용이다. home , internal 신뢰도가 높은 내부 네트워크용이다. mdns, samba-client 등도 허용한다. trusted 모든 연결을 허용한다(ACCEPT). 4. 웹 서버(httpd) 설치와 실행 # 설치 nobreak@rocky-node2:~$ sudo dnf install httpd # 실행 nobreak@rocky-node2:~$ sudo systemctl start httpd # 부팅 시 자동 실행 nobreak@rocky-node2:~$ sudo systemctl enable httpd Created symlink '/etc/systemd/system/multi-user.target.wants/httpd.service' → '/usr/lib/systemd/system/httpd.service'. # 두 가지를 한 번에 nobreak@rocky-node2:~$ sudo systemctl enable httpd --now start 는 지금 한 번 실행하는 것이다. enable 은 심볼릭 링크를 만들어서 시스템이 부팅될 때 자동으로 서비스를 구동하게 한다. enable --now 는 두 동작을 합친 명령어이다. 서버 내부에서 curl localhost 로 웹 서버가 올라간 것을 확인한다. nobreak@rocky-node2:~$ curl localhost <!doctype html> <html> <head> <title>HTTP Server Test Page powered by: Rocky Linux</title> ... 기본 테스트 페이지가 출력된다. 웹 콘텐츠는 /var/www/html/ 에 넣는다. 테스트 페이지를 없애려면 /etc/httpd/conf.d/welcome.conf 를 수정한다. IP 주소는 ip addr show 로 확인한다. nobreak@rocky-node2:~$ ip addr show 2: enp2s0: ... inet 172.16.72.138/24 ... 3: enp18s0: ... inet 192.168.100.11/24 ... 5. 트러블슈팅 : 브라우저에서 접속이 안 되는 경우 5.1 증상과 원인 서버 내부에서는 curl localhost 가 되는데, 외부 브라우저에서 IP 주소로 접속하면 연결되지 않는다. 원인은 방화벽에서 http 서비스가 허용되지 않았기 때문이다. 서버 자체의 문제가 아니라 방화벽이 80번 포트로 들어오는 트래픽을 차단한 것이다. 5.2 해결 : 서비스 허용 # 현재 설정에 추가 (reload하면 사라진다) nobreak@rocky-node2:~$ sudo firewall-cmd --add-service=http success # 영구 설정에 추가 (현재 설정에는 바로 반영되지 않는다) nobreak@rocky-node2:~$ sudo firewall-cmd --add-service=http --permanent success # 영구 설정을 현재 설정에 적용 nobreak@rocky-node2:~$ sudo firewall-cmd --reload success 이렇게 설정하면 브라우저에서 접속할 수 있다. --permanent 만 입력했을 때는 --reload 까지 해야 현재 설정에 반영된다. services 에 http 가 추가된 것을 --list-all 로 확인한다. 5.3 서비스 제거 nobreak@rocky-node2:~$ sudo firewall-cmd --remove-service=http --permanent success nobreak@rocky-node2:~$ sudo firewall-cmd --reload success nobreak@rocky-node2:~$ sudo firewall-cmd --list-all services: cockpit dhcpv6-client ssh # http가 사라졌다. 제거 후에는 브라우저에서 다시 접속할 수 없다. 5.4 포트 번호로 허용 서비스 이름 대신 포트 번호를 직접 열 수도 있다. nobreak@rocky-node2:~$ sudo firewall-cmd --add-port=80/tcp success 포트로 열어도 브라우저에서 접속이 된다. 서비스 방식은 이름만으로 관리할 수 있고, 포트 방식은 표준이 아닌 포트(예: 83번)를 열 때 사용한다. 6. 반드시 기억할 포트 번호 포트 서비스 22 SSH 80 HTTP 443 HTTPS 3306 MySQL / MariaDB 7. 특수 목적 Zone 만들기 DB 서버처럼 특수한 목적의 서버는 별도의 zone을 만들어 방화벽을 설정한다. 여기서는 dbserver 라는 zone을 만들어 3306 포트만 허용한다. # zone 생성 : zone은 영구 설정이므로 --permanent가 필수이다. nobreak@rocky-node2:~$ sudo firewall-cmd --new-zone=dbserver Option can be used only with --permanent. # --permanent 없이 실행하면 오류가 발생한다. nobreak@rocky-node2:~$ sudo firewall-cmd --new-zone=dbserver --permanent success # zone에 포트 허용 nobreak@rocky-node2:~$ sudo firewall-cmd --add-port=3306/tcp --permanent --zone=dbserver success # reload로 zone을 인식시킨다. nobreak@rocky-node2:~$ sudo firewall-cmd --reload success # 기본 zone을 dbserver로 변경 nobreak@rocky-node2:~$ sudo firewall-cmd --set-default-zone=dbserver success # 확인 nobreak@rocky-node2:~$ sudo firewall-cmd --list-all dbserver (default, active) interfaces: enp2s0 services: ports: 3306/tcp ... 실습을 마친 뒤에는 원래 상태로 복구한다. # 기본 zone을 public으로 복귀 nobreak@rocky-node2:~$ sudo firewall-cmd --set-default-zone=public success # 만든 zone 삭제 nobreak@rocky-node2:~$ sudo firewall-cmd --delete-zone=dbserver --permanent success 주의. dbserver zone에는 ssh 서비스가 허용되어 있지 않다. 이 zone을 기본 zone으로 지정하면 새로운 SSH 접속이 차단될 수 있다. 원격 서버에서 작업할 때는 반드시 --add-service=ssh 를 먼저 추가하거나 콘솔로 접근할 수 있는 상태에서 실습해야 한다. 8. 핵심 정리 로그는 시간이 정확해야 의미가 있으므로 NTP(chrony)로 시간을 동기화한다. 시간 설정은 timedatectl , 동기화 상태는 chronyc sources ( * 표시)로 확인한다. 원본 로그는 journalctl , 가공된 로그는 /var/log 에 있으며 설정은 /etc/rsyslog.conf 에서 한다. 로그를 실시간으로 확인할 때는 tail -f 를 사용한다. restart 는 프로그램 재시작, reload 는 설정 파일 재로드이다. 방화벽은 1차 방어선이며, 설정에는 현재 설정과 영구 설정이 있다. 영구 설정( --permanent )은 --reload 를 해야 현재 설정에 반영된다. 용도별로 zone을 나누어 정책을 관리하며, zone 생성과 삭제는 --permanent 가 필요하다. 서비스는 필요한 포트만 열고, 22 · 80 · 443 · 3306 포트 번호는 반드시 기억한다.
주문 기능에서는 단순히 주문 데이터만 저장하는 것이 아니라 포인트 차감과 주문 생성이 하나의 흐름으로 함께 처리됩니다. 전체 흐름은 다음과 같습니다. 포인트 조회 ↓ 포인트 잔액 확인 ↓ 포인트 차감 ↓ Order 저장 ↓ OrderItem 저장 이 과정에서 중간 작업 하나가 실패하면 데이터 불일치가 발생할 수 있습니다. 예를 들어 포인트 차감은 성공했지만 주문 저장 과정에서 오류가 발생하는 경우입니다. 포인트 차감 성공 ↓ 주문 저장 실패 ↓ 주문은 없지만 포인트는 감소한 상태 이런 상황을 방지하기 위해 주문 처리 로직에 @Transactional 을 적용했습니다. @Transactional public CreateOrderResponse createOrder(...) { // 포인트 확인 // 포인트 차감 // 주문 저장 // 주문 상품 저장 } @Transactional 을 사용하면 해당 메서드 안에서 수행되는 작업들을 하나의 트랜잭션으로 묶어서 처리할 수 있습니다. 처리 중간에 예외가 발생하면 이전에 수행된 변경사항도 함께 Rollback됩니다. 포인트 차감 ↓ Order 저장 ↓ OrderItem 저장 중 오류 ↓ Rollback ↓ 포인트 차감도 취소 Order 저장도 취소 즉 주문과 관련된 데이터가 모두 성공 또는 모두 실패 하도록 처리할 수 있습니다. 적용 이유 포인트 차감과 주문 생성은 서로 독립적인 작업처럼 보이지만 실제로는 하나의 주문을 완성하기 위해 반드시 함께 성공해야 하는 작업입니다. 따라서 다음 작업들을 하나의 트랜잭션 범위 안에서 처리했습니다. Point 차감 Order 저장 OrderItem 저장 이를 통해 일부 데이터만 반영되는 상황을 방지하고 주문 데이터와 포인트 데이터의 일관성을 유지할 수 있도록 했습니다. 핵심 정리 서로 연관된 여러 DB 작업은 하나의 트랜잭션으로 묶을 수 있습니다. 처리 중 예외가 발생하면 이전 변경사항도 Rollback됩니다. 포인트 차감과 주문 저장처럼 같이 성공해야 하는 작업에 적합합니다. 트랜잭션을 통해 데이터가 일부만 반영되는 문제를 방지할 수 있습니다.
[simsim-arcade 팀 프로젝트] 1. 스카이 에이스 보스 디자인 보강 먼저 기존 스카이 에이스의 보스 디자인을 전반적으로 점검하고, 보스가 등장하는 화면의 시각적 완성도를 높이는 작업을 진행했다. 이번 작업에서는 특히 다음 보스들을 대상으로 디자인을 보강했다. 골리앗 크라켄 이그니스 기존 게임 플레이 구조를 변경하기보다는, 실제 플레이 과정에서 플레이어가 보스를 마주했을 때 각 스테이지의 최종 적이라는 느낌이 더욱 명확하게 전달되도록 시각적인 부분을 강화하는 데 초점을 맞췄다. 특히 보스가 단순한 이미지 요소처럼 보이지 않고, 게임의 스테이지와 자연스럽게 연결될 수 있도록 각 보스의 개성과 콘셉트를 강조했다. 주요 변경 범위 골리앗 2페이지 디자인 보강 크라켄 전 페이지 디자인 보강 이그니스 전 페이지 디자인 보강 보스별 화면 연출과 디자인 요소 정리 이를 통해 스카이 에이스의 보스전이 스테이지별로 보다 뚜렷한 개성을 가질 수 있도록 개선했다. 2. 3스테이지 보스 「이그니스」 재디자인 이번 업데이트에서 가장 큰 변화 중 하나는 3스테이지 보스 이그니스의 콘셉트 변경이다. 기존 이그니스의 디자인을 단순히 수정하는 것이 아니라, 화염을 사용하는 거대한 공중 항모라는 새로운 방향으로 재디자인했다. 이그니스 — 화염 공중 항모 새로운 이그니스는 이름에서 느껴지는 화염 이미지를 단순한 불꽃 효과에만 의존하지 않고, 거대한 공중 전함·항모 형태의 보스라는 콘셉트로 확장했다. 이를 통해 3스테이지의 최종 보스가 단순한 적 기체가 아니라, 플레이어가 마지막까지 상대해야 하는 거대한 공중 병기처럼 느껴지도록 방향을 잡았다. 특히 이번 변경은 보스의 외형 자체에 스테이지의 분위기와 전투 규모를 반영하는 데 의미가 있다. 즉, 기존 보스 이미지 → 화염을 사용하는 보스 에서 화염 + 거대한 공중 항모 + 최종 보스급 위압감 을 갖춘 형태로 콘셉트를 확장한 것이다. 3. 보스 2페이지 디자인 개편 이후 추가 작업으로 스카이 에이스 보스 2페이지의 디자인을 다시 개편했다. 이번 작업 대상은 다음과 같다. 골리앗 크라켄 이그니스 즉, 특정 한 보스만 수정한 것이 아니라 보스 2페이지 전반을 함께 정리했다. 보스전 화면에서 각 보스가 등장하는 상황과 디자인이 서로 따로 노는 느낌을 줄이고, 스카이 에이스 전체의 게임 분위기 안에서 자연스럽게 연결될 수 있도록 디자인을 다시 조정했다. 특히 글리앗과 크라켄, 이그니스가 서로 다른 보스라는 점이 화면에서도 확실하게 드러나도록 각각의 개성을 살리는 방향으로 작업했다. 마무리 오늘 심심오락실에서는 스카이 에이스의 보스 디자인을 집중적으로 개선했다. 특히 이번 작업에서는 단순한 UI 수정이 아니라 각 보스가 가지고 있는 캐릭터성과 스테이지별 분위기를 시각적으로 강화하는 데 집중했다. 그중에서도 3스테이지의 이그니스를 화염 공중 항모로 재디자인한 작업은 스카이 에이스의 후반부 전투 규모와 긴장감을 표현하는 데 중요한 변화가 되었다. 또한 글리앗, 크라켄, 이그니스의 보스 2페이지 디자인까지 함께 개편하면서, 스카이 에이스의 보스전 화면 전체적인 완성도도 한 단계 높일 수 있었다. 앞으로도 단순히 게임을 플레이할 수 있는 수준에서 끝나는 것이 아니라, 각 스테이지와 보스마다 확실한 개성과 연출을 느낄 수 있는 아케이드 게임을 만드는 것을 목표로 계속 개선해 나갈 예정이다.
아침마다 머리 말리는 데 십 분 넘게 걸리는 긴 머리라면, 바람 세기부터 다른 드라이기가 답이에요. JMW M5001A PLUS PRO는 미용실에서 쓰는 브랜드로 유명한 JMW의 스테디셀러 모델로, 헤어 드라이기에 항공모터로 불리는 BLDC 모터를 얹은 제품이에요. 일단 숫자로 정리하면 BLDC 디지털 터보 모터에 풍속은 12m/s대, 소비전력 1,700W예요. 무게는 코드 빼고 430g 정도라 손목 부담이 적은 편이에요. 모터와 히터가 따로 제어돼서 냉풍 버튼을 누르면 미열 없이 온전한 찬바람이 나오고, 음이온 방출로 정전기도 잡아줘요. 스토어 평점 4.88에 리뷰가 14,784건, 가격은 정가 130,000원에서 34% 할인된 85,000원이에요. 구매 전에 알아둘 점 풍속과 온도가 각각 2단뿐이라 세밀한 조절은 안 돼요. 바람 세기와 온도를 따로따로 조합하는 방식도 아니에요. 무광 블랙 마감이라 지문이나 먼지가 눈에 잘 띄고, 바람이 센 만큼 완전히 조용한 제품은 아니에요. 그리고 1~2만 원짜리 저가형과 비교하면 분명 비싼 몸값이에요. 싼 드라이기나 프리미엄급과 비교하면 1 2만 원대 DC 모터 제품은 바람이 약해서 결국 뜨거운 열로 말리게 되는데, 이러면 머릿결이 상하기 쉬워요. 이 제품은 바람 자체가 세서 열에 덜 의존해요. 반대로 40 50만 원대 프리미엄 드라이기와 비교하면 핵심인 강풍과 냉풍 전환은 갖추면서 가격은 5분의 1 수준이에요. M50 시리즈가 누적 100만 대 이상 팔린 모델이라 내구성 평판도 누적돼 있어요. 매일 아침 달라지는 부분 샴푸 후 타월 드라이만 하고 이걸 켜면 긴 머리도 몇 분이면 뿌리까지 말라요. 다 말리고 나서 쿨 버튼을 눌러 냉풍으로 마무리하면 정전기 없이 차분하게 정돈돼요. 아이 머리 말려줄 때 온풍과 냉풍을 버튼 하나로 오가며 쓸 수 있는 것도 편한 부분이에요. 코드가 2m라 콘센트 위치에 덜 얽매이는 것도 은근히 쓸모 있어요. 이런 분께 맞아요 머리 말리는 시간을 줄이고 싶은 긴 머리, 열 손상이 걱정돼 냉풍 위주로 말리는 분, 한번 사서 오래 쓸 드라이기를 찾는 분이면 잘 맞아요. 반대로 짧은 머리라 몇 분이면 마르는 분, 스타일링용 세밀한 온도 조절이 필요한 분, 가벼운 여행용을 찾는 분은 더 저렴하거나 작은 선택지가 나아요. 성능 대비 가격은 JMW > 프리미엄 브랜드, 초기 비용은 저가형이 앞서요. 결국 매일 쓰는 물건이니 하루 몇 분의 차이가 1년이면 꽤 커져요. 리뷰 만사천 건 넘게 쌓인 스테디셀러를 할인가에 살 수 있는 조합이니, 드라이기 교체를 고민 중이었다면 아래 링크에서 스펙을 확인해보세요. 이 포스팅은 네이버 쇼핑커넥트 활동의 일환으로, 판매 발생 시 수수료를 제공받습니다. 🛒 https://naver.me/5reIe8HB