Profile img

자손킴

@jasonkim@hackers.pub · 11 following · 18 followers

도커로 구축한 랩에서 혼자 실습하며 배우는 네트워크 프로토콜 입문 #3-1

자손킴 @jasonkim@hackers.pub

이 글은 네트워크 계층(L3) 프로토콜의 핵심인 IP에 대해 심도 있게 다룬다. L3 라우터의 역할부터 시작하여, IPv4의 구조, IP 단편화 과정, 그리고 PMTUD(Path MTU Discovery)의 중요성을 설명한다. IP 헤더의 각 필드(버전, 헤더 길이, ToS, 패킷 길이 등)에 대한 자세한 분석을 제공하며, IP 주소와 서브넷 마스크를 통해 네트워크와 호스트를 구분하는 방법을 설명한다. 클래스풀 주소 지정 방식과 클래스리스 주소 지정 방식의 차이점을 비교하고, 공인 IP 주소와 사설 IP 주소의 개념을 명확히 한다. 마지막으로, ICMP(Internet Control Message Protocol)를 통해 IP 레벨의 통신 상태를 확인하고 오류를 알리는 방법을 소개한다. 이 글을 통해 독자는 IP 프로토콜의 기본 원리를 이해하고, 네트워크 문제 해결 능력을 향상시킬 수 있을 것이다.

Read more →
4

도커로 구축한 랩에서 혼자 실습하며 배우는 네트워크 프로토콜 입문 #2

자손킴 @jasonkim@hackers.pub

이 글은 동일 네트워크 내 장비들이 L2 스위치를 통해 통신하는 방식에 대해 설명합니다. MAC 주소를 기반으로 통신하는 L2 계층은 통신 시작 시 IP 주소만 알고 MAC 주소를 모르는 상황을 해결하기 위해 ARP를 사용합니다. MAC 주소와 IP 주소의 역할, 이더넷 프레임 구조, 그리고 ARP의 동작 방식과 한계점을 상세히 다룹니다. 또한 L2 스위칭의 핵심인 MAC 주소 테이블과 flooding 과정을 설명하고, MAC 주소 중복 문제와 VLAN 기술을 소개합니다. 마지막으로, OVS 환경에서 VLAN 설정 시 vlan_mode와 tag의 의미를 명확히 구분하여 네트워크 구성에 대한 이해를 돕습니다.

Read more →
4

Ailrun (UTC-5/-4) replied to the below article:

공허한 참

박준규 @curry@hackers.pub

하스켈의 `all` 함수에 빈 리스트를 넣었을 때 왜 `True`가 반환되는지에 대한 의문을 "공허한 참(Vacuous truth)"이라는 개념을 통해 탐구합니다. 흔히 '구현이 그렇게 되어 있으니까'라고 생각할 수 있지만, 저자는 이 현상을 논리적으로 분석합니다. `all` 함수의 구현 방식과, 빈 리스트에 대한 연산 결과가 전체 결과에 미치는 영향을 설명하며, 공집합의 모든 원소가 짝수라는 명제가 참인 이유와 유사한 논리적 근거를 제시합니다. 이를 통해 코드와 수학 간의 연결고리를 발견하고, 마지막으로 ChatGPT가 생성한 유머러스한 이미지를 곁들여 독자에게 즐거움을 선사합니다.

Read more →
5

@curry박준규 all을 다음과 같이 정의하면 문제가 무엇일까요?

all p [] = False
all p [x] = p x
all p (x:xs) = p x && all xs

이 질문에 대한 대답 중 all의 의미에 관한 것이 있을 겁니다. 논리적으로 "모든 ...에 대해"를 어떻게 이해해야 하는냐에 대한 것 말이지요.

공집합을 직접 사용하는 것이 가장 간단한 예시겠지만, 좀 더 논리학에서 자주 사용되는 예시로는 "20세기의 모든 프랑스 왕은 대머리다"가 있겠습니다. 이는 무의미하게 (Vacuously) 참인데요, 왜냐면 19세기를 마지막으로 프랑스에는 더 이상 왕이 없기 때문이지요. 즉, 일반적으로 "모든 ...에 대해"에서 "..." 부분이 (결과적으로) 공집합일 경우 "모든 ..."에 의해 수식된 본문이 어떤 문장인지와는 상관 없이 참이라고 이해한다는 것이지요.

5

자손킴 shared the below article:

파서 콤비네이터: 하스켈 초보자를 위한 파싱

박준규 @curry@hackers.pub

이 글은 하스켈 초보자를 위한 파서 컴비네이터에 대한 입문 튜토리얼입니다. 파싱은 프로그래밍에서 흔히 발생하는 작업이지만, 정규 표현식이나 문자열 조작만으로는 복잡한 형식을 다루기 어렵습니다. 저자는 `Text.ParserCombinators.ReadP` 라이브러리를 사용하여 파서 컴비네이터를 소개하고, 이를 통해 더 읽기 쉽고 유지보수가 용이한 파서를 작성할 수 있음을 보여줍니다. METAR 보고서 파싱 예제를 통해 `satisfy`, `many1`, `<|>`, `option` 등의 기본적인 파서 콤비네이터 함수를 설명하고, 펑터와 모나드의 개념을 활용하여 파서를 구성하는 방법을 안내합니다. 또한, 파싱된 데이터의 유효성을 검사하고, 결과를 더 의미 있는 데이터 타입으로 변환하는 방법을 제시합니다. 이 튜토리얼을 통해 독자는 파서 컴비네이터의 기본 원리를 이해하고, 실제 데이터 파싱 작업에 적용할 수 있는 능력을 얻게 됩니다. 마지막으로, 저자는 독자들에게 배운 내용을 바탕으로 전체 METAR 보고서를 파싱하는 라이브러리를 만들어 Hackage에 제출해 볼 것을 권장하며, 파서가 없는 데이터를 만났을 때 `ReadP`를 자신 있게 사용할 수 있기를 바랍니다.

Read more →
11
4

stride 구현에 앞서 어떻게 동작하는건지 정리해봤다.

  • 모든 프로세스는 각자의 '보폭(stride)'을 가진다.
  • 스케쥴러가 프로세스를 실행시키면 해당 프로세스는 'stride'만큼의 '거리(pass)'를 이동한다.
  • 스케쥴러는 항상 가장 '짧은 거리를 이동한(pass가 가장 작은)' 프로세스를 실행시킨다.
  • 결과적으로 보폭이 작을수록 더 자주 실행된다.

몇가지 고려해야 할 것들이 떠올랐다.

  • stride로 바뀌면 lottery와는 달리 더 자주 실행되어야 할 프로세스에 작은 값을 부여해야한다. 사용자 입장에서 좋은 방식인가?
  • 신규 프로세스 실행 비율에 대한 공정성이 지켜지는지?
  • pass가 누적되면 overflow 될 가능성은 없나?
  • 가장 작은 pass를 찾는 효율적인 방법

그리고 다음과 같이 결정했다.

  • 우선순위를 정하는 API인 setticket은 그대로 유지하자.
  • tickets의 반비례되는 수인 stride를 만들기 위해 max_tickets(교재의 L)를 정한다.
    • stride = L / tickets
  • 신규 프로세스에는 현재 실행중인 프로세스중에서 가장 작은 pass를 부여하여 공정성을 유지한다.
  • pass를 uin64로 지정하고 max_tickets를 2^32로만 잡아도 최대 42.9억번 정도의 tick이 지나야 overflow가 발생한다. 지금은 고려하지 말자.
  • 초기 구현은 선형탐색으로 단순하게 구현하고 이후 최적화를 하자.
1

그동안 Conal이란걸 만들고 있었습니다. Classic FRP 라이브러리입니다.

또, 소개글 쓰려니까 머리 아파서, 클로드랑 즉석 팟캐스트를 열었습니다. 술술 읽혔으면 좋겠네요.

당장 프로덕션에 쓰려면 개선할 부분이 많습니다. 피드백과 기여 환영합니다.

8
8

스터디에 참여하고 계신 조교(?)님 께서 lottery scheduler 구현을 리뷰해주셨다. 혹시나 잘못 이해하고 구현한 부분이 있을까봐 걱정했는데, 리뷰해주신 덕분에 잘못된 부분은 없음을 확인했다. 더불어서 C로 코딩할때 유의해야 할 부분들을 배울 수 있었다.

0

lottery scheduler를 구현했다. 이어서 스케쥴러가 정해진 티켓 비율만큼 프로세스를 실행하는지 테스트하는 프로그램을 작성하기 위해 계획을 세웠다. 자식 프로세스의 pid를 알면 일정 주기마다 getpinfo를 호출하여 프로세스가 실행된 tick을 구해 계산 할 수 있을 것이다. 그런데 부모 프로세스가 자식의 pid를 알려면 어떻게 해야하지? 여기서 갑자기 혼란이 생겼다.

fork의 반환값은 0이면 자식이고 0보다 크면 부모이다. 0보다 작다면 실패이다. 이것을 사용하면 포크를 실행한 부모 프로세스인지 포크로 만들어진 자식 프로세스인지 알 수 있다.

내가 혼란스러웠던 부분은 코드를 작성하는 입장에서 fork의 호출은 한 번이지만 이후 실행은 2개로 갈라져서 반환 값이 2개가 된다는 사실을 정확히 인지하지 못해서였다. 부모인지 자식인지 판단하는 코드를 나눠서 생각하지 못하고 오로지 부모 입장에서만 생각하다보니 0도 부모가 리턴 받는다고 착각을 했던 것이다.

이제 부모가 자식을 더 잘 알 수 있게 되었으니 마저 코딩을 하도록 해야겠다.

0

settickets, getpinfo를 구현하고 ps를 구현했다. pstat을 전역 변수로 두고 관리하려 했으나 proc, pstat을 각각 관리하면 복잡해 진다는 것을 깨달았다. 다른 스터디원분이 먼저 작성하신 코드를 참고하여 proc에 ticks를 추가하고 getpinfo 호출시 pstat을 만드는 방향으로 계획을 바꿨다. 유저 공간에서 시스템콜에 포인터를 넘기고, 그 포인터에 값을 채워받는 것을 구현하는게 난관이었는데 Claude Code가 해결해줘서 넘어갔다.

0

스케쥴러가 어떤 프로세스를 실행중인지 확인해보기 위해 컨텍스트 스위칭 대상이 되는 프로세스의 pid를 출력해봤다. 기본 구현은 라운드 로빈이기 때문에 순서대로 순환되며 출력 될거라 예상했지만 순서가 뒤섞여서 출력이 되었다. 원인은 CPUS 옵션을 주지 않으면 기본값이 3으로 설정되기 때문에 여러 스케쥴러가 동시에 실행되어서 그런 것이었다. CPUS를 1로 설정하자 기대한대로 순서대로 순환되어 출력하는 것을 확인하였다.

0

scheduler 함수의 분석을 시작했다. scheduler는 컨텍스트 스위칭을 위해 swtch함수를 호출하는데, 이 함수는 어셈블리로 작성되어 있는지라 언제 리턴 되는지 더더욱 파악하기가 어려웠다. (사실 어셈블리가 아니었어도 파악하기는 쉽지 않았을거다.)

Claude Code에게 코드베이스 분석을 맡기고 몇가지 문답을 통해 타이머 인터럽트가 trap 핸들러를 호출하고, trap 핸들러가 yield->sched->swtch흐름으로 호출하여 scheduler함수로 돌아간다는 사실을 파악했다. 이 과정이 반복되며 프로세스는 순차 실행되고 그 사이사이에 scheduler가 교차 실행된다는 것을 알게 되었다.

책에 설명이 있긴 하지만 코드를 한땀한땀 분석했으면 오래 걸렸을텐데, Claude Code로 시간을 아낄 수 있었다. 예전엔 공부할 엄두가 안나던 것들이 LLM덕분에 조금은 가벼운 마음으로 시작해 볼 수 있게 되었다. 좋은 세상이다.

1

현재의 스케쥴러 구현인 라운드 로빈 방식을 이해해볼겸 루프안에서 fork를 사용해 n개의 프로세스를 생성하는 간단한 코드를 작성했다.

프로세스가 기대와 달리 n개가 생성되는게 아닌, (말 그대로) 기하급수적으로 생성되어서 원인을 생각해봤다. fork는 프로세스를 처음부터 실행하는게 아니라 현재까지 실행된 상태를 복제하여 새로운 프로세스를 만들고, 자기자신의 프로세스도 계속해서 실행하기 때문이다.

그렇기 때문에 n개의 프로세스를 만들려면 fork를 부모 프로세스만 실행할 수 있도록 자식 프로세스는 원하는 작업을 실행하고 루프를 탈출시켜야 한다는 일종의 fork 사용에 대한 관례를 배웠다.

사실 책 앞에 다 나왔던 내용인데 대충 읽었더니 이렇게 멀리 돌아가는 고생을 하고 있다. ㅋㅋㅋ

0

난수 생성기가 만만해보여 먼저 찾아봤다. 적당히 복붙해서 구현하려 했으나 균일성, 편향제거, 동시성, 병렬성등 고려해야 할게 많다는 사실을 새삼 깨달았다. 그동안 단일 스레드 환경에서 편하게 살았음을 실감했다.

1
0
4
1

여성향 커미션 중개 플랫폼 크레페를 운영하는 쿠키플레이스에서 시니어 백엔드 엔지니어 채용을 진행중입니다. 채용공고에 해당 직무 소개, 복지, 연봉, 회사문화의 내용이 포함돼 있습니다. 많은 관심 부탁드립니다. Node.js, TypeScript, GraphQL에 대한 높은 숙련도 및 지식으로 팀에 기여해주실 분을 쿠키플레이스에서 극진히 기대하고 있습니다.

크레페에서는 이런 기술스택을 사용합니다

  • Node.js, TypeScript, Vitest, Fastify
  • GraphQL  - Yoga, Relay, Pothos, Prisma
  • ElasticSearch, MongoDB, FCM
  • Docker, Github Actions
  • AWS  - ElasticBeanstalk, CloudWatch, Aurora PostgreSQL, Lambda, SES, S3, ElastiCache (Redis)
  • Grafana, Sentry

구성원의 성장과 덕질을 지원해요

  • 희망 도서 구매 (만화책 및 TRPG 룰북 포함)
  • 워크샵 및 교육 프로그램 지원
  • 전시, 공연 및 각종 행사 티켓 지원
  • 월 5만 크레페 포인트
  • 전동작탁 AMOS JP-EX COLOR
  • 6인용 TRPG/보드게임 테이블

지원자님이 예상하실 수 있는 처우는 이래요

  • 연봉: 최소 8000만원 ~ 최대 2억원 (주 40시간)
  • 스톡옵션 부여에 열려있는 포지션
크레페, 나만의 레시피, 나만의 창작물
10
1
0
0

자손킴 replied to the below article:

펑터Functor

lionhairdino @lionhairdino@hackers.pub

하스켈 펑터 입문자를 위한 이 글은 `Maybe Int` 타입의 값에서 `Int`를 직접 "꺼내올 수 없다"는 개념을 설명합니다. `Maybe`의 `fmap`이나 `fromJust`가 마치 값을 꺼내는 것처럼 보이지만, 이는 실제로는 값을 꺼내는 것이 아니라, 원본 타입(`Int`)의 구조를 보존하며 새로운 `Maybe Int` 타입의 값을 "생성"하는 과정이라는 것입니다. 미끄럼틀 비유를 통해, `Maybe Int`의 `Just 1`은 `Int` 값 `1`과 연관되어 있지만, `Just 1` 자체가 `1`을 의미하는 것은 아닙니다. 펑터는 원본 타입의 관계(구조)를 그대로 유지하며 다른 타입으로 변환하는 역할을 합니다. `fmap`은 `Maybe Int` 안의 `Int`를 직접 조작하는 것이 아니라, 원본 `Int` 값의 관계를 바탕으로 새로운 `Maybe Int` 값을 만들어내는 것입니다. 상자 메타포가 유용할 때도 있지만, 펑터의 본질을 오해하게 만들 수 있습니다. 상자 안의 값을 꺼내는 것이 아니라, 값의 "성격"은 값을 다루는 함수들의 동작에 따라 결정된다는 점을 강조합니다. 이 글은 "없을 수도 있는 수를 꺼낸다"는 표현의 모순을 지적하며, 펑터의 개념을 더 깊이 이해하도록 돕습니다.

Read more →
6
1

tanstack query의 initialPageParam에 대하여 오늘 배운 것

자손킴 @jasonkim@hackers.pub

TanStack Query의 `useInfiniteQuery` 훅을 사용할 때 `initialPageParam`이 어떻게 동작하는지에 대한 중요한 통찰을 공유합니다. 이 훅은 초기 렌더링 시 `initialPageParam`을 `pageParams[0]`으로 설정하고, 동일한 `queryKey`를 가진 캐시가 유지되는 동안 이 값을 계속 사용합니다. 따라서 여러 컴포넌트에서 동일한 `queryKey`로 `useInfiniteQuery`를 호출하면서 다른 `initialPageParam` 값을 제공하더라도, 처음 호출된 `initialPageParam` 값으로 고정됩니다. 이는 시작 커서가 다를 경우 `queryKey`를 다르게 지정해야 함을 의미합니다. 이러한 동작은 이해하고 나면 당연하지만, 익숙하지 않은 개발자에게는 혼란스러울 수 있습니다. `initialPageParam`이 `queryKey`와 강하게 연결되어 있다는 점이 InfiniteQueryOptions에서 타입 제약으로 더 명확하게 표현된다면 개발 경험이 향상될 것입니다.

Read more →
6