canonical / pgbouncer-operator Goto Github PK
View Code? Open in Web Editor NEWA charmed operator for running PgBouncer on virtual machines.
Home Page: https://charmhub.io/pgbouncer?channel=1/stable
License: Apache License 2.0
A charmed operator for running PgBouncer on virtual machines.
Home Page: https://charmhub.io/pgbouncer?channel=1/stable
License: Apache License 2.0
Hi!
Previously, I have been told by Canonical team, that pgbouncer-operator is following mysql-router concept with Unix socket propagation to the client application. But i did research and found that it doesn't work that way: instead, it uses TCP port for both exposed and not exposed relations (it is code snippet, but everything were double-checked in real tests):
exposed = bool(
self.database_provides.fetch_relation_field(relation.id, "external-node-connectivity")
)
if exposed:
rw_endpoint = f"{self.charm.leader_ip}:{self.charm.config['listen_port']}"
else:
rw_endpoint = f"localhost:{self.charm.config['listen_port']}"
Mysql-router, on the other hand, really propagates single Unix socket. Issue lays in that pgbouncer-operator was made as subordinate charm only with assumption, that it will work with similar concept as mysql-router.
Also, there is no mentions of Unix socket in application and relation specification documentations - i think it should be.
Pgbouncer creates N processes (for each CPU core / thread), and each process has it's own TCP port and Unix socket. In the TCP case, all the ports are combined by so_reuseport, which also includes load balancing. But for the Unix socket, there is no such feature to combine them all, so basically there is N different Unix sockets, and in case these all sockets will be reported to the client application, there wouldn't be any automatic load balancing (and therefore multithreading) - it will be only on the client application responsibility.
1. Install postgresql `14/stable`, pgbouncer `1/edge` or `1/stable` with the default settings and relate them with a single Juju Terraform run.
2. Install maas-region latest/edge charm and relate it to pgbouncer.
The maas-region charm receives the connection details through the dabatase
relation and tries to run maas init region+rack --database-uri postgres://relation_id_9:HZjrAAmo5U0i37DVwUEba7FB@localhost:6432/maas_region_db
. Details are coming through the relation. MAAS is initialized successfully.
MAAS fails to initialize since when it receives the connection details and tries to authenticate a refusal error occurs (see logs).
Operating system: Ubuntu 22.04.4 LTS
Juju CLI: 3.4.3-genericlinux-amd64
Juju agent: 3.4.3
postgresql charm revision: 363
pgbouncer charm revision: 203
LXD: 5.0.3
2024-06-10 12:40:01 INFO juju.worker.uniter.operation runhook.go:186 ran "api-relation-changed" hook (via hook dispatching script: dispatch)
2024-06-10 12:40:06 INFO juju.worker.uniter.operation runhook.go:186 ran "maas-db-relation-joined" hook (via hook dispatching script: dispatch)
2024-06-10 12:40:09 INFO juju.worker.uniter.operation runhook.go:186 ran "maas-db-relation-changed" hook (via hook dispatching script: dispatch)
2024-06-10 12:40:17 INFO juju.worker.uniter.operation runhook.go:186 ran "maas-db-relation-changed" hook (via hook dispatching script: dispatch)
2024-06-10 12:40:22 INFO unit.maas-region/0.juju-log server.go:325 maas-db:9: endpoints changed on 2024-06-10 12:40:22.992699
2024-06-10 12:40:23 INFO unit.maas-region/0.juju-log server.go:325 maas-db:9: MAAS database endpoints have been changed to: localhost:6432
2024-06-10 12:40:23 INFO juju.worker.uniter.operation runhook.go:186 ran "maas-db-relation-changed" hook (via hook dispatching script: dispatch)
2024-06-10 12:40:25 INFO unit.maas-region/0.juju-log server.go:325 maas-db:9: database created at 2024-06-10 12:40:25.993849
2024-06-10 12:40:26 INFO unit.maas-region/0.juju-log server.go:325 maas-db:9: MAAS database credentials received for user 'relation_id_9'
2024-06-10 12:40:26 INFO unit.maas-region/0.juju-log server.go:325 maas-db:9: DSN: postgres://relation_id_9:HZjrAAmo5U0i37DVwUEba7FB@localhost:6432/maas_region_db
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 Traceback (most recent call last):
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 File "/snap/maas/35359/usr/lib/python3/dist-packages/django/db/backends/base/base.py", line 219, in ensure_connection
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 self.connect()
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 File "/snap/maas/35359/usr/lib/python3/dist-packages/django/utils/asyncio.py", line 33, in inner
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 return func(*args, **kwargs)
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 File "/snap/maas/35359/usr/lib/python3/dist-packages/django/db/backends/base/base.py", line 200, in connect
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 self.connection = self.get_new_connection(conn_params)
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 File "/snap/maas/35359/usr/lib/python3/dist-packages/django/utils/asyncio.py", line 33, in inner
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 return func(*args, **kwargs)
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 File "/snap/maas/35359/usr/lib/python3/dist-packages/django/db/backends/postgresql/base.py", line 187, in get_new_connection
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 connection = Database.connect(**conn_params)
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 File "/snap/maas/35359/usr/lib/python3/dist-packages/psycopg2/__init__.py", line 122, in connect
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 psycopg2.OperationalError: connection to server at "localhost" (::1), port 6432 failed: Connection refused
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 Is the server running on that host and accepting TCP/IP connections?
2024-06-10 12:40:28 WARNING unit.maas-region/0.maas-db-relation-changed logger.go:60 connection to server at "localhost" (127.0.0.1), port 6432 failed: FATAL: password authentication failed
After 5-10 minutes, if operation is manually performed, the connection is working and MAAS is initialized
Hello,
I'm trying to build juju PostgreSQL cluster with 3 nodes and PGBouncer+Data-integrator available via single VIP address.
I found that data-integrator supports only [email protected] versions:
data-integrator available releases are:
channel "latest/stable": available bases are: [email protected]
channel "latest/candidate": available bases are: [email protected]
channel "latest/beta": available bases are: [email protected]
channel "latest/edge": available bases are: [email protected]
This OS version (22.04) supports only 1/* versions of PGBouncer, but this versions does not supports ha-cluster-vip/dns parameters on configuration.
I would like to know, is there any way to build such cluster with VIP, or it is impossible and we have to wait [email protected] support for pgbouncer latest/stable?
(here https://charmhub.io/pgbouncer/integrations?channel=1/edge integration with HACluster announced, but looks like does not support)
juju integrate hacluster pgbouncer
integrated successfully
ERROR ambiguous relation: "hacluster pgbouncer" could refer to "hacluster:juju-info pgbouncer:juju-info"; "hacluster:peer-availability pgbouncer:juju-info"; "pgbouncer:juju-info hacluster:juju-info"
Operating system: Ubuntu 22.04.4 LTS
Juju CLI: 3.4.2-genericlinux-amd64
ubuntu@jujunode1-tst:~$ juju status
Model Controller Cloud/Region Version SLA Timestamp
postgresql-maas lxd-remote-default lxd-remote/default 3.4.2 unsupported 09:52:39Z
App Version Status Scale Charm Channel Rev Exposed Message
data-integrator active 3 data-integrator latest/stable 19 no
hacluster unknown 0 hacluster 2.4/stable 131 no
pgbouncer 1.21.0 active 3 pgbouncer 1/edge 142 yes
postgresql 14.11 active 3 postgresql 14/edge 393 yes
Unit Workload Agent Machine Public address Ports Message
data-integrator/0 active idle 3 240.9.0.161
pgbouncer/0 active idle 240.9.0.161 6432/tcp
data-integrator/1* active idle 4 240.81.0.43
pgbouncer/3* active idle 240.81.0.43 6432/tcp
data-integrator/2 active idle 5 240.7.0.43
pgbouncer/2 active idle 240.7.0.43 6432/tcp
postgresql/0* active idle 0 240.81.0.166 5432/tcp
postgresql/1 active idle 1 240.9.0.212 5432/tcp
postgresql/2 active idle 2 240.7.0.203 5432/tcp Primary
Machine State Address Inst id Base AZ Message
0 started 240.81.0.166 juju-f7af5d-0 [email protected] Running
1 started 240.9.0.212 juju-f7af5d-1 [email protected] Running
2 started 240.7.0.203 juju-f7af5d-2 [email protected] Running
3 started 240.9.0.161 juju-f7af5d-3 [email protected] Running
4 started 240.81.0.43 juju-f7af5d-4 [email protected] Running
5 started 240.7.0.43 juju-f7af5d-5 [email protected] Running
postgresql charm revision: 393
pgbouncer charm revision: 142
LXD: 5.21.1 LTS
Currently the Pgbouncer VM charm is expected to be subordinated on one of the client interfaces, but needs to also allow subordination based on the juju-info
interface. If the user subordinates based on juju-info
by mistake the charm will go into active
state despite not serving any client. The charm should detect this case and block/unblock itself based on presence of a client connection for a better UX.
Following on the tutorial here: https://discourse.charmhub.io/t/pgbouncer-tutorial-deploy-pgbouncer/12290/1 I can't relate the data-integrator with the pgboucher charm:
juju integrate data-integrator pgbouncer --debug --verbose
09:40:55 INFO juju.cmd supercommand.go:56 running juju [3.4.2 a80becbb4da5985fa53c63824a4bd809e9d03954 gc go1.21.8]
09:40:55 DEBUG juju.cmd supercommand.go:57 args: []string{"/snap/juju/26968/bin/juju", "integrate", "data-integrator", "pgbouncer", "--debug", "--verbose"}
09:40:55 INFO juju.juju api.go:86 connecting to API addresses: [192.168.2.32:17070 10.100.2.92:17070 253.2.92.1:17070]
09:40:55 DEBUG juju.api apiclient.go:1172 successfully dialed "wss://192.168.2.32:17070/model/0f46e0dd-74e1-4e91-8943-860640f2e2d1/api"
09:40:55 INFO juju.api apiclient.go:707 connection established to "wss://192.168.2.32:17070/model/0f46e0dd-74e1-4e91-8943-860640f2e2d1/api"
09:40:55 DEBUG juju.api monitor.go:35 RPC connection died
ERROR cannot add relation "data-integrator:postgresql pgbouncer:database": establishing a new relation for data-integrator:postgresql would exceed its maximum relation limit of 1 (quota limit exceeded)
09:40:55 DEBUG cmd supercommand.go:549 error stack:
cannot add relation "data-integrator:postgresql pgbouncer:database": establishing a new relation for data-integrator:postgresql would exceed its maximum relation limit of 1 (quota limit exceeded)
github.com/juju/juju/rpc.(*Conn).Call:178:
Be able to integrate the charms
Integration fails
Model Controller Cloud/Region Version SLA Timestamp
default openstacklab-regionone openstacklab/RegionOne 3.4.0 unsupported 09:41:48+03:00
App Version Status Scale Charm Channel Rev Exposed Message
data-integrator active 1 data-integrator stable 19 no
pgbouncer unknown 0 pgbouncer 1/stable 88 no
postgresql 14.10 active 3 postgresql 14/stable 363 no
Unit Workload Agent Machine Public address Ports Message
data-integrator/0* active idle 5 10.100.2.109
postgresql/0 active idle 2 10.100.2.255 5432/tcp
postgresql/1 active idle 3 10.100.3.84 5432/tcp
postgresql/2* active idle 4 10.100.1.186 5432/tcp Primary
Machine State Address Inst id Base AZ Message
2 started 10.100.2.255 5166fd77-b663-47c4-aad3-961773d26642 [email protected] nova ACTIVE
3 started 10.100.3.84 c0e19107-7eb0-4785-9793-7724f80583ab [email protected] nova ACTIVE
4 started 10.100.1.186 5754ca24-3a65-4882-a36d-903b29a4abea [email protected] nova ACTIVE
5 started 10.100.2.109 04bea12f-d1d7-4b45-a2e8-c000264e0c95 [email protected] nova ACTIVE
Integration provider Requirer Interface Type Message
data-integrator:data-integrator-peers data-integrator:data-integrator-peers data-integrator-peers peer
pgbouncer:pgb-peers pgbouncer:pgb-peers pgb_peers peer joining
pgbouncer:upgrade pgbouncer:upgrade upgrade peer joining
postgresql:database data-integrator:postgresql postgresql_client regular
postgresql:database pgbouncer:backend-database postgresql_client regular
postgresql:database-peers postgresql:database-peers postgresql_peers peer
postgresql:restart postgresql:restart rolling_op peer
postgresql:upgrade postgresql:upgrade upgrade peer
Operating system: jammy
Juju CLI: 3.4.2-genericlinux-amd64
Juju agent: 3.4.0
postgresql charm revision: 14/stable 363
pgbouncer charm revision: 1/stable 88
LXD: N/A, runs on openstack
Juju debug log: N/A; client error
Upgrade when switching snaps is not sufficiently stable:
We should investigate and fix before promoting to stable.
Hi! I've discovered some weird behavior with read-only-endpoints.
Model:
Pgbouncer always includes read-only channel in its config upon different database name (main database name + _readonly
suffix) if there are postgresql replicas. (example below is on data-integrator/1)
[databases]
asd = host=10.88.216.205 dbname=asd port=5432 auth_user=pgbouncer_auth_relation_7
asd_readonly = host=10.88.216.56,10.88.216.129 dbname=asd port=5432 auth_user=pgbouncer_auth_relation_7
But pgbouncer does not notify client application about read-only-endpoints if it's not exposed (external-node-connectivity
).
And even when it exposed, it notifies client application with read-only-endpoints
pointing to each non-leader pgbouncer unit IP.
Pgbouncer always must provide application with both RW and read-only channels if there are postgresql replicas active. And, i assume, there is need to move read-only channel to different endpoint, because postgresql_client/v0 assumes that database name will be the same and only endpoint is different.
When not exposed, pgbouncer will ignore read-only channel, so that application just can't use postgresql replicas.
And when exposed, app will put all the non-leader pgbouncer unit IPs into read-only-endpoints
(but it has nothing to do with read-only channels to postgresql replicas, it just another pgbouncer units pointing to the same primary).
Operating system: Ubuntu 23.10
Juju CLI: 3.4.2-genericlinux-amd64
Juju agent: 3.4.0
postgresql charm revision: edge (rev. 393)
pgbouncer charm revision: 1/edge (rev. 142)
LXD: 5.21.1 LTS
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These updates are awaiting their schedule. Click on a checkbox to get an update now.
undefined
, cosl
).github/workflows/ci.yaml
canonical/data-platform-workflows v21.0.1
actions/checkout v4
codecov/codecov-action v4
canonical/data-platform-workflows v21.0.1
canonical/data-platform-workflows v21.0.1
.github/workflows/lib-check.yaml
actions/checkout v4
canonical/charming-actions 2.6.2
.github/workflows/release.yaml
canonical/data-platform-workflows v21.0.1
canonical/data-platform-workflows v21.0.1
.github/workflows/sync_docs.yaml
canonical/data-platform-workflows v21.0.1
pyproject.toml
ops ^2.16.0
pgconnstr ^1.0.1
tenacity ^9.0.0
cosl ^0.0.24
pydantic ^1.10.18
poetry-core ^1.9.0
cryptography ^43.0.0
jsonschema ^4.23.0
psycopg2 ^2.9.9
psycopg ^3.2.1
jinja2 ^3.1.4
ops >=2.0.0
poetry-core *
pydantic ^1.10, <2
cosl *
cryptography *
jsonschema *
opentelemetry-exporter-otlp-proto-http 1.21.0
ruff ^0.6.3
codespell ^2.3.0
coverage ^7.6.1
pytest ^8.3.2
pytest-asyncio *
parameterized ^0.9.0
pytest ^8.3.2
pytest-github-secrets v21.0.1
pytest-operator ^0.36.0
pytest-operator-cache v21.0.1
pytest-operator-groups v21.0.1
juju <=3.5.2.0
tenacity *
mailmanclient ^3.3.5
psycopg2-binary ^2.9.9
landscape-api-py3 ^0.9.0
allure-pytest ^2.13.5
allure-pytest-collection-report v21.0.1
.github/workflows/ci.yaml
juju/juju 2.9.50
juju/juju 3.4.5
juju/juju 3.4.5
.github/workflows/ci.yaml
juju 2.9.49.0
Deploy standart tutorial with COS and start continious writes on test-app.
All dashboards have data.
Missing data on dashboard for "Total Query Time" and "Database reserved pool size".
Hovewer the exporter provides them:
> juju ssh pgbouncer/0
ubuntu@juju-801057-2:~$ curl 127.0.0.1:9127/metrics | grep pgbouncer_stats_received_bytes_total
pgbouncer_stats_received_bytes_total{database="pgbouncer"} 0
0 pgbouncer_stats_received_bytes_total{database="postgresql_test_app_first_database"} 1.789629e+06
App Version Status Scale Charm Channel Rev Exposed Message
pgbouncer 1.21.0 active 1 pgbouncer 1/stable 88 no
pgbouncer-cos-agent active 1 grafana-agent latest/stable 65 no
postgresql 14.10 active 1 postgresql 14/stable 363 no Primary
postgresql-test-app active 1 postgresql-test-app latest/stable 119 no received database credentials of the first database
Unit Workload Agent Machine Public address Ports Message
postgresql-test-app/0* active idle 2 10.184.219.161 received database credentials of the first database
pgbouncer-cos-agent/1* active idle 10.184.219.161
pgbouncer/1* active idle 10.184.219.161
postgresql/0* active idle 0 10.184.219.86 5432/tcp Primary
No related information in logs.
juju deploy postgresql --channel=14/edge --revision=393 [email protected]
juju deploy data-integrator --channel=latest/stable [email protected] --config database-name=maasdb,extra-user-roles=svc_pgsql_maas --to 0
juju deploy pgbouncer --channel 1/edge [email protected]
juju integrate pgbouncer data-integrator
juju integrate postgresql pgbouncer
PgBouncer service is running and working
PGBouncer status: PgBouncer service pgbouncer-pgbouncer@0 not running
Operating system: Ubuntu 22.04.4 LTS
Juju CLI: 3.4.2-genericlinux-amd64
Juju agent:
Every 2.0s: juju status --relations --color ubuntu@jujunode1-tst:~$ juju status --relations
Model Controller Cloud/Region Version SLA Timestamp
postgresql-14edge-rev393-test lxd-remote-default lxd-remote/default 3.4.2 unsupported 15:03:09Z
App Version Status Scale Charm Channel Rev Exposed Message
data-integrator active 1 data-integrator latest/stable 19 no
pgbouncer 1.21.0 blocked 1 pgbouncer 1/edge 142 no PgBouncer service pgbouncer-pgbouncer@0 not running
postgresql 14.11 active 1 postgresql 14/edge 393 no Primary
Unit Workload Agent Machine Public address Ports Message
data-integrator/0* active idle 0 240.81.0.101
pgbouncer/0* blocked idle 240.81.0.101 6432/tcp PgBouncer service pgbouncer-pgbouncer@0 not running
postgresql/0* active idle 0 240.81.0.101 5432/tcp Primary
Machine State Address Inst id Base AZ Message
0 started 240.81.0.101 juju-f2963e-0 [email protected] Running
Integration provider Requirer Interface Type Message
data-integrator:data-integrator-peers data-integrator:data-integrator-peers data-integrator-peers peer
pgbouncer:database data-integrator:postgresql postgresql_client subordinate
pgbouncer:pgb-peers pgbouncer:pgb-peers pgb_peers peer
pgbouncer:upgrade pgbouncer:upgrade upgrade peer
postgresql:database pgbouncer:backend-database postgresql_client regular
postgresql:database-peers postgresql:database-peers postgresql_peers peer
postgresql:restart postgresql:restart rolling_op peer
postgresql:upgrade postgresql:upgrade upgrade peer
LXD: 5.20
Juju debug log:
juju debug-log --include pgbouncer/0 --limit 1000
unit-pgbouncer-0: 14:54:02 INFO juju.worker.uniter unit "pgbouncer/0" started
unit-pgbouncer-0: 14:54:03 INFO juju.worker.uniter hooks are retried true
unit-pgbouncer-0: 14:54:34 INFO juju Starting unit workers for "pgbouncer/0"
unit-pgbouncer-0: 14:54:34 INFO juju.worker.apicaller [a42e70] "unit-pgbouncer-0" successfully connected to "240.9.0.100:17070"
unit-pgbouncer-0: 14:54:38 INFO juju.worker.apicaller [a42e70] "unit-pgbouncer-0" successfully connected to "240.9.0.100:17070"
unit-pgbouncer-0: 14:54:43 INFO juju.worker.migrationminion migration migration phase is now: NONE
unit-pgbouncer-0: 14:54:43 INFO juju.worker.logger logger worker started
unit-pgbouncer-0: 14:54:47 INFO juju.worker.upgrader no waiter, upgrader is done
unit-pgbouncer-0: 14:55:11 INFO juju.worker.uniter unit "pgbouncer/0" started
unit-pgbouncer-0: 14:55:12 INFO juju.worker.uniter hooks are retried true
juju show-status-log pgbouncer/0
Time Type Status Message
10 Apr 2024 14:39:50Z juju-unit executing running pgb-peers-relation-changed hook
10 Apr 2024 14:39:54Z workload maintenance Updating database relation connection information
10 Apr 2024 14:39:55Z workload blocked PgBouncer service pgbouncer-pgbouncer@0 not running
10 Apr 2024 14:39:55Z workload maintenance updating PgBouncer users
10 Apr 2024 14:39:56Z workload blocked PgBouncer service pgbouncer-pgbouncer@0 not running
10 Apr 2024 14:39:58Z workload maintenance updating PgBouncer config
10 Apr 2024 14:39:58Z workload blocked PgBouncer service pgbouncer-pgbouncer@0 not running
10 Apr 2024 14:41:02Z juju-unit error hook failed: "pgb-peers-relation-changed"
10 Apr 2024 14:41:09Z juju-unit executing running pgb-peers-relation-changed hook
10 Apr 2024 14:41:13Z workload maintenance Updating database relation connection information
10 Apr 2024 14:41:14Z workload blocked PgBouncer service pgbouncer-pgbouncer@0 not running
10 Apr 2024 14:41:15Z workload blocked PgBouncer service pgbouncer-pgbouncer@1 not running
10 Apr 2024 14:41:16Z workload maintenance updating PgBouncer users
10 Apr 2024 14:41:16Z workload blocked PgBouncer service pgbouncer-pgbouncer@1 not running
10 Apr 2024 14:41:20Z workload maintenance updating PgBouncer config
10 Apr 2024 14:41:20Z workload blocked PgBouncer service pgbouncer-pgbouncer@1 not running
10 Apr 2024 14:41:21Z workload maintenance Reloading Pgbouncer
10 Apr 2024 14:41:21Z workload blocked PgBouncer service pgbouncer-pgbouncer@1 not running
10 Apr 2024 14:41:22Z workload blocked PgBouncer service pgbouncer-pgbouncer@0 not running
10 Apr 2024 14:55:13Z juju-unit idle
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.