두 번째로 가보는 일본 출장 로그(30 Jun – 4 Jul 2026)

2년 전의 출장은 하네다 공항으로 가는 비행편이 없어 나리타 공항으로 예매를 했었는데, 도심에서 한참이나 떨어져 있어서 이동하는데 시간이 꽤나 걸렸었다. 이번에는 하네다 공항편으로 예매를 했더니 김포공항 출발편이어서 이동하기도 비교적 수월하고 도착해서도 이동에 많은 시간이 소요 되지는 않았다.

연예인과 함께하는 출국

김포공항 3층 출국장으로 오르는 길에 수많은 팬들이 누군가를 마중나와서 한참이나 기다리고있었다. ‘누구 대단한 사람 이라도 오나…’ 출국 수속을 마친다음 터미널 앞에서 기다리고 있었는데 어디 선가 굵은 목소리의 남자들이 “비켜요! 앞에 비켜!!” 외치면서 다가오고 있었다 그에 앞서서는 한 무리의 여자 사람 팬들이 웅성 거리면서 시진찍기 좋은 위치를 찾아 경쟁하며 뛰어들어왔다. 의자 밟고 올라서서 사진을 열심히 찍다가 왠 예쁘장한 남자 아이돌 무리가 나타나서는 비즈니스석 입구로 들어가자 아쉬운듯 그제야 카메라를 내려 놓았다.

그런데 터미널 안까지 들어 왔다는 건, 비행기표까지 끊어가면서 연예인을 따라다닌 다는 말이지 않는가. 게 중에는 비지니스석은 아니지만 마일리지 많은 사람들에게 주는 우선탑승객 사람들도 꽤나 있었는데 연예인을 따라다니면서 썼을 돈의 규묘를 짐작하게했다. 아무리 덕질에 돈이 든다고는 하지만…

나중에 인터넷 기사 검색을 해보니 아마도 그룹 투어스가 아니었나 싶다.

전시회 준비

협력회사 측에서 마중을 나와준 덕에 하네다공항에서 전시회장인 도쿄 빅사이트까지는 편하게 이동 할 수 있었다. 문제는 전시품의 배치와 연결 이었는데 사전에 사무실에서는 연결과 동작이 잘 되는지 확인만 한 정도였기 때문에 실제 전시회장에 들어오는 전선의 길이나 전시회 물품의 배치에 따른 연결은 고려되지 않았었다.

서버를 전시품 탁자 아래에 숨겨 놓고 220v 승압기 세대에 전시품들을 각각 연결하고, 유선랜을 배치하고 네트워크를 설정하는 작업을 하고 나니 어느새 저녁이 되었다.

유니콘 건담

설정을 마치고 호텔 체크인을 한 다음 저녁밥을 먹고 나서는 산책 삼아 걷다가 호텔에서 걸어서 30분정도 거리에 떨어져있는 유니콘 건담 실물 크기 상이 있다고 해서 구경하러 갔다.

낮에 보니까 이렇게 생겼더라

2일차 – 4일차

전시회 기간동안은 정신없이 지나 갔는데, 아침은 맥도날드, 저녁은 스키야 규동을 먹는 삶을 반복하며 정신없이 보냈다. 일본어를 할 줄 모르기 때문에 방문자가 찾아와도 할 수 있는게 없기는 했지만 데모 시스템이 이상동작 기미를 보일 때 마다 움직여 주거나 과열되지 않도록 보기 흉한 통풍구를 잠시 열어 둔다던가 가끔씩 영어로 소통이 가능한 사람이 있으면 설명하고 질문에 답하는 일을 했다. 의외로 한국에서 온 관람객들도 꽤나 있었는데, 데모에 관심을 보여서 명함을 교환하고 다시 연락하기로 했다.

전시장 뒷편에는 짬짬히 앉아서 쉴 수 있는 공간이 있는데, 관람객들이 없을 때는 여기에 들어가서 잠깐씩 쉬기도 했다. 그런데 꽤나 큰 행사여서 그런지 사실상 사람들이 없는 시간은 많지 않았다.

마지막날

온전히 편하게 쉴 수 있는 하루가 주어졌다. 뭘 해야 할까? 행사도 잘 끝나고 해체작업도 완료 했으니 이 날 하루는 마음 편하게 쉬는 것만 남았다.

그래도 아무것도 안하는 것은 좀 아니지 않나 싶어서 지하철로 40분 거리에 떨어져 있는 Shimbashi에 다녀 왔다. 점심 먹을 곳을 찾던 중 일본에서 유명하다고 들었던 이치란 라멘집이 있어서 가볼까 했더니 벌써 가게 밖까지 줄을 서고 있었다. 그 외에도 몇군데를 다녀봤는데 사람들이 가게 주변에 줄을 서 있는 곳이 많아서 마땅히 밥먹을 곳을 찾기가 쉽지 않았다.

저렇게 까지 해야 되나… 싶어하던 안일한 관광객의 점심은 편의점 라면이 되었다.

Bento Stand

나에게는 조금 특이하다고 생각되었던 것 중 하나가 여러 건물들에서 보이는 “Bento Stand”라는 것이었는데, 건물의 로비 혹은 한 층에 테이블과 한 두 사람이 앉을 수 있는 공간을 마련해 두고 다른 곳에서 사온 음식을 먹거나 간단하게 사무를 볼 수 있는 공간이 마련되어 있었다. 실제로 많은 사람들이 이곳에 앉아서 도시락을 먹고는 하는데 이름에서도 그렇듯이 아마도 일본의 도시락 문화와 관련한 배려가 아닐까 싶다.

도라에몽

호텔 근처에 도라에몽 케릭터에 관련한 전시물들이 여기저기에 있었는데 혹시 오다이바 지역이 도라에몽과 관련이 있는 지 일본인 지인에게 물어 봤더니. 아닐꺼라고 한다. 그냥 있는 것일 거라고…

유동인구도 별로 없는 이런곳에 이런 썰렁한 동상들을 왜 세워 놨을까 하던 의구심은 주말이 되자 바로 풀렸다. 어마어마 하게 많은 사람들이 이 지역을 찾고 도라에몽을 둘러싸고 사진을 찍으면서 가족 나들이 하는 모습을 보니 오다이바 지역을 왜 도심속 관광지라고들 하는지 알것 같았다.

gRPC Streaming으로 상태 전송 받기 예제

gRPC에서 Observer pattern을 구현하는 예제를 고민했던 적이 있었는데(gRPC를 이용한 Observer Pattern) 알고보니 gRPC에서는 ReadReactor / WriteReactor 보다 간단하게 object의 상태 변경을 감지할 수 있는 메커니즘을 제공하고 있었다. gRPC를 이용한 Observer pattern 구현 예제를 찾기 어려웠던 이유가 어쩌면 그런 복잡한 패턴 없이도 구현이 가능하기 때문이었는지도 모르겠다.

이 포스팅에서는 Stream을 통해 변경되는 값들을 subscribe하는 간단한 예제를 Rust로 구현해 본다. gRPC의 Rust 구현체인 tonic과 proto compiler인 prost, 비동기 처리를 위해 tokio를 사용했다.

Proto file – stockprice.proto

랜덤하게 생성된 주가 정보를 gRPC로 구독하는 간단한 서비스 예제를 가정해 보자. Proto file은 UpdateStockPrice()를 정의하고 호출되면 StockPriceResponse의 stream을 받아오도록 다음과 같이 구성한다.

syntax = "proto3";
package stockservice;

import "google/protobuf/empty.proto";

message StockPriceResponse {
    string symbol = 1;
    double price = 2;
    uint64 sequence = 3;
}

service StockService {
    rpc UpdateStockPrice(google.protobuf.Empty) returns (stream StockPriceResponse);
}

Server

StockServiceImpl 구조체를 선언하고 서비스를 제공하기 위한 StockService를 다음과 같이 구현한다.

// Server 구현체
struct StockServiceImpl;

// tokio_stream을 이용해서 gRPC stream을 구현.
type StockStream = Pin<Box<dyn Stream<Item = Result<StockPriceResponse, Status>> + Send>>;

#[tonic::async_trait]
impl StockService for StockServiceImpl {
    type UpdateStockPriceStream = StockStream;

    async fn update_stock_price(
        &self,
        _request: Request<()>,
    ) -> Result<Response<Self::UpdateStockPriceStream>, Status> {

        // 1초 간격으로 stock price 생성
        let mut interval = tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(
            Duration::from_secs(1),
        ));

        // Stream으로 전송할 데이터를 생성하고 yield로 반환.
        let output = async_stream::stream! {
            let mut rng = SmallRng::from_os_rng();
            let mut sequence: u64 = 0;

            // 매 interval마다 순번을 증가시키고 랜덤 주가를 생성해 클라이언트로 전송
            while interval.next().await.is_some() {
                sequence += 1;
                let p = generate_stock_price(&mut rng, sequence);
                yield Ok(p);
            }
        };

        Ok(Response::new(
            Box::pin(output) as Self::UpdateStockPriceStream
        ))
    }
}

이 코드에서 핵심은 async_stream::stream!() 매크로로 데이터를 전송할 데이터임을 정의하고, while / yeild로 data를 전송하는 다음의 부분이다. 이 부분에서 데이터를 갱신하고 stream에 지속적으로 update 시킨다.

        let output = async_stream::stream! {
           ...

            // 매 interval마다 순번을 증가시키고 랜덤 주가를 생성해 클라이언트로 전송
            while interval.next().await.is_some() {
                // Data 생성 및 전송
                ...
                yield Ok(p);
            }
        };

Client

Server에 비해 Client 쪽은 비교적 구현이 간단한데, prost가 proto file로 부터 생성해 준 StockServiceClient를 이용해서 stream을 생성하고, 이 때 반환받은 stream으로 부터 계속 데이터를 읽어 주면된다.

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let mut client = StockServiceClient::connect("http://[::1]:50051").await?;

    // grpc 서비스 stream 생성.
    let mut stream = client
        .update_stock_price(Request::new(()))
        .await?
        .into_inner();

    // stream에서 데이터를 받아서 화면에 출력.
    while let Some(update) = stream.message().await? {
        println!(
            "[{}] {}: {:.2}",
            update.sequence, update.symbol, update.price
        );
    }

    Ok(())
}

코드 위치 및 수행결과

전체 구현 코드는 다음 위치에 있다. https://github.com/litcoder/grpcobsr/tree/stream_rust

Gmail의 Sub-addressing 기능과 활용

거의 모든 인터넷 서비스들이 연락처로 email 주소를 기입하라고 요구한다. 때로는 가입하는 서비스에 따라 서로 다른 이메일을 생성하고 가입하기도 하는데, 이러다 보면 어느새 수 많은 이메일 계정들을 새로 만들어야 하고 관리가 불가능한 지경에 이르게 될 수도 있다. 이 포스팅에서는 별도의 설정 없이 Gmail 주소를 여러개로 확장하여 수신 메일을 라벨링하고, 필터링할 수 있는 서브어드레싱(Sub-addressing)에 대해 정리해본다.

Mechanism: 플러스(+)와 점(.)의 처리 방식

Gmail을 비롯한 많은 서비스 들이 RFC 5233 를 지원한다. Gmail 서버의 경우 수신 주소의 로컬 파트에서 특정 문자를 처리할 때 다음과 같은 규칙을 따른다.

  • The Plus (+) Operatorusername+keyword@gmail.com 형식으로 사용하며, 서버는 + 이후의 문자열을 무시하고 원본 주소인 username@gmail.com으로 라우팅한다.
  • The Dot (.) Neutrality: 로컬 파트 내의 마침표는 무시된다. 즉, user.name@와 username@은 논리적으로 동일한 엔드포인트를 가리킨다.

활용하기

인프라 모니터링 분류

시스템 모니터링 용도로 서비스 별 주소를 분리하면 필터링을 편하게 할 수 있다. 매번 용도에 따라 새로운 email 주소를 만드는 것이 아니라, plus operator를 사용해서 해당 주소로 들어오는 것들은 별도로 필터링을 수행하는 것이다. 예를 들어 GitHub에서 오는 noti 이메일을 별도로 관리해서 자동으로 라벨을 붙이고 싶다면 다음과 같이 +가붙은 새로운 이메일 주소(계정+github@gmail.com)을 GitHub에 등록하고 Gmail엣 filtering에서 label을 선택해주면 자동으로 라벨을 붙이도록 할 수 있다.

개발 단계의 멀티 계정 테스트

OAuth 가입 로직이나 이메일 인증 워크플로우를 테스트할 때 매번 새로운 계정을 생성하는 것보다는 user+test01user+test02와 같은 가상 주소를 사용하면 하나의 메일함 내에서 상태별 테스트 데이터를 격리하여 검증할 수도 있다.

유의 사항

RFC5233을 지원하지 않는 오래된 시스템이나 엄격한 정규표현식 검증을 수행하는 서비스의 경우 “+” 기호 자체가 유효하지 않은 문자라 판단하고 거부할 수도 있다는 점은 알아두자.

그리고 회사에서는 “이름.성@회사도메인” 형식으로 이메일을 생성규칙을 적용하는 경우가 많아서 dot neutrality 기능은 상황에 따라 비활성화 시켜두는 곳도 있다.