Lập chỉ mục cơ sở dữ liệu là việc sử dụng các cấu trúc dữ liệu đặc biệt nhằm mục đích cải thiện hiệu suất, bằng cách đạt được quyền truy cập trực tiếp vào các trang dữ liệu. Chỉ mục cơ sở dữ liệu hoạt động giống như phần chỉ mục của một cuốn sách in:bằng cách xem trong phần chỉ mục, việc xác định (các) trang chứa cụm từ mà chúng tôi quan tâm sẽ nhanh hơn nhiều. Chúng tôi có thể dễ dàng xác định vị trí các trang và truy cập trực tiếp. . Điều này thay vì quét tuần tự các trang của cuốn sách, cho đến khi chúng tôi tìm thấy thuật ngữ mà chúng tôi đang tìm kiếm.
Chỉ mục là một công cụ thiết yếu trong tay của một DBA. Việc sử dụng các chỉ mục có thể mang lại hiệu suất tăng đáng kể cho nhiều miền dữ liệu khác nhau. PostgreSQL được biết đến với khả năng mở rộng tuyệt vời và bộ sưu tập phong phú của cả phần bổ trợ lõi và bên thứ 3, và lập chỉ mục cũng không ngoại lệ với quy tắc này. Chỉ mục PostgreSQL bao gồm nhiều trường hợp, từ chỉ mục cây b đơn giản nhất trên các loại vô hướng đến chỉ mục GiST không gian địa lý đến chỉ mục fts hoặc json hoặc mảng GIN.
Tuy nhiên, các chỉ mục vẫn tuyệt vời như chúng có vẻ (và thực tế là như vậy!) Không miễn phí. Có một hình phạt nhất định đi kèm với việc ghi trên một bảng được lập chỉ mục. Vì vậy, DBA, trước khi kiểm tra các tùy chọn của cô ấy để tạo một chỉ mục cụ thể, trước tiên phải đảm bảo rằng chỉ mục đã nói có ý nghĩa ngay từ đầu, nghĩa là lợi ích thu được từ việc tạo ra nó sẽ nhiều hơn tổn thất về hiệu suất khi ghi.
Thuật ngữ chỉ mục cơ bản của PostgreSQL
Trước khi mô tả các loại chỉ mục trong PostgreSQL và việc sử dụng chúng, hãy xem qua một số thuật ngữ mà bất kỳ DBA nào cũng sẽ gặp phải sớm hay muộn khi đọc tài liệu.
- Phương pháp Truy cập Chỉ mục (còn được gọi là Phương thức truy cập ):Loại chỉ mục (B-tree, GiST, GIN, v.v.)
- Loại: kiểu dữ liệu của cột được lập chỉ mục
- Nhà điều hành: một hàm giữa hai kiểu dữ liệu
- Họ Nhà điều hành: toán tử kiểu dữ liệu chéo, bằng cách nhóm các toán tử của các kiểu có hành vi tương tự nhau
- Hạng nhà điều hành (còn được đề cập là chiến lược chỉ mục ):xác định các toán tử sẽ được chỉ mục sử dụng cho một cột
Trong danh mục hệ thống của PostgreSQL, các phương thức truy cập được lưu trữ trong pg_am, các lớp toán tử trong pg_opclass, họ toán tử trong pg_opfamily. Các yếu tố phụ thuộc ở trên được thể hiện trong sơ đồ dưới đây:
Các loại chỉ mục trong PostgreSQL
PostgreSQL cung cấp các loại Chỉ mục sau:
- Cây B: chỉ mục mặc định, áp dụng cho các loại có thể được sắp xếp
- Băm: chỉ xử lý bình đẳng
- GiST: phù hợp với các kiểu dữ liệu không vô hướng (ví dụ:hình dạng hình học, fts, mảng)
- SP-GiST: GIST được phân vùng không gian, một sự phát triển của GiST để xử lý các cấu trúc không cân bằng (cây tứ phân, cây k-d, cây cơ số)
- GIN: phù hợp với các loại phức tạp (ví dụ:jsonb, fts, mảng)
- BRIN: một loại chỉ mục tương đối mới hỗ trợ dữ liệu có thể được sắp xếp bằng cách lưu trữ các giá trị tối thiểu / tối đa trong mỗi khối
Thấp, chúng tôi sẽ cố gắng làm bẩn tay bằng một số ví dụ trong thế giới thực. Tất cả các ví dụ được đưa ra đều được thực hiện với PostgreSQL 10.0 (với cả máy khách 10 và 9 psql) trên FreeBSD 11.1.
Chỉ mục cây B
Giả sử chúng ta có bảng sau:
create table part (
id serial primary key,
partno varchar(20) NOT NULL UNIQUE,
partname varchar(80) NOT NULL,
partdescr text,
machine_id int NOT NULL
);
testdb=# \d part
Table "public.part"
Column | Type | Modifiers
------------+-----------------------+---------------------------------------------------
id | integer | not null default nextval('part_id_seq'::regclass)
partno | character varying(20)| not null
partname | character varying(80)| not null
partdescr | text |
machine_id | integer | not null
Indexes:
"part_pkey" PRIMARY KEY, btree (id)
"part_partno_key" UNIQUE CONSTRAINT, btree (partno)
Khi chúng tôi xác định bảng khá phổ biến này, PostgreSQL tạo ra hai chỉ mục B-tree duy nhất đằng sau hậu trường:part_pkey và part_partno_key. Vì vậy, mọi ràng buộc duy nhất trong PostgreSQL đều được thực hiện với một INDEX duy nhất. Cho phép điền vào bảng của chúng ta một triệu hàng dữ liệu:
testdb=# with populate_qry as (select gs from generate_series(1,1000000) as gs )
insert into part (partno, partname,machine_id) SELECT 'PNo:'||gs, 'Part '||gs,0 from populate_qry;
INSERT 0 1000000
Bây giờ chúng ta hãy thử thực hiện một số truy vấn trên bàn của chúng ta. Đầu tiên, chúng tôi yêu cầu ứng dụng khách psql báo cáo thời gian truy vấn bằng cách nhập \ timing:
testdb=# select * from part where id=100000;
id | partno | partname | partdescr | machine_id
--------+------------+-------------+-----------+------------
100000 | PNo:100000 | Part 100000 | | 0
(1 row)
Time: 0,284 ms
testdb=# select * from part where partno='PNo:100000';
id | partno | partname | partdescr | machine_id
--------+------------+-------------+-----------+------------
100000 | PNo:100000 | Part 100000 | | 0
(1 row)
Time: 0,319 ms
Chúng tôi quan sát thấy rằng chỉ cần vài phần nghìn giây để nhận được kết quả của chúng tôi. Chúng tôi mong đợi điều này vì đối với cả hai cột được sử dụng trong các truy vấn trên, chúng tôi đã xác định các chỉ mục thích hợp. Bây giờ chúng ta hãy thử truy vấn tên phần của cột mà không có chỉ mục nào tồn tại.
testdb=# select * from part where partname='Part 100000';
id | partno | partname | partdescr | machine_id
--------+------------+-------------+-----------+------------
100000 | PNo:100000 | Part 100000 | | 0
(1 row)
Time: 89,173 ms
Ở đây, chúng ta thấy rõ ràng rằng đối với cột không được lập chỉ mục, hiệu suất giảm đáng kể. Bây giờ, hãy tạo một chỉ mục trên cột đó và lặp lại truy vấn:
testdb=# create index part_partname_idx ON part(partname);
CREATE INDEX
Time: 15734,829 ms (00:15,735)
testdb=# select * from part where partname='Part 100000';
id | partno | partname | partdescr | machine_id
--------+------------+-------------+-----------+------------
100000 | PNo:100000 | Part 100000 | | 0
(1 row)
Time: 0,525 ms
Chỉ mục mới part_partname_idx của chúng tôi cũng là một chỉ mục B-tree (mặc định). Đầu tiên, chúng tôi lưu ý rằng việc tạo chỉ mục trên bảng triệu hàng mất một lượng thời gian đáng kể, khoảng 16 giây. Sau đó, chúng tôi nhận thấy rằng tốc độ truy vấn của chúng tôi đã được tăng từ 89 mili giây xuống 0,525 mili giây. Các chỉ mục cây B, ngoài việc kiểm tra sự bình đẳng, cũng có thể trợ giúp với các truy vấn liên quan đến các toán tử khác trên các kiểu có thứ tự, chẳng hạn như <, <=,> =,>. Hãy thử với <=và> =
testdb=# select count(*) from part where partname>='Part 9999900';
count
-------
9
(1 row)
Time: 0,359 ms
testdb=# select count(*) from part where partname<='Part 9999900';
count
--------
999991
(1 row)
Time: 355,618 ms
Truy vấn đầu tiên nhanh hơn nhiều so với truy vấn thứ hai, bằng cách sử dụng các từ khóa GIẢI THÍCH (hoặc PHÂN TÍCH GIẢI THÍCH), chúng tôi có thể xem chỉ mục thực tế có được sử dụng hay không:
testdb=# explain select count(*) from part where partname>='Part 9999900';
QUERY PLAN
-----------------------------------------------------------------------------------------
Aggregate (cost=8.45..8.46 rows=1 width=8)
-> Index Only Scan using part_partname_idx on part (cost=0.42..8.44 rows=1 width=0)
Index Cond: (partname >= 'Part 9999900'::text)
(3 rows)
Time: 0,671 ms
testdb=# explain select count(*) from part where partname<='Part 9999900';
QUERY PLAN
----------------------------------------------------------------------------------------
Finalize Aggregate (cost=14603.22..14603.23 rows=1 width=8)
-> Gather (cost=14603.00..14603.21 rows=2 width=8)
Workers Planned: 2
-> Partial Aggregate (cost=13603.00..13603.01 rows=1 width=8)
-> Parallel Seq Scan on part (cost=0.00..12561.33 rows=416667 width=0)
Filter: ((partname)::text <= 'Part 9999900'::text)
(6 rows)
Time: 0,461 ms
Trong trường hợp đầu tiên, người lập kế hoạch truy vấn chọn sử dụng chỉ mục part_partname_idx. Chúng tôi cũng nhận thấy rằng điều này sẽ dẫn đến việc quét chỉ mục, có nghĩa là không có quyền truy cập vào các bảng dữ liệu. Trong trường hợp thứ hai, người lập kế hoạch xác định rằng không có ích lợi gì khi sử dụng chỉ mục vì kết quả trả về là một phần lớn của bảng, trong trường hợp này, việc quét tuần tự được cho là nhanh hơn.
Chỉ mục băm
Việc sử dụng các chỉ mục băm lên đến và bao gồm cả PgSQL 9.6 không được khuyến khích do các lý do liên quan đến việc thiếu văn bản WAL. Kể từ PgSQL 10.0, các vấn đề đó đã được khắc phục, nhưng các chỉ mục băm vẫn còn ít ý nghĩa để sử dụng. Có những nỗ lực trong PgSQL 11 để biến các chỉ mục băm trở thành một phương pháp chỉ mục lớp đầu tiên cùng với những người anh em lớn hơn của nó (B-tree, GiST, GIN). Vì vậy, hãy lưu ý đến điều này, hãy thực sự thử một chỉ mục băm đang hoạt động.
Chúng tôi sẽ làm phong phú thêm bảng part của mình với một loại cột mới và điền vào nó các giá trị có phân phối bằng nhau, sau đó chạy một truy vấn kiểm tra loại part bằng ‘Chỉ đạo’:
testdb=# alter table part add parttype varchar(100) CHECK (parttype in ('Engine','Suspension','Driveline','Brakes','Steering','General')) NOT NULL DEFAULT 'General';
ALTER TABLE
Time: 42690,557 ms (00:42,691)
testdb=# with catqry as (select id,(random()*6)::int % 6 as cat from part)
update part SET parttype = CASE WHEN cat=1 THEN 'Engine' WHEN cat=2 THEN 'Suspension' WHEN cat=3 THEN 'Driveline' WHEN cat=4 THEN 'Brakes' WHEN cat=5 THEN 'Steering' ELSE 'General' END FROM catqry WHERE part.id=catqry.id;
UPDATE 1000000
Time: 46345,386 ms (00:46,345)
testdb=# select count(*) from part where id % 500 = 0 AND parttype = 'Steering';
count
-------
322
(1 row)
Time: 93,361 ms
Bây giờ chúng ta tạo chỉ mục băm cho cột mới này và thử lại truy vấn trước đó:
testdb=# create index part_parttype_idx ON part USING hash(parttype);
CREATE INDEX
Time: 95525,395 ms (01:35,525)
testdb=# analyze ;
ANALYZE
Time: 1986,642 ms (00:01,987)
testdb=# select count(*) from part where id % 500 = 0 AND parttype = 'Steering';
count
-------
322
(1 row)
Time: 63,634 ms
Chúng tôi ghi nhận sự cải thiện sau khi sử dụng chỉ số băm. Bây giờ chúng ta sẽ so sánh hiệu suất của một chỉ mục băm trên số nguyên với chỉ mục b-tree tương đương.
testdb=# update part set machine_id = id;
UPDATE 1000000
Time: 392548,917 ms (06:32,549)
testdb=# select * from part where id=500000;
id | partno | partname | partdescr | machine_id | parttype
--------+------------+-------------+-----------+------------+------------
500000 | PNo:500000 | Part 500000 | | 500000 | Suspension
(1 row)
Time: 0,316 ms
testdb=# select * from part where machine_id=500000;
id | partno | partname | partdescr | machine_id | parttype
--------+------------+-------------+-----------+------------+------------
500000 | PNo:500000 | Part 500000 | | 500000 | Suspension
(1 row)
Time: 97,037 ms
testdb=# create index part_machine_id_idx ON part USING hash(machine_id);
CREATE INDEX
Time: 4756,249 ms (00:04,756)
testdb=#
testdb=# select * from part where machine_id=500000;
id | partno | partname | partdescr | machine_id | parttype
--------+------------+-------------+-----------+------------+------------
500000 | PNo:500000 | Part 500000 | | 500000 | Suspension
(1 row)
Time: 0,297 ms
Như chúng ta thấy, với việc sử dụng các chỉ mục băm, tốc độ của các truy vấn kiểm tra sự bình đẳng rất gần với tốc độ của các chỉ mục B-tree. Các chỉ mục băm được cho là nhanh hơn một chút về bình đẳng so với cây B, trên thực tế, chúng tôi đã phải thử từng truy vấn hai hoặc ba lần cho đến khi chỉ mục băm cho kết quả tốt hơn so với b-tree tương đương.
Tải xuống Báo cáo chính thức hôm nay Quản lý &Tự động hóa PostgreSQL với ClusterControlTìm hiểu về những điều bạn cần biết để triển khai, giám sát, quản lý và mở rộng PostgreSQLTải xuống Báo cáo chính thứcChỉ mục GiST
GiST (Cây tìm kiếm tổng quát) không chỉ là một loại chỉ mục đơn lẻ, mà là một cơ sở hạ tầng để xây dựng nhiều chiến lược lập chỉ mục. Bản phân phối PostgreSQL mặc định cung cấp hỗ trợ cho các kiểu dữ liệu hình học, tsquery và tsvector. Trong đóng góp có các triển khai của nhiều lớp toán tử khác. Bằng cách đọc tài liệu và công cụ đóng góp, người đọc sẽ nhận thấy rằng có một sự trùng lặp khá lớn giữa các trường hợp sử dụng GiST và GIN:mảng int, tìm kiếm toàn văn để đặt tên cho các trường hợp chính. Trong những trường hợp đó, GIN nhanh hơn và tài liệu chính thức nêu rõ điều đó. Tuy nhiên, GiST cung cấp hỗ trợ kiểu dữ liệu hình học mở rộng. Ngoài ra, tại thời điểm viết bài này, GiST (và SP-GiST) là phương thức có ý nghĩa duy nhất có thể được sử dụng với các ràng buộc loại trừ. Chúng ta sẽ xem một ví dụ về điều này. Giả sử (trong lĩnh vực cơ khí) chúng ta có yêu cầu xác định các biến thể kiểu máy cho một loại máy cụ thể, có giá trị trong một khoảng thời gian nhất định; và đối với một biến thể cụ thể, không có biến thể nào khác có thể tồn tại cho cùng một loại máy có khoảng thời gian trùng lặp (xung đột) với khoảng thời gian biến thể cụ thể.
create table machine_type (
id SERIAL PRIMARY KEY,
mtname varchar(50) not null,
mtvar varchar(20) not null,
start_date date not null,
end_date date,
CONSTRAINT machine_type_uk UNIQUE (mtname,mtvar)
);
Ở trên, chúng tôi đã nói với PostgreSQL rằng đối với mỗi tên loại máy (mtname) chỉ có thể có một biến thể (mtvar). Start_date biểu thị ngày bắt đầu của khoảng thời gian mà biến thể loại máy này có hiệu lực và end_date biểu thị ngày kết thúc của khoảng thời gian này. Null end_date có nghĩa là biến thể loại máy hiện đang hợp lệ. Bây giờ chúng ta muốn thể hiện yêu cầu không chồng chéo với một ràng buộc. Cách để làm điều này là với một ràng buộc loại trừ:
testdb=# alter table machine_type ADD CONSTRAINT machine_type_per EXCLUDE USING GIST (mtname WITH =,daterange(start_date,end_date) WITH &&);
Cú pháp EXCLUDE PostgreSQL cho phép chúng ta chỉ định nhiều cột thuộc các kiểu khác nhau và với một toán tử khác nhau cho mỗi cột. &&là toán tử chồng chéo cho phạm vi ngày và =là toán tử bình đẳng phổ biến cho varchar. Nhưng miễn là chúng ta nhấn enter thì PostgreSQL sẽ phàn nàn với một thông báo:
ERROR: data type character varying has no default operator class for access method "gist"
HINT: You must specify an operator class for the index or define a default operator class for the data type.
Điều còn thiếu ở đây là hỗ trợ opclass GiST cho varchar. Miễn là chúng tôi đã tạo và cài đặt thành công tiện ích mở rộng btree_gist, chúng tôi có thể tiếp tục tạo tiện ích mở rộng:
testdb=# create extension btree_gist ;
CREATE EXTENSION
Và sau đó thử tạo lại ràng buộc và kiểm tra nó:
testdb=# alter table machine_type ADD CONSTRAINT machine_type_per EXCLUDE USING GIST (mtname WITH =,daterange(start_date,end_date) WITH &&);
ALTER TABLE
testdb=# insert into machine_type (mtname,mtvar,start_date,end_date) VALUES('Subaru EJ20','SH','2008-01-01','2013-01-01');
INSERT 0 1
testdb=# insert into machine_type (mtname,mtvar,start_date,end_date) VALUES('Subaru EJ20','SG','2002-01-01','2009-01-01');
ERROR: conflicting key value violates exclusion constraint "machine_type_per"
DETAIL: Key (mtname, daterange(start_date, end_date))=(Subaru EJ20, [2002-01-01,2009-01-01)) conflicts with existing key (mtname, daterange(start_date, end_date))=(Subaru EJ20, [2008-01-01,2013-01-01)).
testdb=# insert into machine_type (mtname,mtvar,start_date,end_date) VALUES('Subaru EJ20','SG','2002-01-01','2008-01-01');
INSERT 0 1
testdb=# insert into machine_type (mtname,mtvar,start_date,end_date) VALUES('Subaru EJ20','SJ','2013-01-01',null);
INSERT 0 1
testdb=# insert into machine_type (mtname,mtvar,start_date,end_date) VALUES('Subaru EJ20','SJ2','2018-01-01',null);
ERROR: conflicting key value violates exclusion constraint "machine_type_per"
DETAIL: Key (mtname, daterange(start_date, end_date))=(Subaru EJ20, [2018-01-01,)) conflicts with existing key (mtname, daterange(start_date, end_date))=(Subaru EJ20, [2013-01-01,)).
Chỉ mục SP-GiST
SP-GiST là viết tắt của GiST được phân vùng theo không gian, giống như GiST, là một cơ sở hạ tầng cho phép phát triển nhiều chiến lược khác nhau trong lĩnh vực cấu trúc dữ liệu dựa trên đĩa không cân bằng. Bản phân phối PgSQL mặc định cung cấp hỗ trợ cho các điểm hai chiều, phạm vi (bất kỳ loại nào), các loại văn bản và inet. Giống như GiST, SP-GiST có thể được sử dụng trong các ràng buộc loại trừ, theo cách tương tự như ví dụ được hiển thị trong chương trước.
Chỉ mục GIN
GIN (Chỉ số đảo ngược tổng quát) như GiST và SP-GiST có thể cung cấp nhiều chiến lược lập chỉ mục. GIN phù hợp khi chúng ta muốn lập chỉ mục các cột kiểu hỗn hợp. Bản phân phối PostgreSQL mặc định cung cấp hỗ trợ cho bất kỳ loại mảng nào, jsonb và tìm kiếm toàn văn (tsvector). Trong đóng góp có các triển khai của nhiều lớp toán tử khác. Jsonb, một tính năng được đánh giá cao của PostgreSQL (và một bản phát triển tương đối gần đây (9.4+)) đang dựa vào GIN để hỗ trợ chỉ mục. Một cách sử dụng phổ biến khác của GIN là lập chỉ mục cho tìm kiếm toàn văn. Tìm kiếm toàn văn trong PgSQL xứng đáng có một bài viết riêng, vì vậy chúng tôi sẽ chỉ trình bày ở đây phần lập chỉ mục. Trước tiên, chúng ta hãy chuẩn bị cho bảng của chúng ta một chút, bằng cách cung cấp không phải giá trị null cho cột partdescr và cập nhật một hàng duy nhất có giá trị có ý nghĩa:
testdb=# update part set partdescr ='';
UPDATE 1000000
Time: 383407,114 ms (06:23,407)
testdb=# update part set partdescr = 'thermostat for the cooling system' where id=500000;
UPDATE 1
Time: 2,405 ms
Sau đó, chúng tôi thực hiện tìm kiếm văn bản trên cột mới được cập nhật:
testdb=# select * from part where partdescr @@ 'thermostat';
id | partno | partname | partdescr | machine_id | parttype
--------+------------+-------------+-----------------------------------+------------+------------
500000 | PNo:500000 | Part 500000 | thermostat for the cooling system | 500000 | Suspension
(1 row)
Time: 2015,690 ms (00:02,016)
Điều này là khá chậm, gần 2 giây để mang lại kết quả của chúng tôi. Bây giờ, hãy thử tạo chỉ mục GIN trên loại tsvector và lặp lại truy vấn, sử dụng cú pháp thân thiện với chỉ mục:
testdb=# CREATE INDEX part_partdescr_idx ON part USING gin(to_tsvector('english',partdescr));
CREATE INDEX
Time: 1431,550 ms (00:01,432)
testdb=# select * from part where to_tsvector('english',partdescr) @@ to_tsquery('thermostat');
id | partno | partname | partdescr | machine_id | parttype
--------+------------+-------------+-----------------------------------+------------+------------
500000 | PNo:500000 | Part 500000 | thermostat for the cooling system | 500000 | Suspension
(1 row)
Time: 0,952 ms
Và chúng tôi nhận được tốc độ gấp 2000 lần. Ngoài ra, chúng tôi có thể lưu ý khoảng thời gian tương đối ngắn mà chỉ mục được tạo. Bạn có thể thử nghiệm việc sử dụng GiST thay vì GIN trong ví dụ trên và đo lường hiệu suất của việc đọc, ghi và tạo chỉ mục cho cả hai phương pháp truy cập.
Chỉ mục BRIN
BRIN (Chỉ số phạm vi khối) là bổ sung mới nhất cho tập hợp các loại chỉ mục của PostgreSQL, kể từ khi nó được giới thiệu trong PostgreSQL 9.5, chỉ có một vài năm là tính năng cốt lõi tiêu chuẩn. BRIN hoạt động trên các bảng rất lớn bằng cách lưu trữ thông tin tóm tắt cho một tập hợp các trang được gọi là “Phạm vi khối”. Chỉ mục BRIN bị mất dữ liệu (như GiST) và điều này đòi hỏi cả logic bổ sung trong trình thực thi truy vấn của PostgreSQL và cũng cần bảo trì thêm. Hãy xem BRIN hoạt động:
testdb=# select count(*) from part where machine_id BETWEEN 5000 AND 10000;
count
-------
5001
(1 row)
Time: 100,376 ms
testdb=# create index part_machine_id_idx_brin ON part USING BRIN(machine_id);
CREATE INDEX
Time: 569,318 ms
testdb=# select count(*) from part where machine_id BETWEEN 5000 AND 10000;
count
-------
5001
(1 row)
Time: 5,461 ms
Ở đây, chúng tôi thấy tốc độ tăng trung bình ~ 18 lần khi sử dụng chỉ số BRIN. Tuy nhiên, ngôi nhà thực sự của BRIN nằm trong phạm vi dữ liệu lớn, vì vậy, chúng tôi hy vọng sẽ thử nghiệm công nghệ tương đối mới này trong các tình huống thế giới thực trong tương lai.