접속자 수와 시청자 수
접속 중·시청 중 인원을 보여 주고, 프레즌스로 고유 사용자를 세고, HTTP API와 웹훅으로 서버에서 방마다 인원을 읽습니다.
"1,284명 접속 중", "38명 시청 중", "방이 가득 찼습니다": 실시간 인원수는 제품을 활기차게 보이게 하고, 같은 숫자로 팀은 어느 방이 붐비는지, 언제 모더레이터를 늘려야 하는지, 저녁 피크가 얼마나 높았는지 알 수 있습니다. 연결을 직접 셀 필요는 없습니다. Hibira는 이미 각 채널에 누가 구독 중인지 알고 있고, Pusher 프로토콜에는 이를 물어보는 방법이 네 가지 있습니다.
사용자가 얻는 것
- 사람이 들어오고 나갈 때마다 바뀌는 "접속 중" 또는 "시청 중" 인원수.
- 아바타 줄과 "외 120명". 탭을 두 개 연 사람도 한 명으로 셉니다.
- 누군가 들어가려 하기 전에 "가득 참"을 보여 주는 방.
- 팀을 위해: 가장 붐비는 방을 보여 주는 관리 페이지와 피크 기록.

사용하는 기능
- 구독자 수(
pusher:subscription_count): 퍼블릭·프라이빗 채널의 인원수를 브라우저에서 받습니다. - 프레즌스 채널(
presence-)로 고유 사용자를 세고, 대형 프레즌스 채널(presence-large-)은 최대 100,000명까지 담습니다. - HTTP API(
GET /channels): 서버에서 언제든 인원수를 읽습니다. - 웹훅(
member_added,member_removed,subscription_count): 인원수가 바뀔 때 알려 주므로 기록을 남길 수 있습니다.
브라우저에서
pusher:subscription_count서버가 조회
GET /channels?info=user_count서버가 알림을 받음
member_added연결 수인가, 사람 수인가?
두 숫자는 서로 다르니 의도적으로 고르세요.
subscription_count는 연결을 셉니다. 탭을 세 개 연 사람은 세 번 셉니다. 로그인 없이 퍼블릭·프라이빗 채널에서 쓸 수 있어 공개 페이지의 "시청 중" 카운터에 맞습니다.user_count는 프레즌스 채널의 고유 사용자를, 서버가 서명한user_id기준으로 셉니다. 탭 두 개도, 휴대폰과 노트북도 한 명입니다. 좌석 제한이나 접속자 목록처럼 "사람"이 중요할 때 쓰세요.
1단계: 브라우저에서 인원수 보여 주기
앱 설정에서 구독자 수 전송을 켭니다. 그러면 퍼블릭·프라이빗 채널의 모든 구독자가, 인원수가 바뀌는 동안 최대 1초에 한 번 현재 인원수를 받습니다.
const stream = pusher.subscribe('stream-42');
stream.bind('pusher:subscription_count', ({ subscription_count }) => {
watching.textContent = subscription_count.toLocaleString();
});코드를 쓰지 않아도 한 줄 임베드 스크립트로 같은 일을 할 수 있습니다: <span data-hibira-online="stream-42">0</span>명 시청 중.
2단계: 프레즌스로 사람 세기
프레즌스 채널에서는 모두가 멤버 목록을 받으므로 인원수가 이미 브라우저에 있습니다.
const room = pusher.subscribe('presence-room-' + roomId);
const show = () => online.render(room.members.count, room.members);
room.bind('pusher:subscription_succeeded', show);
room.bind('pusher:member_added', show);
room.bind('pusher:member_removed', show);프레즌스 채널은 최대 100명입니다. 모든 멤버가 모든 입장을 받기 때문입니다. 사이트 전체나 큰 행사라면 모든 페이지가 하나의 대형 프레즌스 채널에 @hibira-io/large-presence로 참여하게 하세요. 최대 100,000명을 담고, 정확한 인원수와 현재 있는 사람의 샘플을 1초에 한 번 보냅니다.
import { LargePresence } from '@hibira-io/large-presence';
const site = LargePresence.subscribe(pusher, 'presence-large-site', { maxMembers: 12 });
site.on('ready', ({ count }) => badge.render(count, site.members));
site.on('change', ({ count }) => badge.render(count, site.members));3단계: 서버에서 조회하기
서버는 HTTP API로 같은 숫자를 읽을 수 있고, Pusher 호환 서버 라이브러리라면 무엇이든 됩니다. 채널 하나:
const res = await pusher.get({
path: '/channels/presence-room-42',
params: { info: 'user_count,subscription_count' },
});
const { occupied, user_count, subscription_count } = await res.json();모든 방을 한 번에, 붐비는 순서로:
const res = await pusher.get({
path: '/channels',
params: { filter_by_prefix: 'presence-room-', info: 'user_count' },
});
const { channels } = await res.json();
// { "presence-room-7": { "user_count": 42 }, "presence-room-9": { "user_count": 17 } }
const busiest = Object.entries(channels)
.map(([name, { user_count }]) => ({ name, users: user_count }))
.sort((a, b) => b.users - a.users);구독에 서명하기 전에 좌석 제한을 확인하려면, 채널 인가 라우트에서 방 인원수를 읽고 가득 찼으면 거절합니다.
app.post('/pusher/auth', requireLogin, async (req, res) => {
const { socket_id, channel_name } = req.body;
const room = await pusher.get({ path: `/channels/${channel_name}`, params: { info: 'user_count' } });
const { user_count } = await room.json();
if (user_count >= 8) return res.status(403).json({ error: 'room_full' });
res.json(pusher.authorizeChannel(socket_id, channel_name, { user_id: req.user.id, user_info: { name: req.user.name } }));
});그리고 발행할 때 그 순간 구독 중인 연결 수를 물어보면, 이벤트가 몇 명에게 도달했는지 알 수 있습니다.
const sent = await pusher.trigger('stream-42', 'goal', { minute: 81 }, { info: 'subscription_count' });
const { channels } = await sent.json();
console.log(`reached ${channels['stream-42'].subscription_count} viewers`);user_count 는 프레즌스 채널에서만 요청할 수 있고, 채널 목록에서는 filter_by_prefix=presence- 가 필요합니다. 여러 방의 인원수를 더하면 두 방에 동시에 있는 사람이 두 번 셉니다. 사이트 전체의 고유 인원은 대신 GET /channels/presence-large-site?info=user_count 를 읽으세요.
4단계: 웹훅으로 기록 남기기
저녁 피크를 그래프로 그리거나 방이 비면 알림을 보내려면 Hibira가 서버에 알려 주게 하세요. 포털에서 member_added 와 member_removed(프레즌스 채널), subscription_count(퍼블릭·프라이빗 채널, 카운트가 켜져 있을 때), 또는 channel_occupied 와 channel_vacated 웹훅을 추가합니다.
app.post('/hibira/webhooks', express.text({ type: '*/*' }), async (req, res) => {
const webhook = pusher.webhook({ headers: req.headers, rawBody: req.body });
if (!webhook.isValid()) return res.sendStatus(401);
for (const e of webhook.getEvents()) {
if (e.name === 'member_added') await db.online.add(e.channel, e.user_id);
if (e.name === 'member_removed') await db.online.remove(e.channel, e.user_id);
if (e.name === 'subscription_count') await db.viewers.record(e.channel, e.subscription_count);
}
res.sendStatus(200);
});팁
- 인원수 조회는 메시지 할당량을 쓰지 않습니다. 메시지로 세는 것은 발행한 이벤트뿐입니다. 그래도 페이지 조회마다 묻지 말고 HTTP API 결과를 서버에서 몇 초 캐시하세요.
- 웹훅은 늦게 오거나 두 번 올 수 있습니다. 기록과 알림에 쓰고, 지금 정확한 숫자가 필요하면 HTTP API를 읽으세요.
- 붐비는 공개 페이지에는 반올림한 숫자("1.2k 접속 중")를 보여 주세요. 덜 흔들리면서도 생동감이 있습니다.
- 인원수는 앱 단위입니다. 스테이징과 프로덕션 앱이 따로 있으면 각자 자신의 연결만 봅니다.
- 요금제가 제한하는 것은 동시 연결 수이지 인원수가 아닙니다. 요금제별 피크 한도는 요금을 보세요.