วิธีติดตั้ง OpenAnalytics บน VPS ด้วยตนเองแบบครบวงจร
เรียนรู้วิธีติดตั้ง OpenAnalytics บน VPS ของคุณเอง เตรียมทรัพยากรให้พร้อมด้วย RAM 4 GB พื้นที่ 25 GB พร้อมตั้งค่า DNS 4 รายการ และทำความเข้าใจโครงสร้าง ClickHouse กับ Postgres
สิ่งที่ต้องเตรียมก่อนเริ่มขั้นตอนแรก
สำหรับการ self-host OpenAnalytics คุณจำเป็นต้องมี Linux VPS ที่มี RAM ประมาณ 4 GB, พื้นที่ดิสก์ว่าง 25 GB, Docker พร้อมปลั๊กอิน Compose และระเบียน DNS 4 รายการที่ชี้มายังเซิร์ฟเวอร์ของคุณ นี่คือข้อเท็จจริงที่ตรงไปตรงมา และควรทราบก่อนที่จะเริ่มใช้คำสั่งแรก
Stack นี้ประกอบด้วยบริการแอปพลิเคชัน 6 รายการและที่เก็บข้อมูล 3 รายการ โดย Postgres ทำหน้าที่เป็น control plane สำหรับเก็บข้อมูลบัญชีผู้ใช้, เว็บไซต์, API keys และลิงก์แชร์ ส่วน ClickHouse ใช้เก็บข้อมูลเหตุการณ์ดิบและข้อมูลสรุปที่ dashboard อ่าน ส่วน Valkey จะถูกรัน 2 อินสแตนซ์ โดยอินสแตนซ์หนึ่งทำหน้าที่เป็นคิวเหตุการณ์แบบถาวร และอีกอินสแตนซ์หนึ่งทำหน้าที่เป็นแคชที่ระบบยอมรับการสูญหายได้ เนื่องจากทั้งสองงานนี้ต้องการนโยบายการลบข้อมูล (eviction policy) ที่แตกต่างกัน มีเพียงกระบวนการเดียวคือ query gateway เท่านั้นที่ได้รับอนุญาตให้อ่านข้อมูลจาก ClickHouse และมันจะตรวจสอบลายเซ็น Ed25519 บนทุก query envelope ก่อนที่จะดำเนินการ
หากสิ่งที่คุณต้องการคือไฟล์ binary เดียวและไฟล์ config เดียว นี่ไม่ใช่สิ่งที่คุณกำลังมองหา GoatCounter คือตัวเลือกแบบ single-binary ในกลุ่มนี้ ซึ่งประกอบด้วยไฟล์ executable ภาษา Go เพียงไฟล์เดียว และใช้ SQLite เป็นค่าเริ่มต้นโดยไม่มีฐานข้อมูลภายนอก Stack ที่หนักกว่านี้จะช่วยให้คุณได้ฟีเจอร์ funnel, web vitals, การระบุแหล่งที่มาของรายได้จากบัญชี Stripe ของคุณเอง และเซิร์ฟเวอร์ MCP (model context protocol) การเลือกเครื่องมือวิเคราะห์ข้อมูลแบบ self-hosted คือบทความที่เปรียบเทียบข้อดีข้อเสียเหล่านี้ คู่มือนี้ถือว่าคุณได้ตัดสินใจเลือกแล้ว
ชี้ระเบียน DNS สี่รายการไปยังเซิร์ฟเวอร์ก่อน
ต้องกำหนดให้ subdomain สี่รายการชี้ไปยัง public IP ของเซิร์ฟเวอร์ก่อนเริ่มดำเนินการใดๆ เนื่องจาก Caddy จะร้องขอใบรับรองจาก Let's Encrypt ในการเริ่มทำงานครั้งแรก หากชื่อโดเมนยังไม่ชี้มาที่เซิร์ฟเวอร์ การตรวจสอบความถูกต้อง (challenge) จะล้มเหลว
app.example.comใช้สำหรับ dashboardapi.example.comใช้สำหรับ API และ OAuth callbacksc.example.comใช้สำหรับ collector และ tracker scriptrt.example.comใช้สำหรับ realtime stream
ให้ใช้ระเบียน A จำนวนสี่รายการ หรือระเบียน A หนึ่งรายการและ CNAME อีกสามรายการชี้ไปยังระเบียนนั้น ตรวจสอบความถูกต้องด้วย dig +short app.example.com ก่อนดำเนินการต่อ ชื่อโดเมนที่คุณเพิ่งเพิ่มเข้าไปอาจยังถูกแคชเป็น NXDOMAIN อยู่ใน resolver ที่ Let's Encrypt ใช้งาน ดังนั้นหากการขอใบรับรองครั้งแรกล้มเหลว ให้รอสักครู่และตรวจสอบ log ของ Caddy การติดตั้งซ้ำไม่ได้ช่วยให้ DNS เผยแพร่ข้อมูลได้เร็วขึ้น
วิธีการ self-host OpenAnalytics ด้วย Docker Compose
ให้ตรวจสอบเวอร์ชันที่ระบุไว้ใน tagged release เนื่องจาก branch เริ่มต้นเป็นพื้นที่สำหรับการพัฒนา ส่วน release tag คือสิ่งที่ตรงกับ image ที่เผยแพร่อย่างเป็นทางการ คำสั่งด้านล่างนี้ถือว่ามีการติดตั้ง Docker และ Compose plugin เรียบร้อยแล้ว ซึ่งครอบคลุมอยู่ใน การรันบริการ Docker Compose บน VPS
git clone https://github.com/OpenLabs-so/openanalytics
cd openanalytics
git checkout "$(git tag -l 'v*' --sort=-v:refname | sed '/-/d' | head -1)"
cd infra/selfhost
./generate-secrets.sh --domain example.com --email you@example.com --with-geoip
docker compose pull && docker compose up -dการใช้ sed '/-/d' ในบรรทัด checkout จะช่วยคัดกรอง pre-release tag ออก เพื่อให้คุณได้เวอร์ชันเสถียรล่าสุดแทนที่จะเป็น release candidate ส่วน --with-geoip จะทำการดึงฐานข้อมูลเมืองจาก DB-IP ในระหว่างการสร้าง หากข้ามขั้นตอนนี้ไป เหตุการณ์ทุกอย่างจะมีค่าประเทศเป็น null ทำให้มุมมองทางภูมิศาสตร์ไม่แสดงข้อมูลใดๆ คุณสามารถเพิ่มข้อมูลนี้ในภายหลังได้โดยรัน infra/selfhost/geoip/fetch-dbip.sh, ตั้งค่า GEOIP_DB_PATH=/geoip/dbip-city-lite.mmdb ใน env/collector.env จากนั้นสร้าง collector ขึ้นมาใหม่ด้วย docker compose up -d --force-recreate collector ฐานข้อมูลดังกล่าวจะมีการอัปเดตทุกเดือน ดังนั้นควรทำซ้ำขั้นตอนการดึงข้อมูลเป็นประจำทุกเดือน มิฉะนั้นข้อมูลเมืองของคุณจะคลาดเคลื่อนไปจากความเป็นจริง
สำรองข้อมูลความลับที่สร้างขึ้นก่อนดำเนินการต่อ
ตัวสร้างจะเขียนข้อมูลออกมา 3 ส่วน .env เก็บชื่อโดเมนและอ้างอิงของ image ไว้ env/*.env เก็บไฟล์ความลับแยกตามบริการ และ docker-compose.override.yml เก็บกุญแจ Ed25519 จำนวน 3 คู่ในรูปแบบ YAML block scalars เนื่องจากไฟล์ PEM แบบหลายบรรทัดไม่สามารถอยู่ในไฟล์ env ได้ ข้อมูลทั้งหมดนี้ถูกกำหนดให้ git-ignored และไม่สามารถสร้างค่าเดิมขึ้นมาใหม่ได้
ให้คัดลอกไฟล์เหล่านั้นออกจากเครื่องทันที การสูญเสียแต่ละส่วนส่งผลกระทบเฉพาะตัวดังนี้:
- หากทำรหัสผ่านของ store หาย คุณจะถูกล็อกไม่ให้เข้าถึง Postgres และ ClickHouse ซึ่งจะรีเซ็ตได้จากภายในคอนเทนเนอร์เท่านั้น
- หากทำ
OA_CREDENTIAL_KEYRINGหาย ข้อมูลรับรองของบุคคลที่สามที่จัดเก็บไว้ทั้งหมดจะไม่สามารถกู้คืนได้ ส่งผลให้ผู้ที่เชื่อมต่อบัญชี Stripe ไว้ต้องทำการเชื่อมต่อใหม่ - หากทำ
ANONYMOUS_IDENTITY_SECRETหาย ข้อมูลระบุตัวตนของผู้เข้าชมจะถูกรีเซ็ตใหม่ทั้งหมด ผู้เข้าชมจากเมื่อวานจะถูกนับเป็นผู้เข้าชมใหม่ และจะเห็นรอยต่อของข้อมูลในแผนภูมิ - หากทำ
AUTH_SECRETหาย เซสชันทั้งหมดจะถูกยกเลิก ส่งผลให้ทุกคนต้องลงชื่อเข้าใช้ใหม่ - หากทำกุญแจส่วนตัวสำหรับลงนาม (signing private key) หาย คุณเพียงแค่ต้องหมุนเวียนกุญแจคู่ใหม่เท่านั้น โดยไม่มีข้อมูลใดสูญหาย
มีความลับสองรายการที่ต้องมีค่าไบต์เหมือนกันในสองไฟล์ ANONYMOUS_IDENTITY_SECRET ปรากฏอยู่ใน collector.env และ worker.env เนื่องจากตัวรวบรวม (collector) ทำหน้าที่คำนวณแฮชของผู้เข้าชมและตัวประมวลผล (worker) เป็นผู้เขียนข้อมูลนั้น ส่วน OA_CREDENTIAL_KEYRING ปรากฏอยู่ใน api.env และ worker.env ความลับรายการอื่นทั้งหมดถูกจำกัดขอบเขตไว้ที่บริการเดียวโดยเจตนา และหากบริการได้รับความลับที่ไม่ควรได้รับ บริการนั้นจะหยุดทำงานแทนที่จะเริ่มระบบ
เริ่มการทำงานของ stack และตรวจสอบสถานะ
grep OA_IMAGE .env
docker compose pull
docker compose up -d
docker compose logs -f migrate
docker compose psmigrate จะดำเนินการปรับใช้ schema ของ Postgres และ ClickHouse จากนั้นจึงหยุดทำงาน ดังนั้นสถานะที่ถูกต้องของคอนเทนเนอร์ migrate คือการหยุดทำงาน (stopped) ส่วน tracker-build จะคอมไพล์ oa.js ลงใน volume ที่ Caddy ให้บริการและหยุดทำงานเช่นกัน บริการอื่นๆ ทั้งหมดควรแสดงสถานะ healthy ใน docker compose ps หากบริการมีการรีสตาร์ทวนซ้ำ มักเกิดจากการตรวจสอบ environment ไม่ผ่าน โดย log จะแสดงรายการปัญหาทั้งหมดออกมาพร้อมกันแทนที่จะแสดงทีละรายการต่อการรีสตาร์ทหนึ่งครั้ง สาเหตุที่พบบ่อยสองประการคือ การปล่อยตัวแปรว่างไว้ซึ่งระบบจะปฏิเสธแทนที่จะถือว่าไม่ได้ตั้งค่า และการวาง secret ไว้ในไฟล์ service ผิดตำแหน่ง
บนสถาปัตยกรรม arm64 หรือเมื่อใช้งานจาก branch จะไม่มี image ที่เผยแพร่ไว้ คุณต้อง build เองในเครื่องด้วย docker compose up -d --build โฮสต์ที่มีหน่วยความจำ 4 GB จะเกิดปัญหาหน่วยความจำเต็มระหว่างการ build ให้เพิ่ม swap ก่อน ซึ่งจำเป็นต้องใช้เฉพาะในระหว่างการ build เท่านั้น:
fallocate -l 4G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstabกระบวนการ build ใช้เวลาประมาณ 10 นาที ส่วนการดึง image ใช้เวลาเพียงไม่กี่นาที ซึ่งเป็นเหตุผลว่าทำไมจึงต้องมี release image ไว้ใช้งาน
ลงทะเบียนบัญชีแรกทันที
เปิด https://app.example.com การติดตั้งที่ยังไม่มีใครเข้าใช้งานจะไม่แสดงหน้าจอลงชื่อเข้าใช้ แต่จะเสนอให้สร้างบัญชีแรก บัญชีดังกล่าวจะมีสิทธิ์ระดับสูงสุดอย่างถาวร และเป็นบัญชีเดียวที่สามารถเข้าถึงหน้าจอการตั้งค่าการติดตั้งได้ เมื่อบัญชีนี้ถูกสร้างขึ้น เส้นทางดังกล่าวจะตอบกลับด้วย 409 เพื่อป้องกันไม่ให้ผู้อื่นเข้ามาใช้งานต่อจากคุณ ให้ดำเนินการขั้นตอนนี้ทันทีที่ stack พร้อมใช้งาน อย่าปล่อยทิ้งไว้จนถึงสัปดาห์ถัดไป
การติดตั้งตัวติดตาม (tracker)
เพิ่มเว็บไซต์ในแดชบอร์ดแล้วระบบจะมอบแท็กให้ รูปแบบของแท็กนั้นตายตัว:
<script
async
src="https://c.example.com/oa.js"
data-key="YOUR_TRACKING_KEY"
data-collector="https://c.example.com"
></script>ให้นำไปวางไว้ในส่วน head ของหน้าเว็บ คีย์สำหรับการติดตาม (tracking key) ถูกออกแบบมาให้เป็นสาธารณะ จึงสามารถวางไว้ใน HTML ที่ใครก็สามารถอ่านได้ สคริปต์จะทำการติดตั้ง window.oa และการเรียกใช้งานเช่น oa("track", ...) จะถูกจัดคิวไว้โดย stub และจะถูกส่งออกทันทีที่ไฟล์โหลดเสร็จ ดังนั้นเหตุการณ์ที่กำหนดเอง (custom event) ซึ่งถูกเรียกใช้งานตั้งแต่ช่วงต้นจะไม่สูญหาย หากมีส่วนอื่นในหน้าเว็บใช้งาน window.oa อยู่แล้ว ตัวติดตามจะติดตั้งในชื่อ window.openanalytics แทน
จากนั้นให้ตรวจสอบเส้นทางทั้งหมดตั้งแต่ต้นจนจบ:
curl -s https://c.example.com/oa.js -o /dev/null -w '%{http_code} %{size_download}\n'
curl -s https://api.example.com/health | head -c 200
docker compose logs --tail=50 worker | grep -i batchคำสั่งแรกควรแสดงผลเป็น 200 และขนาดไฟล์ไม่กี่กิโลไบต์ ให้โหลดหน้าเว็บของคุณ จากนั้นตรวจสอบบรรทัด batch ใน log ของ worker ภายในไม่กี่วินาที ตัวรวบรวมข้อมูล (collector) จะตอบกลับด้วย 202 ทันทีที่ยอมรับเหตุการณ์ และ 202 หมายถึงเหตุการณ์ถูกจัดคิวไว้แต่ยังไม่ได้จัดเก็บ ตัว worker คือส่วนที่ย้ายเหตุการณ์เข้าไปยัง ClickHouse หากเหตุการณ์ถูกยอมรับแต่ไม่ปรากฏในแดชบอร์ด แสดงว่า worker กำลังติดขัด ซึ่งสามารถยืนยันได้หากความลึกของคิวใน Valkey เพิ่มขึ้นเรื่อยๆ สาเหตุที่พบบ่อยคือข้อมูลรับรอง (credentials) ของ ClickHouse ใน worker.env ไม่ถูกต้อง หรือขาดสิทธิ์การเข้าถึงตารางที่เพิ่งถูกเพิ่มเข้ามาจากการทำ migration
เก็บ collector ไว้เป็นสาธารณะและวาง dashboard ไว้หลังระบบยืนยันตัวตน
Caddy มาพร้อมกับไฟล์ compose และจัดการขอ certificate สำหรับชื่อโดเมนทั้ง 4 ชื่อด้วยตนเอง ดังนั้น path เริ่มต้นจึงไม่จำเป็นต้องตั้งค่า proxy เพิ่มเติม หากเซิร์ฟเวอร์ของคุณรัน nginx reverse proxy อยู่แล้ว ให้ใช้ infra/selfhost/nginx.conf.example ที่เตรียมไว้เป็นหน้าด่านแทน และคงการจัดการ header ตามเดิมไว้:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header CF-Connecting-IP "";
proxy_set_header True-Client-IP "";
proxy_set_header Fly-Client-IP "";Collector จะคำนวณ hash ของผู้เข้าชมรายวันจาก IP ของไคลเอนต์ ดังนั้นจึงต้องดึงที่อยู่ดังกล่าวจากการเชื่อมต่อโดยตรง ห้ามดึงจาก header เด็ดขาด การส่งผ่าน CF-Connecting-IP จาก hop ที่ไม่น่าเชื่อถือจะทำให้ผู้เรียกสามารถปลอมแปลงที่อยู่ใดก็ได้ ซึ่งจะส่งผลให้ข้อมูลตำแหน่งทางภูมิศาสตร์เสียหายและทำให้จำนวนผู้เข้าชมสูงเกินจริงในเวลาเดียวกัน
การเข้าถึงจะถูกแยกตามชื่อโฮสต์อย่างชัดเจน c. และ rt. ต้องสามารถเข้าถึงได้โดยผู้เข้าชมทุกคนจากทุกเว็บไซต์ที่คุณวัดผล ดังนั้นห้ามใส่ basic auth หรือ IP allowlist ไว้หน้าสองส่วนนี้เด็ดขาด ส่วน app. และ api. ควรเข้าถึงได้เฉพาะผู้ที่ล็อกอินแล้วเท่านั้น ระบบยืนยันตัวตนของแอปพลิเคชันเองจะเป็นตัวปกป้อง dashboard: การล็อกอินด้วยรหัสผ่านถูกเปิดใช้งานเป็นค่าเริ่มต้นผ่าน AUTH_PASSWORD_SIGNIN=enabled ใน env/api.env และปุ่ม Google หรือ GitHub จะปรากฏขึ้นก็ต่อเมื่อมีทั้ง client ID และ client secret สำหรับผู้ให้บริการนั้นๆ การใช้งาน magic links จำเป็นต้องมีระบบขนส่งอีเมล หากไม่มีระบบดังกล่าว API จะเขียนคำสั่งส่งอีเมลลงใน outbox เท่านั้น ทำให้ไม่มีการส่งออกและไม่เกิดข้อผิดพลาดใดๆ
การตั้งค่าหนึ่งอย่างจะเป็นตัวตัดสินว่า dashboard จะทำงานได้หรือไม่ AUTH_TRUSTED_ORIGINS ใน env/api.env ต้องตรงกับ dashboard origin อย่างแม่นยำ หากตั้งค่าผิดหรือขาดหายไป API จะไม่ส่ง header CORS (cross-origin resource sharing) ทำให้เบราว์เซอร์ปฏิเสธทุกการเรียกใช้งาน ส่งผลให้ dashboard แสดงผลได้เพียงโครงร่างแต่ไม่แสดงข้อมูล ในขณะที่ docker compose ps รายงานว่าทุกอย่างทำงานปกติ
ในระหว่างที่คุณตั้งค่า proxy ให้จัดการกับ traffic อัตโนมัติด้วย บอท crawl จะเข้าถึง collector เหมือนกับผู้ใช้งานทั่วไป และยอดการเข้าชมหน้าเว็บของบอทจะถูกบันทึกลงใน ClickHouse และรวมอยู่ในสถิติของคุณ การบล็อก AI crawlers ที่ระดับเซิร์ฟเวอร์ จะช่วยคัดกรองข้อมูลเหล่านี้ออกจากฐานข้อมูลก่อนที่จะทำให้คุณเสียทั้งความแม่นยำและพื้นที่จัดเก็บข้อมูล
ความหมายของ cookieless ในบริบทนี้และสิ่งที่คุณต้องแลก
ที่นี่ไม่มีการใช้คุกกี้ ตัวตนของผู้เข้าชมจะถูกเก็บเป็น salted hash โดยค่า salt จะเปลี่ยนทุกวัน และไม่มีการจัดเก็บ IP address แบบดิบ ข้อมูลตำแหน่งทางภูมิศาสตร์จะถูกประมวลผลภายในเครื่องโดยเทียบกับไฟล์ DB-IP บนดิสก์ของคุณเอง ดังนั้นข้อมูลการเข้าชมของผู้ใช้งานจะไม่ถูกส่งออกไปนอกเซิร์ฟเวอร์
สิ่งที่คุณได้รับคือการไม่มีตัวระบุตัวตนที่คงค้างอยู่บนอุปกรณ์ของผู้เข้าชม ซึ่งเป็นปัจจัยสำคัญที่ทำให้เครื่องมือติดตามต้องอยู่ภายใต้กฎระเบียบการให้ความยินยอมตาม ePrivacy ของสหภาพยุโรป ด้วยเหตุนี้ การตั้งค่าแบบรวมกลุ่ม (aggregate-only) เช่นนี้จึงมักใช้งานได้โดยไม่ต้องมีแบนเนอร์ขอความยินยอม อย่างไรก็ตาม GDPR ยังคงควบคุมข้อมูลที่คุณจัดเก็บและระยะเวลาในการจัดเก็บ ซึ่งการตัดสินใจทางกฎหมายขึ้นอยู่กับที่ปรึกษาของคุณ ไม่ใช่เอกสาร README นี้
สิ่งที่คุณต้องแลกคือความสามารถในการระบุตัวตนข้ามวัน การหมุนเวียนค่า salt หมายความว่าผู้ที่เข้าชมในวันจันทร์และกลับมาอีกครั้งในวันพุธจะถูกนับเป็นผู้เข้าชมสองคน ซึ่งเป็นการออกแบบที่ไม่มีวิธีแก้ไข จำนวนผู้เข้าชมที่ไม่ซ้ำกันรายวันนั้นมีความแม่นยำ แต่จำนวนผู้เข้าชมรายสัปดาห์และรายเดือนจะถูกคำนวณจากยอดรายวัน ซึ่งจะทำให้ตัวเลขสูงเกินจริง ดังนั้นตัวเลข "ผู้เข้าชมที่กลับมา" ในช่วงเวลาที่ยาวนานจึงไม่ได้วัดผลตามชื่อเรียกของมัน ส่วนเซสชันและเส้นทางการเข้าชมจะมีความน่าเชื่อถือภายในวันเดียวกันเท่านั้น การหมุนเวียน ANONYMOUS_IDENTITY_SECRET มีผลเช่นเดียวกับการเปลี่ยนวัน ดังนั้นให้ถือว่าการหมุนเวียนดังกล่าวเป็นการเปลี่ยนแปลงของข้อมูลมากกว่าการดูแลรักษาตามปกติ
ตัวเก็บข้อมูลจะเคารพคำสั่ง Do Not Track และ Global Privacy Control ซึ่งเป็นสัญญาณจากเบราว์เซอร์ที่แจ้งเว็บไซต์ไม่ให้ขายหรือแบ่งปันข้อมูลส่วนบุคคล แท็กสคริปต์มีสวิตช์ควบคุมในตัวสำหรับกรณีเดียวกัน ได้แก่ data-respect-gpc, data-respect-dnt และ data-require-consent ซึ่งจะระงับการเก็บข้อมูลทั้งหมดจนกว่าจะได้รับความยินยอม และจดจำคำตอบไว้ใน localStorage ภายใต้คีย์ oa.consent การตั้งค่า data-storage="none" จะเป็นการปิดการจัดเก็บข้อมูลบนเบราว์เซอร์โดยสมบูรณ์
เหตุใดดิสก์จึงเต็มหลังจากผ่านไปหกเดือน
นี่คือสาเหตุที่ทำให้เซิร์ฟเวอร์วิเคราะห์ข้อมูลแบบ self-hosted ล่ม และโดยปกติแล้วจำนวนเหตุการณ์ (events) ไม่ใช่สาเหตุหลัก
ให้เริ่มตรวจสอบจากอิมเมจ (images) แต่ละ release จะมีการเผยแพร่อิมเมจออกมาประมาณ 10 รายการ ซึ่งใช้พื้นที่บนดิสก์รวมกันประมาณ 13 GB การอัปเกรดจะดึงอิมเมจรุ่นใหม่มาก่อนที่จะลบของเก่าทิ้ง ดังนั้นในช่วงเวลาหนึ่งคุณจะถือครองอิมเมจไว้ถึงสองรุ่น ซึ่งนั่นคือส่วนใหญ่ของความต้องการพื้นที่ 25 GB ก่อนที่จะมีผู้เข้าชมหน้าเว็บแม้แต่รายเดียว
จากนั้นคือเรื่องของ snapshot โดย snapshot.sh จะหยุดการทำงานของ stack ทำการสำรองข้อมูล (archive) ทั้งสอง volume พร้อมกับ secret ทุกตัว แล้วจึงเริ่มการทำงานใหม่ การสำรองข้อมูลแบบ cold copy เป็นวิธีเดียวที่ปลอดภัยในกรณีนี้ เนื่องจาก ClickHouse จะรวมข้อมูล (merge) ในเบื้องหลัง และการสำรองข้อมูลที่ทำระหว่างการ merge จะทำให้ข้อมูลไม่สอดคล้องกัน upgrade.sh จะทำการสำรองข้อมูลโดยอัตโนมัติก่อนการอัปเกรดทุกครั้ง ดังนั้นไฟล์ archive จะสะสมอยู่บนดิสก์ลูกเดิมจนกว่าคุณจะกำหนดขีดจำกัดไว้
./snapshot.sh create --label before-something-risky
./snapshot.sh list
./snapshot.sh --keep 3บนโฮสต์ที่พื้นที่ใกล้เต็ม ให้ลบอิมเมจรุ่นก่อนหน้าทิ้งก่อนทำการอัปเกรด วิธีนี้ปลอดภัยในขณะที่ stack กำลังทำงานอยู่ เนื่องจากอิมเมจที่รองรับคอนเทนเนอร์ที่กำลังรันอยู่ยังคงถูกอ้างอิงใช้งานอยู่:
docker image prune -a -fจากนั้นคือเรื่องของตัวเหตุการณ์ (events) เอง ClickHouse บีบอัดข้อมูลแบบ columnar ได้อย่างมีประสิทธิภาพ ดังนั้นปริมาณข้อมูลเหตุการณ์ดิบจึงเติบโตช้ากว่าที่คนส่วนใหญ่คาดไว้ และตารางสรุปผล (rollup tables) ที่แดชบอร์ดอ่านนั้นมีขนาดเล็กเมื่อเทียบกับตารางข้อมูลดิบ ให้ใช้วิธีวัดผลแทนการคาดเดา:
docker system df -v
docker compose exec clickhouse df -h /var/lib/clickhouseสำหรับตัวเลขแยกตามตาราง ให้รันคำสั่งนี้ด้วยข้อมูลรับรอง (credentials) ของ ClickHouse ที่ตัวสร้าง (generator) เขียนไว้ภายใต้ infra/selfhost/env/:
SELECT table, formatReadableSize(sum(bytes_on_disk)) AS size, sum(rows) AS row_count
FROM system.parts
WHERE active
GROUP BY table
ORDER BY sum(bytes_on_disk) DESC;ให้จดบันทึกค่าดังกล่าวในสัปดาห์ที่หนึ่งและสัปดาห์ที่สี่ ข้อมูลสองจุดจะทำให้คุณทราบอัตราการเติบโต และอัตราการเติบโตจะบอกคุณว่าเมื่อใดที่ต้องขยายขนาด volume คู่มือการใช้งานแบบ self-hosting ไม่ได้ระบุถึงการตั้งค่า retention หรือ time-to-live สำหรับเหตุการณ์ดิบ ณ เดือนสิงหาคม 2026 ดังนั้นให้กำหนดขนาดดิสก์ตามอัตราที่คุณวัดได้จริง แทนที่จะคาดหวังว่าแถวข้อมูลเก่าจะหมดอายุไปเอง
มีกับดักเรื่องการลบข้อมูลหนึ่งอย่างที่ควรทราบก่อนที่จะเกิดปัญหา การลบไซต์หรือบัญชีจะนำงานไปเข้าคิวไว้ที่ worker และ worker นั้นจำเป็นต้องมีการตั้งค่า CLICKHOUSE_MAINTENANCE_USER และ CLICKHOUSE_MAINTENANCE_PASSWORD โดยต้องมีผู้ใช้ oa_maintenance ที่ตรงกันอยู่ใน ClickHouse หากไม่มีการตั้งค่าเหล่านี้ งานลบข้อมูลจะค้างอยู่ในคิวตลอดไป ไซต์จะหายไปจากแดชบอร์ด แต่ทุกแถวข้อมูลจะยังคงอยู่บนดิสก์ ทำให้คุณเห็นเหมือนว่ามีการล้างข้อมูลแล้ว แต่พื้นที่จัดเก็บไม่ได้ถูกคืนกลับมาเลย
การอัปเกรดและต้นทุนสามประการ
git fetch --tags
git checkout "$(git tag -l 'v*' --sort=-v:refname | sed '/-/d' | head -1)"
cd infra/selfhost
./upgrade.shupgrade.sh จะแสดงต้นทุนสามประการก่อนเริ่มดำเนินการ ช่วงเวลาที่ระบบหยุดทำงาน (Downtime) เป็นเรื่องจริง: เหตุการณ์ที่เกิดขึ้นในขณะที่ตัวรวบรวมข้อมูล (collector) หยุดทำงานจะสูญหายไป เนื่องจากตัวติดตาม (tracker) ไม่มีการลองส่งข้อมูลซ้ำ (retry) การย้อนกลับ (rollback) จะทำให้ข้อมูลสูญหาย เนื่องจาก rollback.sh --to backups/<snapshot> จะแทนที่ที่เก็บข้อมูลทั้งสองแห่งทั้งหมดและทิ้งทุกแถวที่ถูกเขียนขึ้นหลังจากที่ snapshot นั้นถูกสร้างขึ้น ต้นทุนประการที่สามคือพื้นที่ดิสก์ ซึ่งก็คือกลุ่มไฟล์ snapshot ที่อธิบายไว้ข้างต้น
กฎการรีสตาร์ทสองข้อที่มักทำผิดพลาดได้ง่าย ประการแรก ให้เริ่มการทำงานของ query gateway ก่อน API เนื่องจาก API เวอร์ชันใหม่จะส่ง query fields ที่ gateway เวอร์ชันเก่าไม่รองรับ ประการที่สอง ClickHouse จำเป็นต้องใช้การสร้างคอนเทนเนอร์ใหม่ (recreate) แทนการรีสตาร์ท เนื่องจาก docker compose restart จะนำ environment เดิมของคอนเทนเนอร์กลับมาใช้ใหม่และเพิกเฉยต่อการแก้ไขของคุณโดยไม่มีการแจ้งเตือน:
docker compose up -d --force-recreate clickhouseแดชบอร์ดก็มีกับดักในลักษณะเดียวกัน ต้นทาง NEXT_PUBLIC_* ทั้งสามแห่งใน env/web.env จะถูกคอมไพล์รวมเข้าไปใน browser bundle และถูกแทนที่เมื่อคอนเทนเนอร์เริ่มทำงาน ดังนั้นแดชบอร์ดที่เรียกใช้ hostname ผิดจะต้องแก้ไขด้วย docker compose up -d --force-recreate web เท่านั้น ไม่ใช่ด้วย restart บันทึกการทำงาน (log) ของ web container จะแสดงต้นทางที่ระบบใช้ในการเริ่มทำงาน ซึ่งเป็นวิธีที่รวดเร็วที่สุดในการยืนยันว่าการแก้ไขมีผลแล้ว
หาก ClickHouse ปฏิเสธที่จะเริ่มทำงานหลังจากแก้ไขไฟล์ config ให้ตรวจสอบบรรทัดแรกของ log บรรทัดที่ขึ้นต้นด้วย oa-entrypoint: หมายถึง entrypoint ปฏิเสธค่าที่คุณตั้งไว้ หากเป็นข้อความอื่น มักหมายความว่าไฟล์ config มีรูปแบบ XML ที่ไม่ถูกต้อง ซึ่งสาเหตุที่พบบ่อยที่สุดคือการใช้เครื่องหมายขีดกลางสองตัว (double hyphen) ภายในคอมเมนต์ของ XML ซึ่งเป็นสิ่งที่ไม่อนุญาตให้ทำ
AGPL-3.0 และชื่อผลิตภัณฑ์
ซอฟต์แวร์นี้อยู่ภายใต้สัญญาอนุญาต AGPL-3.0 การเรียกใช้งานซอฟต์แวร์โดยไม่มีการแก้ไขสำหรับเว็บไซต์ของคุณเองนั้น ไม่ก่อให้เกิดภาระผูกพันในการเผยแพร่ซอร์สโค้ดแต่อย่างใด ภาระผูกพันจะเริ่มขึ้นเมื่อคุณแก้ไขซอร์สโค้ดและเรียกใช้งานเวอร์ชันที่แก้ไขนั้นในฐานะบริการเครือข่าย โดยสัญญาอนุญาตกำหนดให้คุณต้องเสนอซอร์สโค้ดที่แก้ไขแล้วแก่ผู้ใช้งานบริการนั้น ซึ่งครอบคลุมถึงการให้แดชบอร์ดแก่ลูกค้าบนอินสแตนซ์ของคุณ และการรวมซอฟต์แวร์เข้าเป็นส่วนหนึ่งของผลิตภัณฑ์ที่คุณจำหน่าย การเก็บการเปลี่ยนแปลงของคุณไว้ใน fork แบบสาธารณะถือว่าปฏิบัติตามข้อกำหนดนี้โดยไม่ต้องมีขั้นตอนเพิ่มเติม
ในส่วนของแบรนด์นั้นแยกออกจากตัวซอร์สโค้ด ชื่อ "OpenAnalytics" และโดเมนที่โฮสต์โครงการนั้นใช้เพื่อระบุอินสแตนซ์ที่ผู้พัฒนาเป็นผู้ดูแลเท่านั้น และไม่ได้เป็นส่วนหนึ่งของการอนุญาตให้ใช้งานตามสัญญา การติดตั้งใช้งานของคุณเป็นการรันซอฟต์แวร์โดยไม่ได้ใช้แบรนด์ดังกล่าว ดังนั้นคุณควรตั้งชื่อบริการของคุณเองก่อนที่จะนำไปให้บริการแก่ลูกค้าที่ชำระเงิน
FAQ
ฉันสามารถรัน OpenAnalytics บน VPS ขนาด 1 GB ได้หรือไม่?
ไม่ได้ โครงการนี้ต้องการ RAM ประมาณ 4 GB และพื้นที่ดิสก์ว่าง 25 GB เนื่องจากหนึ่งการติดตั้งต้องรันบริการแอปพลิเคชัน 6 รายการควบคู่ไปกับ Postgres, ClickHouse และอินสแตนซ์ Valkey อีก 2 รายการ เฉพาะ ClickHouse เพียงอย่างเดียวก็ไม่ใช่กระบวนการขนาดเล็ก บนเครื่องขนาด 1 GB คอนเทนเนอร์จะเริ่มทำงานแล้ว kernel out-of-memory killer จะเข้ามาจัดการหนึ่งในนั้น ซึ่งมักจะเป็น ClickHouse หากแผน 1 GB เป็นข้อจำกัดที่เลี่ยงไม่ได้ ให้ใช้เครื่องมือที่เป็น single-binary เช่น GoatCounter ซึ่งรันบน SQLite โดยไม่ต้องมีฐานข้อมูลภายนอก
ฉันจำเป็นต้องมีแบนเนอร์คุกกี้สำหรับ OpenAnalytics หรือไม่?
นั่นเป็นคำถามสำหรับทนายความของคุณ แต่ข้อเท็จจริงทางเทคนิคเป็นประโยชน์ต่อคุณ ไม่มีการใช้คุกกี้ ตัวตนของผู้เข้าชมคือ salted hash ที่หมุนเวียนทุกวัน และไม่มีการจัดเก็บ IP address แบบดิบ ดังนั้นจึงไม่มีการเขียนข้อมูลถาวรเพื่อระบุตัวตนของผู้เข้าชม GDPR ยังคงควบคุมสิ่งที่คุณจัดเก็บและระยะเวลาที่คุณเก็บข้อมูลนั้น หากคุณต้องการให้มีการรวบรวมข้อมูลโดยต้องได้รับความยินยอมอย่างชัดเจน ให้ตั้งค่า data-require-consent บนแท็กสคริปต์ ตัวติดตามจะไม่รวบรวมข้อมูลใดๆ จนกว่าจะได้รับความยินยอมและจะเก็บคำตอบไว้ใน localStorage ภายใต้ oa.consent
ทำไมเหตุการณ์ต่างๆ ถึงส่งค่ากลับเป็น 202 แต่ไม่ปรากฏในแดชบอร์ด?
202 หมายความว่าตัวรวบรวมข้อมูลยอมรับและนำเหตุการณ์เข้าคิวแล้ว ไม่ได้หมายความว่าได้จัดเก็บข้อมูลนั้นลงฐานข้อมูลแล้ว ตัว worker จะดึงข้อมูลจากคิวไปใส่ใน ClickHouse ดังนั้นหากแดชบอร์ดว่างเปล่าในขณะที่คำขอสำเร็จ ปัญหาจะอยู่ที่ตัว worker ให้อ่าน docker compose logs --tail=50 worker และตรวจสอบความลึกของคิวใน Valkey หากคิวเพิ่มขึ้นเรื่อยๆ แสดงว่า worker ถูกบล็อก สาเหตุที่พบบ่อยคือข้อมูลรับรอง ClickHouse ใน worker.env ไม่ถูกต้อง หรือไม่มีสิทธิ์เข้าถึงตารางที่การย้ายข้อมูลล่าสุดสร้างขึ้น
ทำไมแดชบอร์ดถึงว่างเปล่าในขณะที่คอนเทนเนอร์ทุกตัวมีสถานะปกติ?
ให้ตรวจสอบ AUTH_TRUSTED_ORIGINS ใน env/api.env ก่อน ค่านี้ต้องตรงกับ origin ของแดชบอร์ดอย่างแม่นยำ หากไม่ตรงกัน API จะไม่ส่ง CORS headers ออกมา ทำให้เบราว์เซอร์ปฏิเสธทุกการเรียกใช้งาน คุณจึงเห็นเลย์เอาต์ทำงานปกติแต่ไม่มีข้อมูล สิ่งที่สองที่ต้องตรวจสอบคือค่า NEXT_PUBLIC_* ทั้งสามค่าใน env/web.env ซึ่งจะถูกแทนที่เมื่อคอนเทนเนอร์เว็บเริ่มทำงาน การแก้ไขค่าเหล่านี้จำเป็นต้องใช้ docker compose up -d --force-recreate web เนื่องจากการรีสตาร์ทแบบปกติจะยังคงใช้ค่าเดิมอยู่
AGPL-3.0 ห้ามไม่ให้ฉันนำบริการนี้ไปเสนอให้ลูกค้าหรือไม่?
ไม่ห้าม โดยมีเงื่อนไขเดียวคือ หากคุณรันโค้ดโดยไม่มีการแก้ไข คุณไม่ต้องรับผิดชอบต่อใคร แต่หากคุณแก้ไขโค้ดและรันเวอร์ชันที่แก้ไขนั้นเป็นบริการให้ผู้อื่นใช้งาน คุณต้องเสนอซอร์สโค้ดที่แก้ไขแล้วให้ผู้ใช้เหล่านั้น ซึ่งการทำ public fork สามารถตอบโจทย์นี้ได้ นอกจากนี้ ชื่อ "OpenAnalytics" ไม่ได้รับอนุญาตให้ใช้ร่วมกับโค้ด ดังนั้นสิ่งใดก็ตามที่คุณนำไปขายจำเป็นต้องใช้ชื่อของตนเอง