この
v1.7.0 で
リリースしてから
- 初期表示が
遅い - 1 人の
ユーザが 短時間に PV を 増やせる (重複計測)
上記の
使うのか
なぜ Redis をまず 1 つ目の
Upstash は
2 つ目の
以上の
法
実装方 セットアップ
Upstash のまずは.env
ファイルに
UPSTASH_REDIS_REST_URL=UPSTASH_REDIS_REST_TOKEN=
次に
pnpm add @upstash/redis
最後にfromEnv
を
import { Redis } from '@upstash/redis';
export const redis = Redis.fromEnv();
作成
API の当サイトは
まずはincr
コマンドを
import type { TRPCRouterRecord } from '@trpc/server';import { TRPCError } from '@trpc/server';import { z } from 'zod';
import { env } from '../../env';import { publicProcedure } from '../trpc';import { getIpHash, redis } from '../utils';
export const pageViewRouter = { incrementViews: publicProcedure.input(z.object({ slug: z.string() })).mutation(async ({ ctx, input: { slug } }) => { if (!ctx.ip) { throw new TRPCError({ code: 'BAD_REQUEST' }); }
const ip = ctx.ip; const ipHash = await getIpHash(ip);
const isNew = await redis.set(['deduplicate', ipHash, slug].join(':'), true, { nx: true, ex: 24 * 60 * 60, });
if (!isNew) { return 'Deduplicated'; }
await redis.incr(['pageviews', env.NODE_ENV, slug].join(':'));
return 'Incremented'; }),} satisfies TRPCRouterRecord;
お問いnx: true
すでに'Deduplicated'
を'Incremented'
をincrementViews
の'Deduplicated' | 'Incremented'
の
呼び出す
クライアント側からPV カウントを
import type { TRPCRouterRecord } from '@trpc/server';import { z } from 'zod';
import { env } from '../../env';import { publicProcedure } from '../trpc';import { redis } from '../utils';
export const pageViewRouter = { bySlug: publicProcedure .input(z.object({ slug: z.string() })) .query(async ({ input: { slug } }) => (await redis.get<number>(['pageviews', env.NODE_ENV, slug].join(':'))) ?? 0),} satisfies TRPCRouterRecord;
セットした
'use client';
import * as React from 'react';
import { Skeleton } from '@kkhys/ui';
import { api } from '#/lib/trpc/react';
export const ViewCounter = ({ slug }: { slug: string }) => { const utils = api.useUtils();
const { mutate } = api.pageView.incrementViews.useMutation({ onSuccess: async (status) => { if (status === 'Incremented') { await utils.pageView.bySlug.invalidate({ slug }); } }, });
const { data } = api.pageView.bySlug.useQuery({ slug }, { staleTime: 60 * 1000 });
React.useEffect(() => mutate({ slug }), [mutate, slug]);
if (!data) return <ViewCounterSkeleton />;
return <p className='font-sans text-sm text-muted-foreground'>{data.toLocaleString()} views</p>;};
export const ViewCounterSkeleton = () => <Skeleton className='h-4 w-14' />;
ポイントは
まず 1 つ目はuseMutation
にonSuccess
)、onSuccess
のincrementViews
のIncremented
の
2 つ目のuseQuery
にstaleTime
をstaleTime
を
移行
データベースの今までは
Next.js に
import type { TRPCRouterRecord } from '@trpc/server';
import { publicProcedure } from '../trpc';import { redis } from '../utils';
export const pageViewRouter = { import: publicProcedure.mutation(async ({ ctx }) => { const posts = await ctx.db.query.posts.findMany(); posts.forEach((post) => void redis.setnx(['pageviews', 'production', post.slug].join(':'), post.views)); }),} satisfies TRPCRouterRecord;
本番用のforEach
で
冪等性setnx
を
念の
まずは
SELECT * FROM me_post ORDER BY slug;
+-------+-----+--------------------------+--------------------------+|slug |views|created_at |updated_at |+-------+-----+--------------------------+--------------------------+|p128uug|47 |2024-07-07 06:05:55.569318|2024-07-27 00:10:42.336000||p143t9d|8 |2024-07-27 07:44:32.707432|2024-07-27 08:21:13.394000||p15e6x7|105 |2024-05-19 12:40:25.512419|2024-07-27 00:10:11.409000||p164vu8|45 |2024-06-15 13:50:02.707545|2024-07-27 00:10:30.951000||p16ceda|38 |2024-04-07 04:04:19.174891|2024-07-27 08:01:10.329000||p16vfnq|59 |2024-04-07 04:04:31.853837|2024-07-27 03:29:25.920000||p18vcqd|43 |2024-04-07 04:04:33.979543|2024-07-27 03:28:57.797000||p1a95jw|118 |2024-05-15 14:00:43.888764|2024-07-22 04:34:23.322000||p1c8jpk|58 |2024-06-09 15:00:00.573275|2024-07-27 00:10:16.745000||p1e0lpm|37 |2024-04-07 04:04:58.937393|2024-07-27 03:29:37.042000||p1eemm6|64 |2024-04-07 04:04:37.635623|2024-07-27 06:07:07.161000||p1fw2ts|54 |2024-04-07 04:04:39.854620|2024-07-27 03:03:01.710000||p1g6z2d|26 |2024-04-07 04:04:25.338530|2024-07-07 03:52:33.672000||p1gvayx|48 |2024-06-20 14:18:37.999824|2024-07-27 00:10:39.531000||p1kc29z|32 |2024-07-14 10:23:41.045142|2024-07-26 12:58:25.458000||p1kqv7s|48 |2024-07-12 10:42:57.077724|2024-07-24 15:32:21.075000||p1n03k6|58 |2024-06-10 14:42:21.595135|2024-07-27 03:17:41.104000||p1r60de|157 |2024-04-07 04:03:58.912900|2024-07-27 08:16:17.285000||p1rklfz|31 |2024-04-07 04:04:47.664132|2024-07-24 14:27:28.827000||p1srf75|55 |2024-04-24 15:19:48.949461|2024-07-06 08:29:28.758000||p1t6el8|83 |2024-06-16 13:03:18.070267|2024-07-27 03:15:35.024000||p1ua4wh|40 |2024-05-14 15:17:23.575571|2024-07-25 10:40:58.893000||p1uchql|203 |2024-05-18 06:53:17.430213|2024-07-27 00:09:55.951000||p1v00e8|136 |2024-04-20 13:28:10.858223|2024-07-27 03:21:25.136000||p1v9jvx|72 |2024-04-23 15:11:34.886525|2024-07-27 09:19:21.205000||p1y4nft|52 |2024-04-07 04:04:42.417156|2024-07-22 04:33:42.593000||p1ys5j8|93 |2024-04-07 04:04:03.583977|2024-07-27 03:28:49.794000|+-------+-----+--------------------------+--------------------------+
次に
redis-cli --tls -u redis://default:password@hostname:port "pageviews:production:*" | sort -n | while read key; do echo "$key: $(redis-cli --tls -u redis://default:password@hostname:port get $key)"; done
pageviews:production:p128uug: 47pageviews:production:p143t9d: 8pageviews:production:p15e6x7: 105pageviews:production:p164vu8: 45pageviews:production:p16ceda: 38pageviews:production:p16vfnq: 59pageviews:production:p18vcqd: 43pageviews:production:p1a95jw: 118pageviews:production:p1c8jpk: 58pageviews:production:p1e0lpm: 37pageviews:production:p1eemm6: 64pageviews:production:p1fw2ts: 54pageviews:production:p1g6z2d: 26pageviews:production:p1gvayx: 48pageviews:production:p1kc29z: 32pageviews:production:p1kqv7s: 48pageviews:production:p1n03k6: 58pageviews:production:p1r60de: 157pageviews:production:p1rklfz: 31pageviews:production:p1srf75: 55pageviews:production:p1t6el8: 83pageviews:production:p1ua4wh: 40pageviews:production:p1uchql: 203pageviews:production:p1v00e8: 136pageviews:production:p1v9jvx: 72pageviews:production:p1y4nft: 52pageviews:production:p1ys5j8: 93
PV が
バックアップ
Upstash は

ごに
さいUpstash を
PV を