항목 이 구현의 선택 빈 블록 관리 묵시적 빈 리스트 블록 형식 헤더 + 페이로드 + 푸터 (경계 태그) 배치 정책 first fit 연결 free 시점에 즉시 연결 정렬 8바이트 (더블워드) 워드 크기 4바이트 0. 할당기가 풀어야 할 네 가지 질문 (9.9.5) 가장 단순한 할당기는 힙을 큰 바이트 배열로 보고 포인터 p 만 유지함. malloc 은 p 를 size만큼 올리고, free 는 아무것도 안 함. 처리량은 최고지만 블록을 재사용하지 않아서 이용도는 최악임. 그래서 실용적인 할당기는 네 가지를 정해야 함. 질문 이 구현의 답 코드 빈 블록을 어떻게 추적? 묵시적 리스트 헤더 크기를 따라 순회 어느 빈 블록에 넣을까? (배치) first fit find_fit 남는 부분은? (분할) 나머지가 16B 이상이면 분할 place 해제된 블록은? (연결) 즉시 연결 mm_free → coalesce 할당기의 제약과 목표 (9.9.3) 제약 임의의 malloc / free 순서를 처리해야 함 요청을 모았다가 재배열하지 않고 즉시 응답 해야 함 할당기의 자료구조도 힙 안에만 저장해야 함 정렬 요구를 지켜야 함 이미 할당된 블록은 수정/이동 금지 임 (그래서 압축 불가) 목표 처리량 : 단위 시간당 처리하는 요청 수 메모리 이용도 : 응용이 요청한 데이터 총량 ÷ 힙 크기 이 둘은 서로 충돌함. 빠르게 하면 알뜰하지 못하고, 알뜰하게 하려면 오래 탐색해야 함. 그 균형을 잡는 게 설계의 핵심임. 1. 블록 구조 힙 전체의 모양 [ 패딩 ][ 프롤로그 ][ 일반 블록 ][ 일반 블록 ] ... [ 에필로그 ] ↑ heap_listp 구성 설명 패딩 정렬을 맞추기 위한 빈 칸(4B) 프롤로그 크기 8B, 항상 allocated . 헤더+푸터만 있는 가짜 블록 일반 블록 malloc / free 로 생기는 블록들 에필로그 헤더만 있고 크기 0, allocated 비트 1. 힙의 끝 표지 프롤로그/에필로그를 두는 이유 연결( coalesce )할 때 현재 블록의 앞/뒤 블록 상태를 확인함. 힙 맨 앞/뒤 블록이면 이웃이 없을 수 있어서, 원래는 "이웃이 존재하는가?"를 따로 검사해야 함. 항상 allocated인 가짜 블록을 양 끝에 두면, 모든 블록에 대해 이 검사 없이 똑같은 코드로 처리됨. 에필로그의 값이 0/1 인 이유 크기 0 : 정상 블록은 최소 크기 때문에 0일 수 없음. 헤더를 읽었는데 크기가 0이면 "힙의 끝"으로 판단하고 탐색을 멈출 수 있음. alloc = 1 : 0이면 할당기가 free 블록으로 착각해서 쓰려고 할 수 있음. 절대 가용 블록으로 취급하지 않게 1로 설정함. 블록 하나의 구조 ┌─────────┬──────────────────┬─────────┐ │ 헤더 4B │ payload │ 푸터 4B │ │ size|a │ │ size|a │ └─────────┴──────────────────┴─────────┘ ↑ bp 헤더/푸터 : 블록 전체 크기(헤더+페이로드+푸터+패딩)와 할당 여부를 담음. payload : 사용자가 실제로 쓰는 공간임. int *p = malloc(20); 에서 p 가 가리키는 곳임. bp : 첫 번째 payload 바이트를 가리키는 포인터임. 블록의 시작이 아니라 payload의 시작 이라는 점이 제일 중요함. 응용 프로그램은 헤더의 존재를 모름. 할당기 내부에서 헤더에 접근할 때만 bp 에서 4바이트를 빼서 찾음. 헤더/푸터가 필요한 이유 블록이 얼마나 큰지, 사용 중인지 알아야 함. 앞뒤에 어떤 블록이 있는지 알아야 함. free 할 때 어디와 합칠지 알아야 함. 헤더 32비트를 쪼개 쓰는 법 31 3 2 1 0 ┌──────────────────────────┬──┬──┬──┐ │ block size │ 0│ 0│ a│ a=1: 사용 중, a=0: free └──────────────────────────┴──┴──┴──┘ 가능한 이유는 모든 블록의 크기가 8의 배수이기 때문 임. 32 = 0010 0000 40 = 0010 1000 48 = 0011 0000 8 = 23이라서 8의 배수는 이진수 끝 3자리가 항상 000 임. 어차피 비어 있는 자리라서 그중 최하위 비트를 "allocated" 정보로 재활용함. 예를 들어 할당된 24바이트( 0x18 ) 블록의 헤더는 0x18 | 0x1 = 0x19 , 빈 40바이트( 0x28 ) 블록의 헤더는 0x28 | 0x0 = 0x28 임. 이걸 다루는 매크로가 PACK() , GET_SIZE() , GET_ALLOC() 임. 왜 8의 배수여야 하나 → 정렬때문! malloc 이 반환한 메모리에 어떤 타입이 들어갈지 할당기는 모름. 그래서 가장 엄격한 정렬로 맞춰 둠. 정렬이 안 맞으면 느려지거나, 일부 CPU/명령어에서는 오류가 남. 이 구현은 payload 시작 주소를 8의 배수 로 맞춤. 패딩 워드를 맨 앞에 넣는 이유 패딩 없이 시작: [헤더 4B][payload] payload 시작 = 0x04 X 패딩 4B 추가: [패딩 4B][헤더 4B][payload] payload 시작 = 0x08 O 헤더가 4바이트라서, 헤더 바로 뒤의 payload를 8의 배수에 두려면 헤더가 8n+4 위치에 있어야 함. 첫 블록만 맞으면 뒤도 다 맞음. 블록 크기가 항상 8의 배수 라서 다음 헤더도 8n+4 위치가 되기 때문임. 묵시적 리스트와 최소 블록 크기 묵시적 리스트 빈 블록끼리 포인터로 연결돼 있지 않음. 헤더의 크기 필드를 따라 힙의 모든 블록을 훑어서 빈 블록을 찾음. 그래서 "묵시적"임. 장점은 단순함이고, 단점은 탐색 비용이 (할당+빈) 전체 블록 수에 비례 한다는 것임. 최소 블록 크기: 16바이트 헤더 4B + 푸터 4B + 정렬을 위한 최소 payload 8B임. 1바이트를 요청해도 16바이트 블록이 만들어짐. 할당기가 하는 일 요약 malloc() → free block을 찾아서 allocated로 변경 free() → allocated block을 free로 바꾸고 옆 free block과 합침 extend_heap() → 에필로그 자리에 새로운 free block 추가 2. memlib.c - 힙 공간 구현 (9.9.1) 실제 프로세스 힙 대신 큰 배열을 힙처럼 만들어 사용함. void *mem_sbrk(int incr) { char *old_brk = mem_brk; if ((incr < 0) || ((mem_brk + incr) > mem_max_addr)) { errno = ENOMEM; return (void *)-1; } mem_brk += incr; return (void *)old_brk; // 늘리기 전 brk를 반환 } 진짜 sbrk 와 같은 인터페이스 늘리기 전의 brk를 반환 함. 이 값이 새로 얻은 영역의 시작 주소라서 extend_heap 에서 bp 가 됨. 진짜 sbrk 와 다른 점은 힙 축소(음수 incr)를 거부 한다는 것임. free(ptr) 에는 malloc 이 반환한 포인터를 넘겨야 함. mm_free 는 bp-4 를 헤더로 보고 접근하기 때문에, 잘못된 포인터를 넘기면 엉뚱한 값을 읽어 힙이 깨질 수 있음. 3. 상수와 매크로 #define WSIZE 4 // 워드 = 헤더/푸터 크기 #define DSIZE 8 // 더블워드 = 정렬 단위 #define CHUNKSIZE (1<<12) // 힙 확장 기본 단위 (4096B) #define MAX(x, y) ((x) > (y) ? (x) : (y)) 값 만들기/읽기/쓰기 매크로 의미 PACK(size, alloc) 크기와 할당 비트를 OR해서 헤더 값을 만듦 GET(p) 주소 p 의 4바이트 읽기 PUT(p, val) 주소 p 에 4바이트 쓰기 GET_SIZE(p) GET(p) & ~0x7 → 하위 3비트를 지워서 크기만 가져옴 GET_ALLOC(p) GET(p) & 0x1 → 마지막 한 비트만 가져옴 GET / PUT 은 p 가 보통 void * 라서 역참조가 안 됨. 그래서 unsigned int * (4바이트)로 캐스팅해서 읽고 씀. 헤더/푸터가 1워드라서 4바이트 단위임. PACK 은 하위 3비트가 비어 있어서 OR만 해도 정보가 섞이지 않음. PACK(24, 1) = 0x19 , 에필로그는 PACK(0, 1) 임. 헤더 값이 33이면 이렇게 풀림. 33 = 0010 0001 GET_SIZE → 32 (하위 3비트를 지움) GET_ALLOC → 1 (사용 중) bp 로 주소 찾기 bp-4 bp bp+size-8 bp+size ↓ ↓ ↓ ↓ [ 헤더 ][ payload ................ ][ 푸터 ][ 다음 블록 헤더 ] HDRP payload 바로 앞이 헤더임. HDRP(bp) = bp - 4 FTRP 푸터 위치를 구하려면 블록 전체 크기를 알아야 해서 헤더를 읽음. FTRP(bp) = bp + GET_SIZE(HDRP(bp)) - DSIZE 숫자 예시로 보면 이렇게 됨. 주소: 100 104 120 124 [ H ][ ........ payload 16B ........ ][ F ] ↑ bp 블록 크기 24 → bp + size = 104 + 24 = 128 (다음 블록의 bp) 128 - 8 = 120 → 푸터 시작 bp + size 는 다음 블록의 bp 위치임. 여기서 8을 빼면 현재 블록의 푸터 위치가 나옴. bp가 현재 블록의 payload 시작점이라서, 블록의 시작점까지 4B, 푸터까지 4B를 빼는 것임. NEXT_BLKP 블록 전체 크기만큼 이동하면 다음 블록의 bp가 됨. bp-4 bp bp+size-4 bp+size ↓ ↓ ↓ ↓ [ 헤더 ][ payload ......... ][ 푸터 ][ 다음 헤더 ][ 다음 payload ... ↑ NEXT_BLKP(bp) = bp + size NEXT_BLKP(bp) = bp + GET_SIZE(bp - WSIZE) 헤더의 크기를 따라가는 이 동작이 묵시적 리스트 순회 의 핵심! PREV_BLKP 현재 블록의 바로 앞에는 이전 블록의 푸터가 있음. 이전 블록 현재 블록 ┌────────────────────────┐ ┌────────────────────┐ │ H │ payload │ F(20) │ │ H │ payload │ F │ └────────────────────────┘ └────────────────────┘ ↑ ↑ ↑ bp-8 bp-4 bp (이전 푸터) (현재 헤더) PREV_BLKP(bp) = bp - GET_SIZE(bp - DSIZE) 이전 블록의 푸터( bp-8 )에서 크기를 읽고, 그만큼 bp 에서 뒤로 이동하면 이전 블록의 bp 를 찾을 수 있음. 푸터가 없으면 힙을 처음부터 훑어야 함 코드를 읽을 때 주의할 점: 갱신 순서 FTRP(bp) 는 헤더에 적힌 크기 로 푸터 위치를 계산함. 그래서 place 와 coalesce 에서는 아래 순서가 중요함. 헤더를 새 크기로 먼저 바꾼 뒤 FTRP 를 부르면 → 새 크기 기준 위치 바꾸기 전에 부르면 → 옛 크기 기준 위치 순서가 틀리면 엉뚱한 주소를 덮어쓰게 됨. 4. mm_init : 초기 힙 구성 allocator가 처음 시작할 때 힙을 처음 만들어줌. mem_sbrk(4 * WSIZE) 로 16바이트 를 확보하고 이렇게 채움. 패딩(4B) - 프롤로그(헤더 4B, 푸터 4B) - 에필로그(헤더 4B) → 총 16B 오프셋: 0 4 8 12 [ 0 ][ 8/1 ] [ 8/1 ] [ 0/1 ] ↑ heap_listp 과정: 패딩 만들기 : 정렬을 위한 값 0을 씀. 프롤로그 헤더 생성 (크기 8, allocated) 프롤로그 푸터 생성 에필로그 헤더 생성 (크기 0, allocated) heap_listp 가 프롤로그 블록의 bp 를 가리키도록 +2*WSIZE 이동함. 이후 힙 순회의 출발점임. extend_heap 을 호출해서 첫 free block을 만듦. 패딩 4B + 프롤로그 헤더 4B = 8B라서 이후 블록들의 payload가 8의 배수 주소에 놓임. 5. extend_heap : 힙을 늘리고 free block 만들기 호출되는 경우 처음 allocator를 초기화할 때 ( mm_init ) malloc 할 공간이 없을 때 ( mm_malloc ) 코드 흐름 크기 맞추기 : words 가 홀수면 하나 올려서 짝수 워드(8의 배수 바이트) 로 만듦. 8바이트 정렬을 유지하려는 것임. mem_sbrk(size) : 힙의 끝을 size 만큼 늘리고, 새로 확보한 영역의 시작 주소 를 bp 로 받음. 새 블록의 헤더, 푸터 를 PACK(size, 0) 으로 씀. 새 블록 바로 뒤에 새 에필로그 를 만듦. coalesce(bp) 로 앞 블록이 비어 있으면 합침. 핵심: 왜 HDRP(bp) 가 맞나 확장 전: [ ... ][ 에필로그 ] 확장 후: [ ... ][ 에필로그 ][ 새 공간 ........ ] ↑ ↑ bp-4 bp (mem_sbrk가 반환한 옛 brk) mem_sbrk 가 반환한 bp 는 기존 에필로그 바로 뒤 임. 그래서 bp - 4 가 기존 에필로그 헤더 자리 이고, 거기에 새 free block의 헤더를 덮어씀. 기존 에필로그가 새 블록의 헤더가 되는 것임. 새로 확보한 영역의 맨 마지막 워드에 새 에필로그를 만듦. 확장 후 정리: [ ... ][ 헤더 | ...... free ...... | 푸터 ][ 새 에필로그 ] ↑ ↑ 옛 에필로그 자리 HDRP(NEXT_BLKP(bp)) 새 에필로그 위치는 이렇게 구함. PUT(HDRP(NEXT_BLKP(bp)), PACK(0, 1)); 방금 쓴 헤더의 크기를 이용해서 다음 블록의 bp 를 구하고, 그 헤더 자리가 새 에필로그가 됨. ex) 초기 힙이 [패딩(0~3)][프롤로그(4~11)][에필로그(12~15)] 일 때 extend_heap(1024) 를 호출하면: size = 4096 , mem_sbrk(4096) 이 16 을 반환하고 bp = 16 임. HDRP(bp) = 12 에 4096/0 을 씀 (옛 에필로그 자리). FTRP(bp) = 16 + 4096 - 8 = 4104 에 푸터를 씀. HDRP(NEXT_BLKP(bp)) = 4108 에 새 에필로그 0/1 을 씀. 왜 마지막에 coalesce 를 부르나 힙이 늘기 전에 마지막 블록이 free였다면 새 free block과 연속된 free 블록 이 생김. 확장 전: [P][A a][B f][E] 확장 후: [P][A a][B f][새로운 f][E] ← free 두 개가 붙음 coalesce: [P][A a][ B + 새 f ][E] ← 하나로 합침 초기화 때는 앞이 프롤로그(allocated)라서 합칠 게 없음. 6. mm_malloc : 블록 할당 흐름은 두 질문으로 요약됨. 몇 바이트 블록이 필요한가? 그 크기를 담는 빈 블록이 있는가? ex) malloc(20) │ ▼ asize 계산 │ ▼ find_fit() ↙ ↘ 찾음 못 찾음 │ │ ▼ ▼ place() extend_heap() │ │ ▼ ▼ bp 반환 place() │ ▼ bp 반환 (1) size와 asize size : 사용자가 요청한 데이터 크기 asize : allocator가 실제로 만들 블록 전체 크기 규칙) size 가 8 이하면 asize = 16 (최소 블록 크기) 그 외에는 size + 8 (헤더+푸터)을 8의 배수로 올림 요청 size 계산 asize 1 ~ 8 최소 블록 16 9 9+8=17 → 올림 24 20 20+8=28 → 올림 32 size == 0 이면 NULL 을 반환함. (2) find_fit : 맞는 빈 블록 찾기 asize 이상의 free block이 있는지 heap_listp 에서 시작해 힙을 훑음. 못 찾으면 NULL 을 반환하고, 그러면 힙을 확장함. 탐색 종료 조건 -> 에필로그(크기 0) 만나는 것. 배치 정책 (9.9.7) 정책 방식 장점 단점 first fit 처음부터 훑어 처음 맞는 블록 큰 블록이 뒤쪽에 남음 앞쪽에 작은 조각(splinter)이 쌓여 큰 요청의 탐색이 느려짐 next fit 직전 탐색이 끝난 곳부터 앞쪽에 조각이 많아도 빠를 수 있음 일부 연구에서 first fit보다 이용도가 나쁨 best fit 전부 훑어 가장 작게 맞는 블록 이용도가 대체로 가장 좋음 단순 리스트에서는 힙 전체 를 훑어야 함 이 구현은 first fit임. first fit은 의도해서 큰 블록을 뒤에 남기는 게 아니라, 앞쪽부터 쓰고 쪼개다 보니 생기는 부수 효과임. best fit의 단점은 나중에 나오는 분리 빈 리스트(9.9.14) 로 해결할 수 있음. 크기 클래스별로 리스트를 나눠서 전체를 훑지 않고도 best fit에 가까운 결과를 얻음. (3) place : 배치와 분할 찾은 블록이 필요한 것보다 클 때 선택지 1. 현재 블록 크기 - asize >= 최소 블록 크기(16) → 앞부분은 할당, 뒷부분은 새 free 블록으로 남김 (분할) 2. 아니면 → 통째로 사용 (남는 부분이 너무 작아서 블록을 못 만듦) 분할: [ 4048 free ] → [ 32 할당 ][ 4016 free ] 통째로 사용 : 단순하고 빠르지만 내부 단편화 가 생김. 분할 : 공간을 아끼지만 헤더/푸터를 하나 더 만들어야 함. 분할 기준이 16인 이유는 최소 블록 크기가 16이라 그보다 작은 나머지는 블록이 될 수 없기 때문임. (4) 못 찾으면 힙 확장 (9.9.9) extendsize = MAX(asize, CHUNKSIZE); 작은 요청마다 조금씩 sbrk 하면 비효율적이라 기본 4096바이트 씩 한 번에 늘림. 요청이 4096보다 크면(예: asize = 5008 ) 필요한 만큼 늘림. 책 본문은 "먼저 인접 빈 블록 연결을 시도하고, 안 되면 sbrk "라고 설명함. 이 구현은 free 마다 즉시 연결하므로 이미 최대한 합쳐져 있어서 바로 extend_heap 으로 감. 늘린 뒤에는 새 free block에 place 하고 bp 를 반환함. 이 흐름에서 place 는 각 경로에서 한 번씩만 호출됨. 7. mm_free 와 coalesce mm_free 블록의 alloc 비트를 1 → 0 으로 바꿈 (헤더와 푸터 둘 다) coalesce 를 호출해서 free 블록이 연속으로 붙어 있지 않게 해줌 연결 시점: 즉시 vs 지연 (9.9.10) 연결을 안 하면 거짓 단편화 가 생김. 실제로는 붙어 있는 빈 블록인데 별개 블록으로 취급해서 큰 공간으로 못 쓰게 됨. [3워드 빈 블록][3워드 빈 블록] → 각각 3워드로 관리 → 4워드 요청 시 사용 불가 합치면 [ 6워드 빈 블록 ] → 4워드 요청 가능 즉시 연결 지연 연결 시점 free 시점에 바로 병합 free 는 상태만 바꾸고, 나중에 malloc 이 빈 블록을 못 찾을 때 병합 장점 구현이 단순, 외부 단편화를 빠르게 줄임 불필요한 병합/분할을 줄임, free 가 빠름 단점 할당/해제를 반복하면 스래싱 (합쳤다가 바로 다시 쪼갬) 병합이 필요한 시점에 추가 작업 필요 교재의 코드 구현은 즉시 연결 을 사용 coalesce : 이웃의 상태로 네 가지 경우 prev_alloc = GET_ALLOC(FTRP(PREV_BLKP(bp))); // 이전 블록: 푸터로 확인 next_alloc = GET_ALLOC(HDRP(NEXT_BLKP(bp))); // 다음 블록: 헤더로 확인 Case [이전][현재][다음] 결과 1 [ a ] [ f ] [ a ] 합칠 것 없음 (현재만 free 표시) 2 [ a ] [ f ] [ f ] 현재 + 다음 합침 3 [ f ] [ f ] [ a ] 이전 + 현재 합침, bp 를 이전 블록으로 이동 4 [ f ] [ f ] [ f ] 셋 다 합침, bp 를 이전 블록으로 이동 합칠 때의 규칙: 합쳐진 덩어리의 맨 앞 헤더 와 맨 뒤 푸터 만 새 크기로 고침. 중간에 끼어 있던 옛 헤더/푸터는 지우지 않아도 되고, 그냥 내부 데이터로 남아서 무시됨. Case 4: [H 64][...][F 64] [H 32][...][F 32] [H 48][...][F 48] ↓ [H 144][........................................][F 144] ↑ 이전 블록의 헤더 다음 블록의 푸터 ↑ Case 합쳐진 블록의 헤더 합쳐진 블록의 푸터 2 현재 헤더 다음 블록의 푸터 3 이전 블록의 헤더 현재 푸터 4 이전 블록의 헤더 다음 블록의 푸터 Case 3, 4는 합쳐진 블록의 시작이 이전 블록으로 옮겨지므로
『실무로 통하는 타입스크립트』 Boolean 생성자로 null/undefined 제거하기 배열에서 null 종류의 값을 거르는 Boolean 생성자를 사용하려는데, TypeScript는 여전히 null 과 undefined 를 포함한 같은 타입을 반환한다. const array = [1, 2, 3, undefined, 4, null]; const filtered = array.filter((val) => !!val); 직접 확인한 결과, filtered 의 타입은 여전히 (number | null | undefined)[] 다. 런타임에서는 실제로 걸러졌어도, 타입 시스템은 필터 콜백의 로직까지 분석하지 못해서 원본과 같은 타입을 그대로 돌려준다. 해결 : 선언 합치기로 Array 의 filter 메서드를 오버로드한다. declare global { interface Array<T> { filter(predicate: BooleanConstructor): NonNullable<T>[]; } interface ReadonlyArray<T> { filter(predicate: BooleanConstructor): NonNullable<T>[]; } } // BooleanConstructor는 Boolean 생성자 자체의 타입이다 (typeof Boolean). // 즉 predicate 자리에 Boolean을 "그대로" 넘긴 특정 호출만 이 오버로드가 잡아낸다. const filtered2 = array.filter(Boolean); 직접 검증한 결과, filtered2 는 정확히 number[] 로 좁혀졌다. falsy 값까지 제거하는 Truthy BooleanConstructor 는 falsy한 값( "" , false , 0 , 0n , null , undefined )을 전부 거른다. 그런데 NonNullable<T> 는 null / undefined 만 제거할 뿐, 나머지 falsy 값( 0 , false , "" )은 타입에 그대로 남는다. type Truthy<T> = T extends "" | false | 0 | 0n ? never : T; declare global { interface Array<T> { filter(predicate: BooleanConstructor): Truthy<NonNullable<T>>[]; } interface ReadonlyArray<T> { filter(predicate: BooleanConstructor): Truthy<NonNullable<T>>[]; } } const nullOrOne: Array<0 | 1> = [0, 1, 0, 1]; const onlyOnes = nullOrOne.filter(Boolean); // onlyOnes: 1[] 직접 확인한 결과, 0 | 1 에서 0 이 제거되고 1[] 만 남았다. 라이브러리 타입 정의 패치하기 — 모듈 선언 합치기 Preact나 React 같은 라이브러리를 사용 중인데, 타입 정의가 최신 기능을 포함하지 않을 때가 있어 패치하고 싶다. 해결 : 모듈과 인터페이스 수준에서 선언 합치기를 한다. @types 폴더를 만들고 jsx.d.ts 파일을 추가한다. compilerOptions.types 에 해당 경로를 설정한다. 원하는 정의를 추가한다. import "react"; declare module "react" { interface ImgHTMLAttributes<T> extends HTMLAttributes<T> { loading?: "lazy" | "eager" | "auto"; alt: string; } } declare / module / namespace의 차이 키워드 역할 declare "이 코드는 어딘가에 실제로 존재하지만, 그 구현은 여기 없다"는 선언. 타입 정보만 컴파일러에 알려준다 declare module "이름" 특정 모듈 (파일 또는 패키지)의 타입을 선언하거나 확장한다 declare namespace 전역에 네임스페이스 (이름 묶음)를 선언한다. 레거시 JS 라이브러리(jQuery 등)처럼 전역 변수 하나에 여러 기능을 매달아 쓰던 패턴을 타이핑할 때 주로 쓴다 declare global 모듈 안에서 전역 스코프 를 확장한다 그 밖에 declare const (변수), declare function (함수), declare class (클래스)도 있다. 이들을 묶어 주변 선언(ambient declaration) 이라 부른다. 아직 지원되지 않는 브라우저 기능 타이핑하기 ResizeObserver 같은 브라우저 기능을 썼는데, 현재 TypeScript 설정에서 해당 기능을 사용할 수 없다. 해결 : 전역 네임스페이스에 사용자 정의 타입 정의를 추가한다. lib.dom.d.ts 업데이트를 기다릴 수 없다면 직접 타입을 추가할 수 있다. // resize-observer.d.ts declare var ResizeObserver: { prototype: ResizeObserver; new (callback: ResizeObserverCallback): ResizeObserver; }; 어디에서나 사용하려면 declare global 로 감싼다. declare global { var ResizeObserver: { prototype: ResizeObserver; new (callback: ResizeObserverCallback): ResizeObserver; }; } resize-observer.d.ts 를 @types 폴더에 넣고 typeRoots , include 에 경로를 설정한다. 존재하지 않을 수도 있다는 걸 타입으로 표현하기 대상 브라우저에서 ResizeObserver 를 아직 사용할 수 없을 가능성이 있다면, 정의되지 않은 상태( undefined )일 수 있다는 걸 타입에 포함시킨다. declare global { var ResizeObserver: | { prototype: ResizeObserver; new (callback: ResizeObserverCallback): ResizeObserver; } | undefined; } 타입에 | undefined 를 추가하면, "이 전역 객체는 없을 수도 있다"는 정보가 컴파일러에 전달된다. 그래서 체크 없이 바로 쓰면 에러가 나고, 런타임에 실제로 존재하는지 확인한 뒤에만 쓰도록 강제된다. const x = ResizeObserver.prototype; // TS18048: 'ResizeObserver' is possibly 'undefined' if (typeof ResizeObserver !== "undefined") { const x = new ResizeObserver((entries) => {}); // 통과 } 직접 검증한 결과, 체크 없는 접근은 정확히 이 에러로 막히고, typeof 체크를 거치면 정상적으로 좁혀져 통과한다. "객체를 사용할 수 있는지 확인할 수 있다"는 말은 이 런타임 존재 여부 검사를 타입 시스템이 강제한다 는 뜻이다. 엠비언트 선언 파일을 TypeScript가 인식하지 못할 때 확인할 것 include 속성에 @types 폴더 경로가 포함되어 있는지 확인한다. 여기 포함돼 있어야 컴파일러가 그 폴더의 .d.ts 파일들을 읽어 타입 검사 대상으로 삼는다 ( "파싱한다" 는 이 과정, 즉 폴더 안의 선언 파일들을 읽어 들여 분석한다는 뜻이다). 엠비언트 파일을 types 나 typeRoots 에 추가한다. 엠비언트 파일 끝에 export {} 를 추가하면 TypeScript가 그 파일을 모듈로 인식한다. 직접 확인한 결과, export {} 가 없으면 declare global 자체가 TS2669: Augmentations for the global scope can only be directly nested in external modules or ambient module declarations 에러로 막힌다. declare global 은 모듈 안에서만 쓸 수 있는 문법이기 때문이다. 번들러로 불러온 비-JS 파일 타이핑하기 (.css, 이미지 등) 웹팩 같은 번들러로 JavaScript에서 .css 나 이미지 같은 파일을 불러왔는데, TypeScript는 이러한 파일을 인식하지 못한다. 해결 : 파일명 확장자에 기반해 모듈을 전역으로 선언한다. TypeScript는 환경에 있거나 도구를 통해 접근할 수 있는 모듈에도 엠비언트 모듈 선언 을 지원한다 ( url , http , path 같은 Node 주요 내장 모듈 등). 와일드카드 패턴에도 사용할 수 있다. declare module "*.css" { interface IClassNames { [className: string]: string; } const classNames: IClassNames; export default classNames; } 임포트하려는 모든 .css 파일을 이 선언이 인식한다. 다만 자동완성에서 정확한 클래스 이름까지는 알 수 없다. NPM 패키지를 추가로 설치하면 타입 파일을 자동으로 생성해 해결할 수 있다. 이 밖에 *.mdx 를 선언하면 JSX 파일에서 컴포넌트로 사용할 수 있다. 앰비언트 모듈을 사용하려면 @types 폴더를 만드는 게 좋다. 이 폴더에 모듈 정의가 포함된 .d.ts 파일을 얼마든지 추가할 수 있다. typeRoots 옵션으로 @types 경로를 설정한다.
Corporate Social Responsibility (CSR) has become an important part of India's social development framework. Companies that undertake eligible CSR activities often work with non-governmental organizations (NGOs) to implement projects that address education, healthcare, poverty, environmental sustainability, livelihood development, and other community needs. A CSR-1 registered NGO in India is an organization that has completed the applicable CSR registration process with the Ministry of Corporate Affairs (MCA), enabling it to undertake eligible CSR implementation activities in accordance with the applicable regulatory framework. For companies seeking reliable implementation partners, working with a properly registered organization can provide a structured approach to delivering CSR initiatives. We believe effective CSR is not simply about providing financial support. It is about converting resources into measurable social outcomes. Through responsible planning, transparent implementation, community participation, and appropriate reporting, NGOs can help organizations create sustainable and meaningful impact. What Is CSR-1 Registration for an NGO? CSR-1 registration refers to the registration process through which eligible entities seeking to undertake CSR activities on behalf of companies are required to furnish relevant information to the MCA. The registration is completed through the prescribed CSR-1 form and results in the organization receiving a unique CSR Registration Number after successful submission and processing. For companies, this framework provides a more structured way to identify organizations involved in CSR implementation. For NGOs, CSR registration can form an important part of establishing their eligibility and readiness to collaborate on qualifying CSR projects. However, CSR-1 registration should not be viewed in isolation. Companies and NGOs should also consider the organization's legal status, experience, financial transparency, project capabilities, governance practices, and compliance requirements. Why Companies Work With CSR-1 Registered NGOs Companies often require experienced implementation partners to transform CSR budgets into projects that reach the intended beneficiaries. An NGO can provide the local knowledge, operational infrastructure, professional expertise, and community relationships required for effective implementation. We focus on several important areas when developing CSR initiatives: Community healthcare and medical support Education and skill development Support for economically disadvantaged communities Women and child welfare Livelihood and vocational development Rural and community development Environmental sustainability Awareness and preventive healthcare Support for vulnerable populations An experienced NGO can help companies move from funding decisions to practical implementation by identifying beneficiaries, designing projects, coordinating field activities, maintaining records, and preparing appropriate reports. CSR and Healthcare: Creating Direct Community Impact Healthcare is one of the areas where CSR initiatives can create direct and measurable benefits for communities. Many economically disadvantaged families face challenges in accessing timely diagnosis, treatment, surgery, medicines, and specialist medical care. NGOs working in healthcare can help bridge these gaps by organizing medical services and connecting vulnerable beneficiaries with appropriate treatment. CSR-funded healthcare programs may support activities such as: Medical camps Eye examinations Cataract screening and treatment General healthcare services Preventive healthcare awareness Diagnostic assistance Surgical support for eligible beneficiaries Medicines and essential healthcare assistance Health education programs When such initiatives are properly planned and documented, CSR resources can reach communities that require additional support. The Role of Transparency in CSR Implementation Transparency is a critical component of responsible CSR implementation. Companies need confidence that their CSR resources are being used for the intended purpose and that project activities can be appropriately documented. We recognize the importance of maintaining proper documentation throughout a project. This may include beneficiary records, project reports, photographs where appropriate, expenditure records, activity updates, and other supporting documentation. Transparent processes can help organizations: Establish clear project objectives. Define measurable activities. Identify intended beneficiaries. Track implementation progress. Maintain appropriate financial records. Document project outcomes. Prepare reports for relevant stakeholders. The exact documentation and reporting requirements can vary depending on the project, organization, applicable law, and company requirements. How a CSR-1 Registered NGO Can Support Companies A CSR implementation partner can contribute at several stages of a CSR project. Project Identification We can identify community needs and develop projects around clearly defined social objectives. Effective project identification begins with understanding the actual challenges faced by beneficiaries. Project Planning A structured project plan can define the target population, geographical area, activities, implementation timeline, resources, and expected outcomes. Community Outreach NGOs often have direct relationships with communities. These connections can help identify eligible beneficiaries and improve participation in social development programs. Project Implementation Implementation involves putting the approved CSR plan into action. Depending on the project, this can include healthcare services, educational activities, training programs, awareness campaigns, or community development initiatives. Monitoring and Documentation Regular monitoring helps determine whether activities are progressing according to plan. Documentation also provides companies with information about project implementation and outcomes. Impact Reporting At the completion or review stage, project information can be consolidated into reports describing activities, beneficiaries reached, expenditure, achievements, and other relevant indicators. Choosing a CSR-1 Registered NGO in India Companies should conduct appropriate due diligence before selecting an NGO as a CSR implementation partner. CSR-1 registration is one consideration, but it should not be the only factor. We recommend reviewing several aspects of an organization before beginning a CSR partnership. Legal and Regulatory Compliance Companies should verify relevant registration and compliance documents and ensure that the organization meets applicable CSR requirements. Experience An NGO's previous experience can provide insight into its ability to manage projects, work with communities, coordinate resources, and deliver planned activities. Project Expertise The NGO should have appropriate expertise in the proposed CSR activity. For example, a healthcare project may require an organization with established healthcare infrastructure and qualified professionals. Financial Transparency Clear financial processes and appropriate documentation are essential for responsible management of CSR resources. Geographical Reach Companies should consider whether the organization has an established presence in the communities or regions where the CSR project will be implemented. Monitoring Capabilities A strong monitoring system allows the organization and corporate partner to track project progress and identify implementation challenges. CSR-1 Registration and Corporate Partnerships Corporate-NGO partnerships can create opportunities for organizations to combine financial resources with community-level implementation capabilities. A company may identify a specific social issue and seek an NGO capable of implementing a project in that area. The NGO can then contribute its field experience, operational systems, staff, community relationships, and project-management capabilities. For a successful partnership, both parties should establish clear expectations regarding: Project objectives Target beneficiaries Implementation location Project duration Budget allocation Roles and responsibilities Monitoring mechanisms Reporting requirements Expected outcomes Compliance obligations A clearly defined partnership can reduce confusion and improve coordination throughout the project lifecycle. Creating Measurable Social Impact CSR should ideally focus on outcomes rather than only activities. Conducting a medical camp, for example, is an activity. Understanding how many people were screened, how many required follow-up treatment, and how many ultimately received appropriate care provides additional information about the project's impact. Similarly, an education program can measure more than the number of sessions conducted. It may also consider participation, completion, skills acquired, or other appropriate indicators. Measurable CSR projects allow companies and implementation partners to better understand what their resources are achieving. Building Long-Term Community Development Programs Short-term assistance can address immediate needs, while long-term CSR programs can contribute to broader community development. We believe sustainable initiatives should consider the underlying needs of beneficiaries and establish practical mechanisms for continued support wherever appropriate. For example, a healthcare initiative can combine treatment with awareness and preventive healthcare education. A livelihood project can combine vocational training with employment or entrepreneurship support. An education program can incorporate learning resources, mentoring, and skill development. The appropriate approach depends on the community, project objectives, available resources, and applicable CSR requirements. CSR-1 Registered NGO for Social Development in India India's social de
Introduction The online entertainment industry has experienced tremendous growth over the past decade. Users no longer want simple websites that only offer content to watch; they seek engaging experiences that combine entertainment, interaction, and convenience. This shift in user expectations has led to the rise of platforms that focus on community-building and user engagement. Among these platforms, Filipina Magic has gained attention as a destination that offers a modern and enjoyable online experience. With its intuitive design, engaging features, and commitment to user satisfaction, Filipina Magic continues to attract visitors from around the world. Whether users are looking for interactive entertainment or simply want to explore a dynamic online platform, Filipina Magic delivers a seamless experience that keeps visitors coming back. A User-Centered Entertainment Experience One of the biggest strengths of Filipina Magic is its focus on the user experience. Every aspect of the platform is designed to make navigation easy and enjoyable. Visitors can browse content effortlessly, discover new creators, and access various features without unnecessary complications. Unlike platforms that overwhelm users with cluttered layouts, Filipina Magic emphasizes simplicity and accessibility. This user-centered approach creates a comfortable environment where visitors can focus on enjoying their experience. Interactive Features That Enhance Engagement Modern entertainment is increasingly interactive. Users want opportunities to connect, communicate, and participate rather than simply consume content. Filipina Magic embraces this trend by providing features that encourage engagement and interaction. Interactive experiences help create a stronger connection between users and content creators. This personal touch adds value to the overall entertainment experience and helps foster a vibrant online community. Accessibility Across Multiple Devices Today's users expect flexibility. Whether browsing on a desktop computer, tablet, or smartphone, they want the same high-quality experience. Filipina Magic delivers responsive functionality across all major devices, ensuring that visitors can enjoy the platform wherever they are. Mobile optimization has become essential in the digital age, and Filipina Magic recognizes the importance of providing seamless accessibility for users on the go. Continuous Improvement and Innovation The digital landscape evolves rapidly, and successful platforms must adapt to changing trends and user preferences. Filipina Magic demonstrates a commitment to innovation by regularly enhancing its features and overall user experience. By staying responsive to user feedback and technological advancements, the platform continues to improve and remain relevant in an increasingly competitive market. Conclusion Filipina Magic has established itself as a modern entertainment platform that prioritizes user engagement, accessibility, and innovation. Its focus on creating enjoyable and interactive experiences makes it an attractive destination for users seeking quality online entertainment. FAQs Q1: What is Filipina Magic? Filipina Magic is an online entertainment platform designed to provide engaging content and interactive user experiences. Q2: Is Filipina Magic mobile-friendly? Yes, the platform is optimized for smartphones, tablets, and desktop devices. Q3: What makes Filipina Magic different from other entertainment platforms? Its combination of user-friendly design, interactive features, and community engagement helps distinguish it from many competitors. Q4: Can new users easily navigate the platform? Yes, the platform is designed with simplicity and accessibility in mind. Q5: Does Filipina Magic regularly update its content? Yes, the platform continuously introduces updates and enhancements to improve the user experience. Filipina Model HD XXX Videos Hot Filipina Porn Videos watch free young XXX love https://www.filipinamagic.com/
Choosing the right salon is an important part of maintaining your personal style and appearance. For those searching for a luxury salon in Delhi, Luca Piattelli offers a sophisticated salon experience focused on professional hair styling, personalized attention, and contemporary beauty services. A Personalized Luxury Salon Experience A luxury salon experience goes beyond simply getting a haircut or styling service. It is about receiving personalized recommendations based on your hair type, preferences, lifestyle, and desired appearance. Professional consultation allows stylists to understand your expectations and create a look that complements your personality. At Luca Piattelli, clients can explore professional haircuts, styling, colouring, and other salon services in a refined and comfortable environment. Professional Hair Styling in Delhi Your hairstyle can influence your overall appearance and confidence. Whether you prefer a classic haircut, a modern transformation, or an elegant style for a special occasion, professional styling can help you achieve a polished finish. Experienced stylists consider factors such as face shape, hair texture, length, and personal preferences when creating a suitable hairstyle. This attention to detail helps ensure that the final look is both stylish and manageable. Hair Colour and Transformation A new hair colour can refresh your appearance and add a distinctive touch to your personal style. Professional colour consultation can help you understand different shades and techniques while considering your existing hair condition and desired result. Luca Piattelli provides an opportunity to discuss your colour preferences and explore options suited to your overall look. Styling for Special Occasions Whether you are attending a wedding, party, business event, or evening celebration, the right hairstyle can complete your outfit. Professional stylists can create sleek, elegant, voluminous, or contemporary looks based on the occasion. A personalized approach ensures that the hairstyle works with your outfit and complements your individual features. Why Choose a Luxury Salon in Delhi? When looking for a luxury salon in Delhi, clients often consider professional expertise, personalized service, salon ambience, product quality, and attention to detail. Selecting a salon that understands your expectations can make regular hair care and styling a more enjoyable experience. Luca Piattelli focuses on combining modern styling techniques with individualized attention, helping clients discover hair looks that suit their preferences and lifestyle. Discover Luca Piattelli A professional salon can help you maintain your everyday style while also providing expert assistance when you want a complete transformation. From haircuts and colouring to professional styling for special occasions, personalized salon services can help you feel confident in your appearance. If you are searching for a luxury salon in Delhi, consider Luca Piattelli for professional hair and beauty services. Discover a refined salon experience where contemporary styling, personalized attention, and professional care come together. Visit : https://share.google/wGcDlLNOqNdWHXJqH Contact :87662 65933
실제 비즈니스 이슈를 마주쳤더니 눈물이 질질.. 토스 러너스하이 3기 연사를 듣고 백엔드 엔지니어로써 실무에서 발생하는 여러 이슈들에 대해 관심이 생겼다. 하지만 무직백수 2학년 나. 하나도 이해하지 못했다. 연사에서 언급하신 문제들의 키워드조차 내게는 버거웠기에 그 개념과 실습부터 차근차근 공부하고자 한다. 토스 이직하는 상상 ON. (무직임) 먼저 Kafka란? 팬아웃을 제공해주는 서버 프로그램 이라고 나는 쉽게 쉽게 이해했다.. MQ 브로커 서버 프로그램을 제공함으로써 원활한 1:N 메세지 전송을 보장하는 프로그램인거 같다, 기본 작동 흐름은 다음과 같다. Kafka 기본 흐름 Topic: order │ ├─▶ [재고 그룹] 서버1, 서버2 ← 메시지를 둘이 나눠 가짐 (분배) ├─▶ [알림 그룹] 서버1, 서버2, 서버3 ← 마찬가지 └─▶ [배송 그룹] 서버1 ← ↑ 그룹 사이에서는 전부 같은 메시지를 받음 (팬아웃) -> Producer 1명이 order 토픽을 보냄 -> 각 그룹(재고, 알림, 배송)이 order 토픽의 같은 A메세지를 받음 -> 단 같은 그룹의 다른 서버는 로드밸런싱으로 메세지를 분배받음 토픽에 대해서 다른 그룹은 Fan out, 같은 그룹은 load balance 조금 더 구체적으로 * 이제 Partition, Offset 이라는 개념이 나온다. * Order 토픽에 대해서 Key에 따라서 파티션 N개로 나눠져 메세지를 적재한다. 그리고 파티션 1개 - 서버 1대의 매핑. (메세지의 순차성 보장 목적) Topic: order (이름표일 뿐, 실제 데이터는 아래 파티션에 있음) ├─ Partition 0: [0][1][2][3][4] ← 새 메시지는 오른쪽 끝에 append ├─ Partition 1: [0][1][2] └─ Partition 2: [0][1][2][3] 왜 이런 구조인가? (왜 파티션으로 토픽을 다시 쪼갠거지?) 순차성 보장이 필요한 요청들이 있기 때문이다. “한 서비스 안에서 처리량을 늘리면서 순서도 지키기 위한 것” Kafka가 순차성 문제를 해결하는 방식 순서를 지키려면 같은 주문의 이벤트(생성→결제→취소)는 한 줄(로그)에 쌓여야 한다. 한 줄은 한 명만 읽어야 순서대로 처리된다. (두 명이 읽으면 뒤 메시지를 먼저 끝낼 수 있음) 근데 줄이 하나뿐이면 처리량 한계 가 있다. 그래서 줄을 여러 개로 늘린다 = 파티션. 같은 key는 같은 줄로 → "주문 단위 순서는 유지, 주문들끼리는 병렬" 이 가능해진다. 예시로 하는 정리 order-A는 항상 같은 파티션0에 있고, 그 안에서 생성 → 결제 → 취소 순서가 유지된다. order-C처럼 다른 key가 order-A와 같은 파티션0에 섞일 수 있다. 이건 문제가 없다. 비는 파티션도 생길 수 있기거나 특정 파티션으로 쏠릴 수 있다. 참고: 그럼에도 순차성 이슈가 발생할 수 있다! 서버 안에서 멀티쓰레드 : 비동기 처리시에 생성보다 결제가 먼저 끝난다면? 좀비서버 : 서버A - 파티션0 매핑이었음. 근데 서버A가 죽었다고 판단해서 서버B - 파티션0로 재매핑된 상황. 그런데 서버A가 사실 살아있었다면?? 확실히 백엔드에서 어려운 문제 중 하나가 병렬처리인거 같다.. 이*원 교수님이 항상 수업에서 하시는 말씀이 병렬처리 해봤냐? 멀티 쓰레드 해봤냐? 이었는데.. "순차성 보장과 병렬 처리는 충돌하는데 이걸 어떻게 해결하는가?"가 Kafka의 핵심인듯. Kafka 관련 연사 내용 해석 1 Comsumer 속도를 높이려면? 상황: 메시지는 계속 쌓이는데 Comsumer가 느려서 lag(latest offset - commit)이 늘어나는 상황. 고려 사항 rebalance : Comsumer가 늘고 줄 때마다 멈춤·중복이 생긴다. 순서 보장 : 병렬로 돌리면 메시지 A, B의 처리 완료 순서가 뒤바뀔 수 있다. *"같은 key끼리는 순서대로, 다른 key끼리는 병렬로"** 설계한다. 장애 처리 : 병렬 작업 중 3번만 실패하고 4, 5번은 성공했다면 offset을 어디까지 commit할지 정해야 한다. 잘못하면 유실되거나 중복. 2 처리에 실패하면? 상황: 어떤 메시지가 데이터 오류 등으로 계속 실패하는 상황. 계속 재시도하면 그 뒤 메시지가 전부 막힘. DLQ (Dead Letter Queue) : 여러 번 재시도해도 실패한 메시지를 별도 토픽(예: order.DLQ )으로 치워두는 곳. 재발행 시스템 (retry / redrive) : 일시적 오류(네트워크 끊김 등)는 재시도하면 됨. 일정 간격을 두고 다시 시도하는 retry 토픽 ( retry-1분 , retry-10분 등) - DLQ가기 전 버그를 고친 뒤 DLQ의 메시지를 원래 토픽으로 다시 발행 하는 기능 3 브로커 성능을 개선하려면? 상황: 메시지를 한 건씩 보내면 네트워크 왕복이 너무 많아져 브로커가 느려지거나 다운되는 상황. Producer 설정으로 개선. batch.size : 메시지를 모아서 한 번에 보낼 묶음 크기 linger.ms : 묶음이 찰 때까지 최대 얼마나 기다릴지 (예: 5ms) compression.type : 압축 방식 ( lz4 , snappy , zstd 등). 네트워크·디스크 사용량이 줄어요. buffer.memory : 보내기 전 쌓아두는 메모리 버퍼 크기 트레이드오프는 처리량(throughput) ↑ 대신 지연(latency)이 약간 ↑ . 그래서 "최적화"(서비스 성격에 맞게 균형 잡기)가 필요함. 4 처리 속도를 높이려면? 처리 속도를 높이는 가장 직접적인 방법은 파티션을 늘려서 컨슈머를 더 붙이는 것. (병렬성 상한이 올라가니까). 그런데 병목이 있다.. auto.offset.reset : commit이 없을 때 어디서부터 읽을지 정하는 옵션 latest : 지금 이후 들어오는 메시지부터 읽음 earliest : 가장 처음부터 읽음 Ex) 파티션을 새로 늘리면 새 파티션에는 commit 기록이 없음. 이때 latest 면 새 파티션이 생기고 컨슈머가 인식하기 전까지 들어온 메시지를 건너뛰어 유실 됨. 그래서 earliest 로 설정해야 안전. 5 중복 처리를 막으려면? Kafka는 기본이 at-least-once라 같은 메시지가 두 번 올 수 있음. (리밸런스, 재시도 등) 멱등성(Idempotency): 같은 작업을 여러 번 해도 결과가 한 번 한 것과 같은 성질. 예를 들어 "잔액을 +100 한다"는 멱등하지 않지만, "주문 1234의 상태를 PAID로 설정한다"는 멱등하다. 이건 별도 스터디로 빼서 깊게 공부할 예정 SQL UPDATE문으로 멱등성 보장 SELECT ... FOR UPDATE (비관적 락) unique 제약 등이 있다. producer의 enable.idempotence 같은 기능을 Kafka 내부에서 지원하긴 하지만 이는 Kafka에서의 멱등성만 보장할 뿐, 외부 시스템 호출 (알람 발송, MSA api 호출 등)에서의 멱등성까지 보장하진 못하기에 추가 처리 로직이 필요하다고 한다. Kafka 멱등성 뚫림! (리밸런스 때문에) -> DB에 주문 생성1234 중복 INSERT 요청 -> DB에서 unique로 제약 검 -> 아 Kafka 뚫렸지만? 서비스는 멱등성 보장ᄒᄒ 이런 느낌~? 6 안정적으로 배포하려면? 상황: 서버를 배포하면 컨슈머가 하나씩 껐다 켜지고, 그때마다 리밸런스가 일어나 처리가 흔들림. assigner (partition assignment strategy): 리밸런스 때 파티션을 어떻게 재분배할지 정하는 전략입니다. *_기본 방식: 전부 회수했다가 다시 나누기. 전체가 멈춤. _ CooperativeStickyAssignor 는 기존 배정을 최대한 유지 하고 바뀌는 파티션만 멈추고 옮김. <CooperativeStickyAssignor> [리밸런스 전] 서버A: P0, P1 서버B: P2, P3 [1단계: 옮길 것만 반납] 서버A: P0, P1 ← 계속 처리 중 ▶️ 서버B: P2 ← 계속 처리 중 ▶️ (P3만 회수됨 ⏸️) [2단계: P3을 서버C에게] 서버A: P0, P1 서버B: P2 서버C: P3 -> 배포 시에 롤링 배포를 주로 하는데, 1개씩 서버 - 파티션을 리밸런싱해주니 lag가 덜 튄다! 7 모니터링·장애 대응은? config 점검: 설정이 서비스 특성에 맞는지 확인한다. lag 모니터링: lag은 "컨슈머가 얼마나 밀렸는가"로, Kafka 운영에서 가장 중요한 지표 후속 대책 수립: lag이 늘 때 어떻게 할지를 미리 정해두는 것. Ex) 컨슈머 증설, 파티션 증설, DLQ 확인, 장애 대응 절차서(runbook) 작성 등 <본인 정리본 원본> https://app.notion.com/p/Kafka-3ed0ea4df785800c8cdff0bbf702bfdc?source=copy_link <본인 정리본 원본 2> https://app.notion.com/p/Kafka-3ed0ea4df7858069a6caf5b33bc77a8f?source=copy_link 다음은 실습 다음 Kafka 스터디는 컨테이너 환경에서 Kafka를 열어서 개념 실습 및 실제 연사자분이 말하신 7가지 케이스에 대한 이슈를 경험해보고 토스의 해결방법을 실제 재현해 볼 것이다. 기대해라. 토*. 내 거름이 되어라.
예전에 학사정보 챗봇을 만들었다. 얼마 전 그 프로젝트를 다시 정리하다가, 내가 만든 챗봇이 요즘 말하는 "AI 챗봇"과는 꽤 다른 물건이었다는 걸 알았다. 그래서 이번 글에서는 그 챗봇을 돌아보고, 지금 다시 만든다면 어떤 방식으로 만드는지 를 파이썬 코드로 끝까지 정리해 본다. 결론부터 쓰면 이렇다. 알아듣고 말하는 부분은 LLM이 통째로 가져갔고, 내가 맡았던 데이터 쪽은 거의 그대로 남았다. 0. 회고 — 그때 만든 챗봇 학생이 "도서관 자리 있어?", "오늘 학식 뭐야?", "회로이론1은 뭘 배워?", "졸업요건 알려줘"라고 물으면 답해 주는 챗봇이었다. 네 명이 투입됐고, 챗봇 본체는 팀원이, 나는 챗봇이 읽는 데이터와 그 데이터를 꺼내는 조회 기능 세 개를 맡았다. 학생 질문 ↓ 1 알아듣기 ─ 의도 분류(Intent) + 엔티티 추출(Entity) 예문 358개(nlu.yml)로 학습시킨 분류 모델(DIETClassifier) 한국어 조사는 형태소 분석기(KoNLPy의 Okt)로 떼어 냄 ↓ 2 되묻기 ─ 슬롯(Slot)·폼(Form) 졸업요건이면 학과와 입학년도가 다 찰 때까지 되묻는다 ↓ 3 조회해서 답하기 ─ 커스텀 액션(Custom Action) 9개 ★ 그중 도서관 좌석·학식·과목 설명 3개가 내 것 ↑ DB ─ ★ 웹·PDF에서 모아 정제해 넣은 데이터 전부 개발 땐 구글 시트, 검증계부터 Oracle 도서관 좌석은 5분 배치로 갱신, 통계용 이력은 1년 보관 12는 Rasa 3.1로 팀원이 만들었고, 3의 일부와 DB가 내 자리였다. 학습 예문에 들어간 과목명 56개와 교수명 115명도 내가 모은 데이터에서 뽑았다. 정리하다가 깨달은 게 있다. 이 챗봇에는 LLM이 없었다. Claude나 ChatGPT 같은 거대 언어 모델은 하나도 안 들어갔다. 미리 정해 둔 의도 중 하나로 문장을 분류하고, 정해 둔 흐름대로 되묻고, 정해 둔 문장 틀(응답 템플릿, responses)에 값을 끼워 답하는 구조였다. 문서를 찾아서 답하는 RAG도 아니었다. 그때는 이게 챗봇을 만드는 표준적인 방법이었다. 그럼 요즘은 뭐로 만들까. 1. 요즘의 기본 — LLM + 도구 호출 + RAG 한 문장으로 말을 알아듣고 답을 쓰는 건 LLM에게 맡기고, LLM이 모르는 우리 학교 데이터는 "도구 호출"과 "RAG" 두 통로로 쥐여 준다. LLM이 질문을 보고 필요한 도구를 스스로 골라 부르고, 결과를 보고 또 부르기도 하면서 답을 완성하는 이 구조를 요즘은 흔히 AI 에이전트(Agent)라고 부른다. 세 가지를 하나씩 LLM (거대 언어 모델, Large Language Model) 엄청난 양의 글로 학습이 이미 끝난 모델이다. 내가 학습시키지 않고 API로 빌려 쓴다. 한국어 조사, 오타, 줄임말, "다음 주 화요일"이 며칠인지까지 알아서 이해한다. 다만 동국대 도서관에 지금 몇 자리 남았는지는 모른다. 학습할 때 없던 정보이고, 5분마다 바뀌는 정보라서다. 그래서 우리 데이터를 쥐여 줄 통로가 두 개 필요하다. 도구 호출 (Tool Calling, Function Calling이라고도 한다) LLM에게 "이런 함수들이 있다"는 설명서를 같이 보낸다. LLM은 질문을 보고 " get_library_seats 를 room="IC Zone" 으로 불러 줘"라고 요청만 한다. 실제 실행은 내 서버가 하고, 결과를 LLM에게 돌려주면 LLM이 그걸 보고 답을 쓴다. 좌석, 학식, 연락처처럼 DB에 표로 있는 값, 계속 바뀌는 값 은 이 통로로 간다. RAG (검색 증강 생성, Retrieval-Augmented Generation) 오픈북 시험이다. 질문과 관련 있는 문서 조각을 먼저 찾아서(검색) LLM에게 같이 보여 주고, "이걸 보고 답해"라고 한다. 과목 해설, 학칙처럼 문서로 된 지식 은 이 통로로 간다. 요즘은 이 검색 자체를 도구 하나로 만들어서 LLM이 필요할 때 부르게 하는 경우가 많고, 이 글도 그렇게 한다. 웹 개발 말로 바꾸면 요즘 챗봇 웹으로 치면 도구 (Tool) 백엔드 API 하나 도구 설명 + 입력 스키마 (description, input_schema) Swagger(OpenAPI) 문서 LLM 그 문서를 읽고 API를 골라 부른 뒤, 응답을 보고 화면 문구를 쓰는 클라이언트 에이전트 루프 (Agent Loop) 클라이언트가 API를 몇 번 부르며 화면을 완성하는 흐름 RAG 검색 API. 단, 글자가 아니라 뜻으로 찾는다 그때 나는 API(커스텀 액션)를 만들었고, 그 API를 부르는 쪽이 Rasa에서 LLM으로 바뀐 셈이다. 왜 이렇게 바뀌었나 그때 (Rasa) 지금 (LLM + 도구) 알아듣게 하려면 의도마다 예문을 써서 학습 (358개) 도구 설명 몇 줄 한국어 조사 형태소 분석기(Okt)를 붙였다 신경 쓸 필요 없다 처음 보는 표현 엉뚱한 의도로 가거나 "이해하지 못했어요" 대부분 알아듣는다 "자리랑 학식 둘 다 알려줘" 의도는 하나만 고른다 도구 두 개를 한 번에 부른다 "다음 주 화요일 학식" 날짜 파싱 코드를 직접 짰다 LLM이 계산한다 (오늘 날짜만 알려 주면) 답 문장 템플릿에 값 끼우기 상황에 맞게 LLM이 쓴다 기능 추가 예문 쓰고 다시 학습 도구 하나 추가 약점 정해진 것만 한다 그럴듯하게 지어낼 수 있다(환각, Hallucination). 호출마다 돈과 시간이 든다 마지막 줄이 중요하다. Rasa는 모르면 "모르겠다"고 하는데, LLM은 모르면 그럴듯하게 지어낼 수 있다. 받은 값이 틀려도 의심 없이 아주 자연스럽게 말한다. 그래서 요즘 방식에서 제일 공들이는 게 "LLM이 도구에서 받은 사실로만 답하게 만드는 것"이고, 그 사실을 만드는 게 데이터 쪽 일이다. 그럼 우리 학교 데이터로 모델을 학습시키나? 보통은 아니다. 좌석처럼 5분마다 바뀌는 값은 학습으로 넣을 수가 없고, 학습(파인튜닝, Fine-tuning)은 비싸고 느리다. 대부분 도구와 RAG로 먼저 만들고, 말투나 형식을 꼭 맞춰야 할 때 파인튜닝을 검토한다. Rasa는 사라졌나 아니다. Rasa도 LLM 쪽으로 왔다. 지금 Rasa의 대화 엔진인 CALM(Conversational AI with Language Models)은 LLM이 대화를 이해해서 미리 정의한 업무 흐름(Flow) 중 어디로 갈지 고르고, 흐름 안의 단계와 업무 규칙은 사람이 정해 둔 대로 돈다. 답변도 기본은 정해 둔 문장이다. 은행처럼 정해진 절차를 반드시 지켜야 하는 곳이면 이쪽이 맞을 수 있다. 학사정보처럼 "조회해서 알려 주기"가 대부분인 챗봇은 이 글의 방식이 더 단순하다. 대응표 — 그때 만든 것은 지금 어디로 가나 그때 (Rasa) 지금 누가 의도 분류 (Intent) LLM이 어떤 도구를 부를지 고른다 LLM 엔티티 추출 (Entity) 도구 인자 (tool input) LLM 학습 예문 ( nlu.yml ) 도구 설명 (description) + 입력 스키마 (input_schema) 개발자 분류 모델 (DIETClassifier) LLM 그 자체 — 형태소 분석기 (Okt) 필요 없음. 단, 검색용 정규화는 여전히 필요 — 슬롯 (Slot) 대화 기록 (messages) 서버가 보관 폼 (Form) 도구의 필수 인자 + "모르면 먼저 물어봐라" 규칙 개발자 설계 + LLM 정책 (Policy) 에이전트 루프. 다음 행동은 LLM이 고른다 LLM + 루프 코드 응답 템플릿 (responses) LLM이 쓰고, 지킬 규칙은 시스템 프롬프트(System Prompt)로 개발자 규칙 + LLM 커스텀 액션 (Custom Action) 도구 함수. 거의 그대로 개발자 수집·정제·적재, 5분 배치 그대로 개발자 굵은 두 줄이 그때 내 몫이었다. 위쪽(알아듣기, 되묻기, 말하기)은 LLM이 가져갔고, 아래쪽(데이터와 조회)은 그대로 남았다. 2. 전체 구조 [ 실시간: 질문에 답하기 ] 학생 ─ React 채팅 화면 │ POST /chat ▼ api.py (FastAPI) ─ 세션별 대화 기록 보관 │ ▼ agent.py (에이전트 루프) ⇄ LLM (Claude API) │ LLM: "이 도구를 이 인자로 불러 줘" ▼ tools.py (도구 = 예전 커스텀 액션) ├─ 좌석·학식·연락처·졸업요건 ──→ DB (예제는 SQLite, 운영은 Oracle) └─ 과목 해설 검색 (RAG) ───────→ 벡터 DB (Chroma) [ 배치: 데이터 만들기 ] ← 그때 내가 하던 일 scheduler.py (웹 서버와 따로 도는 프로세스) ├─ 5분마다 도서관 좌석 수집 → DB (현재 값 덮어쓰기 + 이력 쌓기) ├─ 매일 학식 수집, 보관 기간 지난 이력 삭제 └─ 매주 교직원 연락처 갱신 ingest_courses.py (학기마다) └─ 교육과정 PDF → 과목 단위로 자르기 → 임베딩 → 벡터 DB 위 절반이 새로 생긴 부분이고, 아래 절반은 그때와 같다. 3. 준비 폴더 구조 dongguk-bot/ ├─ db.py # DB 연결과 테이블 ├─ seed.py # 샘플 데이터 (바로 돌려 보기용) ├─ collectors/ # 수집기. 그때 코드를 다듬어 재사용 │ ├─ library.py │ ├─ meal.py │ └─ staff.py ├─ rag.py # 벡터 검색 ├─ ingest_courses.py # PDF → 벡터 DB ├─ tools.py # 도구 (= 예전 actions.py) ├─ agent.py # LLM + 도구 루프 ├─ api.py # FastAPI 서버 ├─ scheduler.py # 배치 └─ eval.py # 평가 설치 pip install anthropic voyageai chromadb fastapi uvicorn "apscheduler<4" \ selenium beautifulsoup4 pdfplumber export ANTHROPIC_API_KEY=... # LLM (Claude API) export VOYAGE_API_KEY=... # 임베딩 (Voyage AI) 키가 두 개인 이유: Anthropic은 임베딩 모델을 따로 내놓지 않고, 공식 문서에서 Voyage AI를 권한다. 임베딩은 6장에서 설명한다. APScheduler는 3.x 기준이다. 4.x는 사용법이 크게 달라서 버전을 묶어 뒀다. DB 예제는 설치 없이 바로 돌도록 SQLite로 쓴다. 운영 DB가 Oracle이면 연결을 oracledb (python-oracledb)로 바꾸고 SQLite 전용 문법 몇 군데(코드 주석에 표시)만 고치면 된다. :name 바인드 변수 문법은 둘이 같다. # db.py — 예제는 바로 돌려 보도록 SQLite. 운영 DB가 Oracle이면 연결 부분만 바꾼다. import sqlite3 from contextlib import contextmanager DB_PATH = "dongguk.db" SCHEMA = """ CREATE TABLE IF NOT EXISTS seat_current ( -- 챗봇이 읽는 현재 좌석 (5분마다 덮어쓰기) room TEXT PRIMARY KEY, used INTEGER, total INTEGER, remain INTEGER, status TEXT NOT NULL, -- 'open' | 'closed' collected_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS seat_history ( -- 통계용 이력 (5분마다 쌓기, 1년 보관) room TEXT, used INTEGER, total INTEGER, remain INTEGER, status TEXT, collected_at TEXT ); CREATE TABLE IF NOT EXISTS meal ( day TEXT, cafeteria TEXT, meal_time TEXT, corner TEXT, menu TEXT, status TEXT NOT NULL, -- 'open' | 'no_service' collected_at TEXT ); CREATE TABLE IF NOT EXISTS staff ( dept TEXT, name TEXT, position TEXT, phone TEXT, updated_at TEXT ); CREATE TABLE IF NOT EXISTS graduation_req ( department TEXT, admission_year INTEGER, content TEXT, source TEXT, page INTEGER ); """ @contextmanager def get_conn(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row # row["room"]처럼 컬럼 이름으로 꺼낸다 try: yield conn conn.commit() finally: conn.close() def init_db(): with get_conn() as conn: conn.executescript(SCHEMA) 좌석 테이블이 둘인 건 그때 구조 그대로다. 챗봇은 현재 값( seat_current )을 읽고, 통계는 이력( seat_history )을 본다. 처음부터 들어간 게 하나 있다면 status 컬럼이다. 이유는 바로 다음 장에 나온다. 수집기 없이 바로 돌려 보려면 샘플 데이터를 넣는다. # seed.py — 수집기 없이 바로 돌려 보기용 샘플 데이터 from db import get_conn, init_db init_db() with get_conn() as conn: conn.executemany("INSERT OR REPLACE INTO seat_current VALUES (?, ?, ?, ?, ?, ?)", [ ("IC Zone", 28, 40, 12, "open", "2026-10-03 14:05:00"), ("제1열람실", None, None, None, "closed", "2026-10-03 14:05:00"), ]) conn.execute("INSERT INTO graduation_req VALUES (?, ?, ?, ?, ?)", ("컴퓨터공학전공", 2023, "(예시) 졸업학점 OOO, 전공 OO학점", "2023_교육과정.pdf", 1)) 4. 데이터 — 그때 하던 일, 교훈을 넣어서 이 부분은 그때와 거의 같다. Selenium으로 다 그려진 페이지를 받고, BeautifulSoup으로 값을 뽑고, DB에 넣는다. 다른 점은 운영하면서 배운 걸 처음부터 넣는다 는 것이다. (주소와 선택자는 예시다.) # collectors/library.py — 5분마다 도는 도서관 좌석 수집기 from datetime import datetime from bs4 import BeautifulSoup from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from db import get_conn URL = "https://library.example.ac.kr/seat" # 예시 주소 def fetch_html() -> str: options = webdriver.ChromeOptions() options.add_argument("--headless=new") driver = webdriver.Chrome(options=options) # 드라이버 경로를 적지 않는다 → 크롬 버전에 맞는 드라이버를 Selenium이 찾는다 try: driver.set_page_load_timeout(30) # 사이트가 멈추면 30초 뒤 포기하고 다음 5분에 다시 driver.get(URL) WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, "table tr td"))) return driver.page_source finally: driver.quit() # 에러가 나도 크롬을 꼭 닫는다 def to_int(s: str): s = s.replace(",", "").strip() return int(s) if s.isdigit() else None def to_row(room: str, used, total, collected_at: str) -> dict: """그때의 정제 규칙 + 운영하면서 고친 것""" if not total: # 전체 좌석이 비거나 0 → 문 닫은 것. "0석"이 아니다 return {"room": room, "used": None, "total": None, "remain": None, "status": "closed", "collected_at": collected_at} used = used or 0 return {"room": room, "used": used, "total": total, "remain": max(total - used, 0), # 사이트에 없는 '남은 자리'는 뺄셈으로 만든다 "status": "open", "collected_at": collected_at} def parse(html: str, collected_at: str) -> list[dict]: rows = [] for tr in BeautifulSoup(html, "html.parser").select("table tr"): tds = [td.get_text(strip=True) for td in tr.select("td")] if len(tds) < 3: # 머리줄(th)이나 빈 줄은 건너뛴다 continue room, total, used = tds[0], to_int(tds[1]), to_int(tds[2]) rows.append(to_row(room, used, total, collected_at)) return rows def collect_library_seats(): now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") rows = parse(fetch_html(), now) with get_conn() as conn: # 현재 값은 덮어쓰고(챗봇용), 같은 값을 이력에 쌓는다(통계용). Oracle이면 MERGE INTO로 쓴다 conn.executemany(""" INSERT INTO seat_current VALUES (:room, :used, :total, :remain, :status, :collected_at) ON CONFLICT(room) DO UPDATE SET used = excluded.used, total = excluded.total, remain = excluded.remain, status = excluded.status, collected_at = excluded.collected_at""", rows) conn.executemany(""" INSERT INTO seat_history VALUES (:room, :used, :total, :remain, :status, :collected_at)""", rows) 코드에 넣은 교훈은 네 가지다. "0석"과 "휴관"을 구분한다 ( status ). 운영 중에 휴관일이나 개관 전 새벽에 챗봇이 "0석 남았습니다"라고 답했다. 빈 값을 0으로 채운 게 "꽉 차서 0"과 "문 닫아서 0"을 섞은 것이다. LLM 시대엔 이게 더 위험하다. LLM은 remain: 0 을 받으면 의심 없이 "지금 0석 남았어요"라고 아주 자연스럽게 말한다. 값의 뜻을 데이터에 박아 둬야 한다. 수집 시각을 같이 넣는다 ( collected_at ). LLM이 "14:05 기준"이라고 답할 재료다. 크롬은 무조건 닫는다 ( finally: driver.quit() ). 운영 중에 수집이 겹치고 크롬이 안 닫혀 쌓인 적이 있다. 에러가 나도 닫히게 하고, 페이지 로딩 제한 시간을 둬서 한 번 멈춘 수집이 다음 수집까지 잡아먹지 않게 한다. 드라이버 경로를 적지 않는다. Selenium 4.6부터는 webdriver.Chrome() 만 쓰면 Selenium Manager가 설치된 크롬에 맞는 드라이버를 찾아 준다. 그때 Docker 안에서 막혔던 게, 따로 받아 둔 드라이버를 코드가 가리키고 있어서였다. 학식 수집기에는 하나가 더 들어간다. 운영
들어가며 그동안 이전 티스토리 블로그에는 백엔드 취업 준비 이야기를 주로 썼습니다. 현재는 디스플레이 공정의 R2R 시스템을 개발하고 있어서 이 블로그에는 이 도메인의 이야기를 정리해보려고 합니다. 첫글은 R2R (Run to Run) 이 무엇인지부터입니다. R2R 한 줄 정의 * R2R(Run-to-Run)은 이번 공정 결과를 보고 다음 공정의 설정값을 자동으로 고치는 제어 방식입니다. * 반도체 공정에서 먼저 자리 잡았고, 디스플레이 공정에서도 같은 방식으로 쓰입니다. R2R이 필요한 이유 공장 장비는 기존에 엔지니어들이 지정해둔 레시피대로 돌아갑니다(생산). 레시피는 온도, 시간, 노광량 같은 설정값 묶음입니다. 문제는 같은 레시피로 돌려도 결과가 계속 같지 않다는 점입니다. • 챔버 안에 부산물이 쌓이고 부품이 닳으면서 결과가 서서히 한쪽으로 밀립니다(drift). • 장비 정비(PM) 직후에는 결과가 갑자기 튀기도 합니다(shift). 사람이 매번 측정값을 보고 레시피를 손으로 고칠 수는 없습니다. 그래서 이 일을 시스템이 자동으로 합니다. 용어 정리 제목 내용 Run 공정 1회 실행 단위 (lot 또는 glass 1매 등) Recipe 장비에 내려가는 설정값 묶음 Metrology(Measure) 공정 후 결과를 측정하는 단계 (두께, 선폭 등) Target 목표값 Spec 허용 범위 (상한과 하한) 생산 실측 생산 장비가 실제로 적용한 조건 계측 실측 계측 장비가 결과물을 측정한 값 APC Advanced Process Control. R2R을 포함하는 상위 개념 R2R이 받는 두 가지 데이터: 생산 실측과 계측 실측 생산 실측 계측 실측 어디서 오나 생산(공정) 장비 계측 장비 무엇인가 실제로 적용된 조건 결과물을 측정한 값 예시 실제 증착 시간, 실제 노광량 막 두께, 선폭 제어 관점 입력 출력 R2R 개발에서 중요한 개념은 생산 실측과 계측 실측입니다. 둘 다 필요한 이유는 레시피에 적은 설정값과 장비가 실제로 수행한 값이 다를 수 있기 때문입니다. 50초로 지시했는데 장비가 51초를 돌렸다고 가정해 보겠습니다. 결과가 두껍게 나온 원인 중 일부는 장비 상태가 아니라 1초를 더 돌린 데 있습니다. 설정값만 보고 계산하면 이 차이까지 장비 탓으로 돌려 보정이 과해집니다. R2R을 처음 접할 때 반드시 드는 일상 예시로, 라면으로 치면 물 500ml를 넣으려 했는데 실제로는 520ml가 들어간 것이 생산 실측입니다. 먹어 보니 싱거웠다는 것이 계측 실측입니다. R2R이 실제로 보정하는 방식 Feedback: 결과를 먼저 보고 고치기 Feedback은 직전 run의 결과로 다음 run의 조건을 고치는 사후 보정입니다. 장비가 run을 수행하고 생산 실측을 남깁니다. 계측 장비가 결과물을 측정해 계측 실측을 남깁니다. 컨트롤러가 target과의 오차를 계산합니다. 오차가 줄어들도록 다음 run의 레시피를 보정합니다. 한계도 있습니다. 측정이 끝나야 반영되므로 한 박자 늦습니다. Feedforward: 미리 예측하고 고치기 Feedforward는 공정을 시작하기 전에 이미 알고 있는 정보로 조건을 미리 고치는 사전 조정입니다. 대표적인 정보가 앞 공정의 계측 실측입니다. 앞 공정에서 막이 평소보다 두껍게 올라간 글라스라는 것을 알면, 식각 공정에 들어가기 전에 식각 시간을 미리 늘려 줍니다. * 같은 계측 실측이 자기 공정에는 Feedback으로, 다음 공정에는 Feedforward로 쓰입니다. * Feedback Feedforward 시점 공정 후 공정 전 근거 내 공정의 결과 앞 공정의 결과, 현재 상태 라면 비유 어제 싱거웠으니 물을 줄인다 오늘은 큰 냄비라 물을 더 넣는다 결론적으로 실제 현장에서는 두 방식을 함께 사용합니다. 숫자로 보는 보정 예시 아래 숫자는 설명을 위한 가상의 값입니다. 목표 두께는 100nm이고, 1초에 2nm가 쌓인다고 가정합니다. 모델: 두께 = 2 × 시간 + 보정치 (처음 보정치는 0) 단계 값 설명 설정값 50초 (100 − 0) ÷ 2 생산 실측 51초 장비가 실제로 돌린 시간 모델 예측 102nm 2 × 51 + 0 계측 실측 106nm 실제로 측정한 두께 오차 4nm 106 − 102 새 보정치 2 0.5 × 4 + 0.5 × 0 다음 run 설정값 49초 (100 − 2) ÷ 2 오차 4nm를 전부 반영하지 않고 절반만 반영했습니다. 측정에는 노이즈가 섞여 있어서, 전부 따라가면 레시피가 매번 출렁이기 때문입니다. * 이렇게 최근 오차에 가중치를 두고 과거 값과 섞는 방식을 EWMA(지수가중이동평균)라고 합니다. 통상적으로 R2R에서 가장 기본이 되는 알고리즘입니다. * 가중치가 크면 빠르게 따라가지만 노이즈에 흔들리고, 작으면 안정적이지만 느립니다. 생산 실측 대신 설정값 50초로 계산하면 어떻게 될까요? 모델 예측이 100nm가 되어 오차를 6nm로 잡습니다. 보정치는 3, 다음 설정값은 48.5초가 됩니다. 장비가 1초 더 돌린 몫까지 보정해 버린 과한 보정입니다. R2R 개발자는 뭐하는 건지 생산 실측과 계측 실측을 수집하고 run 단위로 짝짓기. 모델에 따라 보정값을 계산. 보정값이 허용 범위 안인지 검증합니다. 벗어나면 적용X. 보정된 레시피를 다음 run에 전달. 자세한건 회사 대외비라 이정도만 작성하겠습니다. 마무리 정리를 해보자면 R2R 시스템은 고정된 레시피를 반복하지 않고, 실측값을 근거로 레시피를 계속 고쳐 결과를 target에 붙여 두는 구조입니다.
Por Leander Van Linthout Publicado em 3 de outubro de 2026 Para aumentar as chances de aparecer na Meta AI, torne as informações da sua empresa públicas, coerentes e úteis para as perguntas que os clientes fazem no WhatsApp e no Instagram. Comece pelos perfis comerciais, pelas páginas do site e pelas referências externas que confirmam o que você oferece. Depois, teste perguntas sobre a categoria, a localização e as necessidades do comprador, registrando menções e erros. Essas ações melhoram a clareza e a presença digital da marca, mas não garantem uma recomendação. Ter um perfil no WhatsApp Business ou usar uma IA de atendimento também não equivale a entrar nas respostas do assistente. Como a Meta AI encontra informações sobre empresas? A Meta AI pode recorrer a informações e conteúdos disponíveis nas experiências que a Meta oferece, mas as fontes e os recursos variam conforme a pergunta e o contexto. Não existe, na documentação consultada, uma fórmula pública que permita controlar a ordem das recomendações comerciais. A Meta anunciou recursos de compras que combinam anúncios do Facebook Marketplace com opções da internet. Também descreveu formas de consultar conteúdo público de marcas e criadores. Isso confirma caminhos de descoberta, sem demonstrar que toda publicação de uma empresa será usada numa resposta. No Facebook, o AI Mode usa a Meta AI para responder com base em opiniões e recomendações compartilhadas publicamente nas aplicações da empresa. Esse anúncio refere-se a uma experiência específica do Facebook. Não permite concluir que o WhatsApp e o Instagram reproduzem exatamente as mesmas fontes ou resultados. A recomendação prática é construir informações que possam ser verificadas: nome comercial, atividade, localização, produtos, condições de compra e contactos. Trate isso como organização da presença digital, sem apresentar cada campo como um fator de classificação confirmado. Descoberta e atendimento são funções diferentes Uma pessoa pode pedir à Meta AI uma indicação de fornecedor. Outra pode entrar no WhatsApp da sua empresa e conversar com uma IA configurada para atendimento. A primeira situação envolve encontrar e selecionar negócios. A segunda envolve responder a um cliente que já chegou. A Meta descreveu o Business AI no WhatsApp como uma ferramenta que responde usando informações da empresa, incluindo perfil e catálogo. Ativar esse recurso, quando disponível, não comprova inclusão nas recomendações gerais. Pergunta à Meta AI O que o cliente procura: Descobrir fornecedores. Prioridade da empresa: Informações públicas claras. Busca no Instagram O que o cliente procura: Encontrar uma conta ou conteúdo. Prioridade da empresa: Perfil identificável e publicações úteis. Conversa no WhatsApp Business O que o cliente procura: Confirmar condições e comprar. Prioridade da empresa: Catálogo e atendimento atualizados. Conversa com uma IA comercial O que o cliente procura: Receber respostas sobre o negócio. Prioridade da empresa: Base de informações correta. O que corrigir primeiro no Instagram e no WhatsApp Business? Corrija primeiro os dados que identificam a empresa e explicam a sua oferta. Um cliente deve conseguir confirmar quem você é, onde atua e como comprar sem interpretar frases vagas. No Instagram Use um nome reconhecível e descreva a atividade na biografia. Inclua a cidade ou a área de atendimento quando isso for relevante. Organize o acesso ao site e ao contacto comercial. Uma descrição como “design, inspiração e momentos especiais” pouco ajuda alguém que procura um serviço concreto. “Móveis por medida em Lisboa, com projeto e instalação” esclarece atividade e localização, desde que corresponda ao negócio. Mantenha acessível o conteúdo que deseja usar para divulgação pública. Nas publicações, explique materiais, aplicações, prazos e diferenças entre produtos. Escreva legendas compreensíveis sem depender exclusivamente da imagem. Para uma loja de cerâmica, uma publicação útil responde se a peça pode ir à máquina de lavar, qual é a capacidade e como funciona a entrega. A informação ajuda compradores e torna o conteúdo menos ambíguo. No WhatsApp Business Revise nome, descrição, horário, contacto e catálogo, quando essas opções estiverem disponíveis. Use descrições que permitam distinguir produtos parecidos. “Modelo A” é menos informativo do que “caneca de cerâmica artesanal, 300 ml, acabamento verde”. Preços e disponibilidade devem refletir as condições atuais. Quando o valor depende de orçamento, explique quais informações o cliente precisa enviar. Confirme também se o contacto divulgado no site e no Instagram chega ao atendimento correto. Uma marca pode ganhar uma menção e perder a venda porque o número está desatualizado ou ninguém esclarece a condição de compra. Essas melhorias têm utilidade comercial direta. A influência de cada alteração nas respostas da Meta AI precisa ser observada, porque não há uma correspondência pública garantida entre preencher um campo e receber uma indicação. Como escolher as perguntas que a sua empresa precisa responder? Escolha perguntas que representam decisões reais de compra, especialmente sobre categoria, localização, adequação e condições. Comece pelas dúvidas recebidas no atendimento, em vez de depender apenas de termos amplos. O SQSEO ajuda nessa etapa ao transformar uma palavra-chave inicial em consultas de cauda longa e perguntas agrupadas por intenção. Use os resultados como hipóteses de conteúdo e adapte-os ao vocabulário dos seus clientes. Não trate essas sugestões como um relatório comprovado de buscas feitas dentro da Meta AI. Separe as perguntas em quatro grupos: Categoria: “Onde comprar canecas artesanais?” Localização: “Que lojas de cerâmica fazem entregas no Porto?” Necessidade: “Que caneca artesanal serve para um presente?” Condição: “Onde encomendar peças personalizadas em pequenas quantidades?” Para cada pergunta, identifique a página ou publicação que responde com informação concreta. Se ninguém consegue localizar essa resposta, há uma lacuna de conteúdo. Por exemplo, uma página que responde a “Onde comprar canecas artesanais com embalagem para presente em Portugal?” deve explicar quais produtos têm embalagem, onde são entregues, como consultar o prazo e se existe personalização. Fotografias ajudam a avaliar a aparência; o texto esclarece condições que a imagem não comunica. A publicação no Instagram pode apresentar a embalagem e responder à mesma dúvida na legenda. O catálogo no WhatsApp deve usar informações compatíveis. Evite criar dezenas de páginas quase iguais para cidades onde não existe presença ou serviço distinto. Uma página completa sobre a área de entrega pode resolver a dúvida com mais precisão. Como preparar o site e as referências externas? Prepare o site para confirmar a identidade da empresa e responder às perguntas comerciais com conteúdo acessível. Depois, procure referências externas legítimas que acrescentem contexto sobre a marca. Nas páginas de produtos e serviços, apresente a resposta principal antes de textos institucionais extensos. Indique a oferta, o público adequado, as condições e a forma de contacto. Confirme se as páginas relevantes abrem sem exigir autenticação e se o conteúdo principal aparece em texto legível. Peça uma revisão técnica quando houver bloqueios, páginas duplicadas ou informação comercial visível apenas em imagens. Dados estruturados coerentes com a página podem ajudar sistemas compatíveis a interpretar informações. Não apresente schema ou llms.txt como um mecanismo garantido para entrar na Meta AI: não há evidência suficiente para essa promessa. Que referências externas merecem atenção? Priorize diretórios setoriais, associações, parceiros e publicações que tenham relação real com a atividade. Uma referência que descreve o produto e a localização é mais útil ao leitor do que uma menção sem contexto. Solicite avaliações honestas sem orientar clientes a inventar resultados. Não compre comentários nem multiplique perfis artificiais. O objetivo é produzir referências verificáveis, e não simular popularidade. O relatório “Impacto da Meta no Brasil”, produzido com a Morning Consult, reuniu respostas de 794 líderes empresariais no final de 2024. Os resultados descrevem perceções sobre ferramentas da Meta e utilidade comercial. Não medem o que determina uma recomendação da Meta AI. Um estudo da Kantar de 2025, citado pela Meta no anúncio do Business AI na Índia, informa que 91% dos adultos online naquele país conversam semanalmente com empresas. Esse dado contextualiza o atendimento por mensagens; não mede recomendações da IA e não deve ser extrapolado para países lusófonos. Esses recortes ajudam a evitar uma confusão: evidência de valor comercial das mensagens não é evidência de fatores de classificação na IA. Como medir se a empresa aparece na Meta AI? Meça com um conjunto estável de perguntas, registre o contexto e repita os testes. Uma resposta isolada mostra apenas o resultado daquela execução. Como rotina inicial, escolha dez perguntas comerciais e repita cada uma três vezes em conversas novas, nas aplicações em que a Meta AI estiver disponível para a sua conta. Esse número é uma proposta de trabalho para começar, não um padrão estatístico oficial. Separe perguntas que incluem a marca de perguntas que não a mencionam. “O que a empresa X vende?” testa reconhecimento. “Onde comprar canecas artesanais em Portugal?” testa descoberta. Misturar os dois grupos faz a visibilidade parecer maior do que é. Registre a formulação exata, a aplicação, a data, o idioma e qualquer localização fornecida. Guarde a resposta e as fontes exibidas, quando existirem. Não altere todos os elementos entre uma rodada e outra. Menção sem nome da marca Como registrar: Presente ou ausente em cada resposta. O que permite avaliar: Descoberta espontânea. Descrição da empresa Como registrar: Correta, incompleta ou errada. O que permite avaliar: Qualidade da informação. Fonte apresentada Como registrar: Página ou perfil exibido.